/* // // 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.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; 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.metadata.Cube; import org.olap4j.metadata.Level; import org.olap4j.metadata.Measure; import org.openrdf.query.BooleanQuery; import org.openrdf.query.MalformedQueryException; import org.openrdf.query.QueryEvaluationException; import org.openrdf.query.QueryLanguage; import org.openrdf.query.TupleQuery; import org.openrdf.query.TupleQueryResultHandlerException; import org.openrdf.query.Update; import org.openrdf.query.UpdateExecutionException; import org.openrdf.query.resultio.sparqlxml.SPARQLResultsXMLWriter; import org.openrdf.repository.RepositoryConnection; import org.openrdf.repository.RepositoryException; import org.openrdf.repository.sail.SailRepository; import org.openrdf.repository.sail.SailRepositoryConnection; import org.openrdf.rio.RDFFormat; import org.openrdf.rio.RDFParseException; import org.openrdf.sail.memory.MemoryStore; 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; /** * The EmbeddedSesameEngine manages an embedded Sesame repository (triple store) * while executing metadata or olap queries. * * @author b-kaempgen * */ public class QcrumbEngine implements LinkedDataCubesEngine { // Meta data attributes 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 = "LdCatalogSchema"; private static final String TABLE_SCHEM = "LdCatalogSchema"; public String DATASOURCENAME; public String DATASOURCEVERSION; // Helper attributes /** * Map of locations that have been loaded into the embedded triple store. */ private HashMap<Integer, Boolean> loadedMap = new HashMap<Integer, Boolean>(); /** * The Sesame repository (triple store). */ private SailRepository repo; public QcrumbEngine(URL serverUrlObject, ArrayList<String> datastructuredefinitions, ArrayList<String> datasets, String databasename) { // We actually do not need that. URL = serverUrlObject.toString(); if (databasename.equals("EMBEDDEDSESAME")) { DATASOURCENAME = databasename; DATASOURCEVERSION = "1.0"; } // Store this.repo = new SailRepository(new MemoryStore()); try { repo.initialize(); // do something interesting with the values here... // con.close(); } catch (RepositoryException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Those given URIs we load and add to the location map for (String string : datastructuredefinitions) { try { String location = askForLocation(string); if (!isStored(location)) { loadInStore(location); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for (String string : datasets) { try { String location = askForLocation(string); if (!isStored(location)) { loadInStore(location); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 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 */ @Deprecated private String askForFrom(boolean isDsdQuery) { return ""; } /** * Helper Method for asking for location * * @param uri * @return * @throws MalformedURLException * @throws IOException */ private String askForLocation(String uri) throws MalformedURLException { Olap4ldUtil._log.config("Ask for location: " + uri + "..."); 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) { // Header may be a absolute or relative URL if (header.startsWith("http:") || header.startsWith("https:")) { // absolute URL uri = header; } else { /* * relative URL May be: Gleiche Domäne, Gleiche Ressource, * ein Pfad-Segment-Aufwärts, gleiches Pfad-Segment (see * http://de.wikipedia.org/wiki/Uniform_Resource_Locator# * Relative_URL) */ if (header.startsWith("/")) { String domain = url.getHost(); String protocol = url.getProtocol(); uri = protocol + "://" + domain + header; } else if (header.startsWith("../") || header.startsWith("./")) { // 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; } else { int index = uri.lastIndexOf("/"); uri = uri.substring(0, index + 1) + header; } } } // We should remove # uris if (uri.contains("#")) { int index = uri.lastIndexOf("#"); uri = uri.substring(0, index); } } catch (IOException e) { throw new MalformedURLException(e.getMessage()); } Olap4ldUtil._log.config("... result: " + uri); 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 * (not used) * @return */ private List<Node[]> executeSparqlSelectQuery(String query, boolean caching) { Olap4ldUtil._log.config("SPARQL query: " + query); List<Node[]> myBindings = new ArrayList<Node[]>(); try { RepositoryConnection con = repo.getConnection(); ByteArrayOutputStream boas = new ByteArrayOutputStream(); // FileOutputStream fos = new // FileOutputStream("/home/benedikt/Workspaces/Git-Repositories/olap4ld/OLAP4LD-trunk/resources/result.srx"); SPARQLResultsXMLWriter sparqlWriter = new SPARQLResultsXMLWriter( boas); TupleQuery tupleQuery = con.prepareTupleQuery(QueryLanguage.SPARQL, query); tupleQuery.evaluate(sparqlWriter); ByteArrayInputStream bais = new ByteArrayInputStream( boas.toByteArray()); // String xmlwriterstreamString = // Olap4ldLinkedDataUtil.convertStreamToString(bais); // System.out.println(xmlwriterstreamString); // Transform sparql xml to nx InputStream nx = Olap4ldLinkedDataUtil.transformSparqlXmlToNx(bais); 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()); } ; } con.close(); boas.close(); // do something interesting with the values here... // con.close(); } catch (RepositoryException 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(); } catch (MalformedQueryException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (QueryEvaluationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (TupleQueryResultHandlerException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } return myBindings; } /** * Loads location into store and mark it as stored. * * @param location */ private void loadInStore(String location) { SailRepositoryConnection con = null; try { Olap4ldUtil._log.config("Load in store: " + location); con = repo.getConnection(); URL locationurl = new URL(location); if (location.endsWith(".rdf") || location.endsWith(".xml")) { con.add(locationurl, locationurl.toString(), RDFFormat.RDFXML); } else if (location.endsWith(".ttl")) { con.add(locationurl, locationurl.toString(), RDFFormat.TURTLE); } else { // Guess file format RDFFormat format = RDFFormat.forFileName(location); if (format != null) { con.add(locationurl, locationurl.toString(), format); } else { // Heuristics // Check first line InputStream is; HttpURLConnection connection = (HttpURLConnection) locationurl .openConnection(); // Error if (connection.getResponseCode() >= 400) { is = connection.getErrorStream(); BufferedReader rd = new BufferedReader( new InputStreamReader(is)); String response = ""; String line; while ((line = rd.readLine()) != null) { response += line; } Olap4ldUtil._log.config("Error response: " + response); rd.close(); is.close(); } else { is = connection.getInputStream(); BufferedReader in = new BufferedReader( new InputStreamReader( connection.getInputStream())); String inputLine; // Read first line only. // while ((inputLine = in.readLine()) != null) { // } inputLine = in.readLine(); if (inputLine != null && (inputLine .startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>") || inputLine .startsWith("<?xml version=\"1.0\" encoding=\"utf-8\"?>") || inputLine .startsWith("<"))) { con.add(locationurl, locationurl.toString(), RDFFormat.RDFXML); Olap4ldUtil._log .config("Had to guess format to be RDFXML: " + location); } else { con.add(locationurl, locationurl.toString(), RDFFormat.TURTLE); Olap4ldUtil._log .config("Had to guess format to be Turtle: " + location); } in.close(); is.close(); } } } } catch (RepositoryException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (RDFParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } loadedMap.put(location.hashCode(), true); if (con != null) { try { con.close(); } catch (RepositoryException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // Log content String query = "select * where {?s ?p ?o}"; Olap4ldUtil._log.config("Check loaded data: " + query); executeSparqlSelectQuery(query, false); } /** * * @param location * @return boolean value say whether location already loaded */ private boolean isStored(String location) { return this.loadedMap.containsKey(location.hashCode()); } /** * Check whether we query for "Measures". * * @param dimensionUniqueName * @param hierarchyUniqueName * @param levelUniqueName * @return */ private boolean isMeasureQueriedForExplicitly(Node dimensionUniqueName, Node hierarchyUniqueName, Node levelUniqueName) { // If one is set, it should not be Measures, not. // Watch out: no square brackets are needed. boolean explicitlyStated = (dimensionUniqueName != null && dimensionUniqueName .equals(Olap4ldLinkedDataUtil.MEASURE_DIMENSION_NAME)) || (hierarchyUniqueName != null && hierarchyUniqueName .equals(Olap4ldLinkedDataUtil.MEASURE_DIMENSION_NAME)) || (levelUniqueName != null && levelUniqueName .equals(Olap4ldLinkedDataUtil.MEASURE_DIMENSION_NAME)); return explicitlyStated; } /** * * @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. * * * * @return Node[]{} */ public List<Node[]> getCubes(Restrictions restrictions) throws OlapException { checkSufficientInformationGathered(restrictions); String additionalFilters = createFilterForRestrictions(restrictions); String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getCubes_regular.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> result = executeSparqlSelectQuery(querytemplate, 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; } /** * Depending on the restrictions, we check whether sufficient Linked Data * has been gathered. If not, we gather more or throw exception. * * @param restrictions */ private void checkSufficientInformationGathered(Restrictions restrictions) throws OlapException { // For now, if only cube is asked for, we load ds and dsd and run checks try { if (restrictions.cubeNamePattern != null) { Node uri = restrictions.cubeNamePattern; if (!isStored(uri.toString())) { // We also store URI in map String location = askForLocation(uri.toString()); // Here, we always load, since we want to update. if (!isStored(location)) { // We load the entire cube loadCube(uri.toString(), location); // We need to materialise implicit information runNormalizationAlgorithm(); // Now that we presumably have loaded all necessary // data, we check integrity constraints checkIntegrityConstraints(); // Own checks: SailRepositoryConnection con = repo.getConnection(); String prefixbindings = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX skos: <http://www.w3.org/2004/02/skos/core#> PREFIX qb: <http://purl.org/linked-data/cube#> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> PREFIX owl: <http://www.w3.org/2002/07/owl#> "; // Dataset should have at least one // observation String testquery = prefixbindings + "ASK { ?obs qb:dataSet ?CUBE_NAME FILTER (?CUBE_NAME = <" + uri + ">)}"; BooleanQuery booleanQuery = con.prepareBooleanQuery( QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == false) { throw new OlapException( "Failed own check: Dataset should have at least one observation. "); } // XXX Possible other checks // No dimensions // No aggregation function // Code list empty // No member // Important! con.close(); } } } } catch (MalformedURLException e) { throw new OlapException("Problem with malformed url: " + e.getMessage()); } catch (RepositoryException e) { throw new OlapException("Problem with repository: " + e.getMessage()); } catch (MalformedQueryException e) { throw new OlapException("Problem with malformed query: " + e.getMessage()); } catch (QueryEvaluationException e) { throw new OlapException("Problem with query evaluation: " + e.getMessage()); } } /** * We load all data for a cube. * * @param location */ private void loadCube(String uri, String location) throws OlapException { try { loadInStore(location); // For quicker check, also set loaded uri loadedMap.put(uri.hashCode(), true); // For everything else: Check whether really cube RepositoryConnection con; con = repo.getConnection(); // qb:structure is more robust than a qb:DataSet. String testquery = "PREFIX qb: <http://purl.org/linked-data/cube#> ASK { ?CUBE_NAME qb:structure ?dsd. FILTER (?CUBE_NAME = <" + uri + ">)}"; BooleanQuery booleanQuery = con.prepareBooleanQuery( QueryLanguage.SPARQL, testquery); boolean isDataset = booleanQuery.evaluate(); con.close(); if (!isDataset) { throw new OlapException( "A cube should be a qb:DataSet and serve via qb:structure a qb:DataStructureDefinition, also this one " + uri + "!"); } else { // If loading ds, also load dsd. Ask for DSD URI and // load String query = "PREFIX qb: <http://purl.org/linked-data/cube#> SELECT ?dsd WHERE {<" + uri + "> qb:structure ?dsd}"; List<Node[]> dsd = executeSparqlSelectQuery(query, true); // There should be a dsd // Note in spec: // "Every qb:DataSet has exactly one associated qb:DataStructureDefinition." if (dsd.size() <= 1) { throw new OlapException( "A cube should serve a data structure definition!"); } else { // Get the second String dsduri = dsd.get(1)[0].toString(); location = askForLocation(dsduri); // Since ds and dsd location can be the same, // check // whether loaded already if (!isStored(location)) { loadInStore(location); // For quicker check, also add dsduri loadedMap.put(dsduri.hashCode(), true); } } // If loading ds, also load measures query = "PREFIX qb: <http://purl.org/linked-data/cube#> SELECT ?measure WHERE {<" + uri + "> qb:structure ?dsd. ?dsd qb:component ?comp. ?comp qb:measure ?measure}"; List<Node[]> measures = executeSparqlSelectQuery(query, true); // There should be a dsd // Note in spec: // "Every qb:DataSet has exactly one associated qb:DataStructureDefinition." if (measures.size() <= 1) { throw new OlapException("A cube should serve a measure!"); } else { boolean first = true; for (Node[] nodes : measures) { if (first) { first = false; continue; } String measureuri = nodes[0].toString(); location = askForLocation(measureuri); // Since ds and dsd location can be the // same, // check // whether loaded already if (!isStored(location)) { loadInStore(location); // For quicker check, also add dsduri loadedMap.put(measureuri.hashCode(), true); } } } // If loading ds, also load dimensions query = "PREFIX qb: <http://purl.org/linked-data/cube#> SELECT ?dimension WHERE {<" + uri + "> qb:structure ?dsd. ?dsd qb:component ?comp. ?comp qb:dimension ?dimension}"; List<Node[]> dimensions = executeSparqlSelectQuery(query, true); // There should be a dsd // Note in spec: // "Every qb:DataSet has exactly one associated qb:DataStructureDefinition." if (dimensions.size() <= 1) { throw new OlapException("A cube should serve a dimension!"); } else { boolean first = true; for (Node[] nodes : dimensions) { if (first) { first = false; continue; } String dimensionuri = nodes[0].toString(); location = askForLocation(dimensionuri); // Since ds and dsd location can be the // same, // check // whether loaded already if (!isStored(location)) { loadInStore(location); // For quicker check, also add dsduri loadedMap.put(dimensionuri.hashCode(), true); } } } // If loading ds, also load codelists query = "PREFIX qb: <http://purl.org/linked-data/cube#> SELECT ?codelist WHERE {<" + uri + "> qb:structure ?dsd. ?dsd qb:component ?comp. ?comp qb:dimension ?dimension. ?dimension qb:codeList ?codelist}"; List<Node[]> codelists = executeSparqlSelectQuery(query, true); // There should be a dsd // Note in spec: // "Every qb:DataSet has exactly one associated qb:DataStructureDefinition." if (codelists.size() <= 1) { ; } else { boolean first = true; for (Node[] nodes : codelists) { if (first) { first = false; continue; } String codelisturi = nodes[0].toString(); location = askForLocation(codelisturi); // Since ds and dsd location can be the // same, // check // whether loaded already if (!isStored(location)) { loadInStore(location); // For quicker check, also add dsduri loadedMap.put(codelisturi.hashCode(), true); } } } } } catch (RepositoryException e) { throw new OlapException("Problem with repository: " + e.getMessage()); } catch (QueryEvaluationException e) { throw new OlapException("Problem with query evaluation: " + e.getMessage()); } catch (MalformedQueryException e) { throw new OlapException("Problem with malformed query: " + e.getMessage()); } catch (MalformedURLException e) { throw new OlapException("Problem with malformed url: " + e.getMessage()); } } private void checkIntegrityConstraints() throws OlapException { try { // Now, we check the integrity constraints RepositoryConnection con; con = repo.getConnection(); String testquery; BooleanQuery booleanQuery; boolean error = false; String overview = ""; // Each integrity constraint query assumes the following set of // prefix // bindings: String prefixbindings = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX skos: <http://www.w3.org/2004/02/skos/core#> PREFIX qb: <http://purl.org/linked-data/cube#> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> PREFIX owl: <http://www.w3.org/2002/07/owl#> "; // IC-1. Unique DataSet. Every qb:Observation // has exactly one associated qb:DataSet. <= // takes too long since every observation tested // testquery = prefixbindings // + // "ASK { { ?obs a qb:Observation . FILTER NOT EXISTS { ?obs qb:dataSet ?dataset1 . } } UNION { ?obs a qb:Observation ; qb:dataSet ?dataset1, ?dataset2 . FILTER (?dataset1 != ?dataset2) }}"; // BooleanQuery booleanQuery = con.prepareBooleanQuery( // QueryLanguage.SPARQL, testquery); // if (booleanQuery.evaluate() == true) { // error = true; // overview += // "Failed specification check: IC-1. Unique DataSet. Every qb:Observation has exactly one associated qb:DataSet. \n"; // } else { // overview += // "Successful specification check: IC-1. Unique DataSet. Every qb:Observation has exactly one associated qb:DataSet. \n"; // } // IC-2. Unique DSD. Every qb:DataSet has // exactly one associated // qb:DataStructureDefinition. <= tested before testquery = prefixbindings + "ASK { { ?dataset a qb:DataSet . FILTER NOT EXISTS { ?dataset qb:structure ?dsd . } } UNION { ?dataset a qb:DataSet ; qb:structure ?dsd1, ?dsd2 . FILTER (?dsd1 != ?dsd2) }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-2. Unique DSD. Every qb:DataSet has exactly one associated qb:DataStructureDefinition. \n"; } else { overview += "Successful specification check: IC-2. Unique DSD. Every qb:DataSet has exactly one associated qb:DataStructureDefinition. \n"; } // IC-3. DSD includes measure // XXX: Not fully like in spec testquery = prefixbindings + "ASK { ?dsd a qb:DataStructureDefinition . FILTER NOT EXISTS { ?dsd qb:component [qb:componentProperty [a qb:MeasureProperty]] }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-3. DSD includes measure. Every qb:DataStructureDefinition must include at least one declared measure. \n"; } else { overview += "Successful specification check: IC-3. DSD includes measure. Every qb:DataStructureDefinition must include at least one declared measure. \n"; } // IC-4. Dimensions have range testquery = prefixbindings + "ASK { ?dim a qb:DimensionProperty . FILTER NOT EXISTS { ?dim rdfs:range [] }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-4. Dimensions have range. Every dimension declared in a qb:DataStructureDefinition must have a declared rdfs:range.\n"; } else { overview += "Successful specification check: IC-4. Dimensions have range. Every dimension declared in a qb:DataStructureDefinition must have a declared rdfs:range. \n"; } // IC-5. Concept dimensions have code lists testquery = prefixbindings + "ASK { ?dim a qb:DimensionProperty ; rdfs:range skos:Concept . FILTER NOT EXISTS { ?dim qb:codeList [] }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-5. Concept dimensions have code lists. Every dimension with range skos:Concept must have a qb:codeList. \n"; } else { overview += "Successful specification check: IC-5. Concept dimensions have code lists. Every dimension with range skos:Concept must have a qb:codeList. \n"; } // IC-6. Only attributes may be optional <= not // important right now. We do not regard // attributes. testquery = prefixbindings + "ASK { ?dsd qb:component ?componentSpec . ?componentSpec qb:componentRequired \"false\"^^xsd:boolean ; qb:componentProperty ?component . FILTER NOT EXISTS { ?component a qb:AttributeProperty }} "; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-6. Only attributes may be optional. The only components of a qb:DataStructureDefinition that may be marked as optional, using qb:componentRequired are attributes.\n"; } else { overview += "Successful specification check: IC-6. Only attributes may be optional. The only components of a qb:DataStructureDefinition that may be marked as optional, using qb:componentRequired are attributes. \n"; } // IC-7. Slice Keys must be declared <= not // important right now. We do not regard slices. testquery = prefixbindings + "ASK { ?sliceKey a qb:SliceKey . FILTER NOT EXISTS { [a qb:DataStructureDefinition] qb:sliceKey ?sliceKey }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-7. Slice Keys must be declared. Every qb:SliceKey must be associated with a qb:DataStructureDefinition. \n"; } else { overview += "Successful specification check: IC-7. Slice Keys must be declared. Every qb:SliceKey must be associated with a qb:DataStructureDefinition. \n"; } // IC-8. Slice Keys consistent with DSD testquery = prefixbindings + "ASK { ?slicekey a qb:SliceKey; qb:componentProperty ?prop . ?dsd qb:sliceKey ?sliceKey . FILTER NOT EXISTS { ?dsd qb:component [qb:componentProperty ?prop] }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-8. Slice Keys consistent with DSD. Every qb:componentProperty on a qb:SliceKey must also be declared as a qb:component of the associated qb:DataStructureDefinition. \n"; } else { overview += "Successful specification check: IC-8. Slice Keys consistent with DSD. Every qb:componentProperty on a qb:SliceKey must also be declared as a qb:component of the associated qb:DataStructureDefinition. \n"; } // IC-9. Unique slice structure testquery = prefixbindings + "ASK { { ?slice a qb:Slice . FILTER NOT EXISTS { ?slice qb:sliceStructure ?key } } UNION { ?slice a qb:Slice ; qb:sliceStructure ?key1, ?key2; FILTER (?key1 != ?key2) }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-9. Unique slice structure. Each qb:Slice must have exactly one associated qb:sliceStructure. \n"; } else { overview += "Successful specification check: IC-1. IC-9. Unique slice structure. Each qb:Slice must have exactly one associated qb:sliceStructure. \n"; } // IC-10. Slice dimensions complete testquery = prefixbindings + "ASK { ?slice qb:sliceStructure [qb:componentProperty ?dim] . FILTER NOT EXISTS { ?slice ?dim [] }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-10. Slice dimensions complete. Every qb:Slice must have a value for every dimension declared in its qb:sliceStructure. \n"; } else { overview += "Successful specification check: IC-10. Slice dimensions complete. Every qb:Slice must have a value for every dimension declared in its qb:sliceStructure. \n"; } // IC-11. All dimensions required <= takes too // long // testquery = prefixbindings // + // "ASK { ?obs qb:dataSet/qb:structure/qb:component/qb:componentProperty ?dim . ?dim a qb:DimensionProperty; FILTER NOT EXISTS { ?obs ?dim [] }}"; // booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, // testquery); // if (booleanQuery.evaluate() == true) { // error = true; // overview += // "Failed specification check: IC-11. All dimensions required. Every qb:Observation has a value for each dimension declared in its associated qb:DataStructureDefinition. \n"; // } else { // overview += // "Successful specification check: IC-11. All dimensions required. Every qb:Observation has a value for each dimension declared in its associated qb:DataStructureDefinition. \n"; // } // IC-12. No duplicate observations <= takes too // long // testquery = prefixbindings // + // "ASK { FILTER( ?allEqual ) { SELECT (MIN(?equal) AS ?allEqual) WHERE { ?obs1 qb:dataSet ?dataset . ?obs2 qb:dataSet ?dataset . FILTER (?obs1 != ?obs2) ?dataset qb:structure/qb:component/qb:componentProperty ?dim . ?dim a qb:DimensionProperty . ?obs1 ?dim ?value1 . ?obs2 ?dim ?value2 . BIND( ?value1 = ?value2 AS ?equal) } GROUP BY ?obs1 ?obs2 }}"; // booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, // testquery); // if (booleanQuery.evaluate() == true) { // error = true; // overview += // "Failed specification check: IC-12. No duplicate observations. No two qb:Observations in the same qb:DataSet may have the same value for all dimensions. \n"; // } else { // overview += // "Successful specification check: IC-12. No duplicate observations. No two qb:Observations in the same qb:DataSet may have the same value for all dimensions. \n"; // } // IC-13. Required attributes <= We do not // regard attributes testquery = prefixbindings + "ASK { ?obs qb:dataSet/qb:structure/qb:component ?component . ?component qb:componentRequired \"true\"^^xsd:boolean ; qb:componentProperty ?attr . FILTER NOT EXISTS { ?obs ?attr [] }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-13. Required attributes. Every qb:Observation has a value for each declared attribute that is marked as required. \n"; } else { overview += "Successful specification check: IC-13. Required attributes. Every qb:Observation has a value for each declared attribute that is marked as required. \n"; } // IC-14. All measures present testquery = prefixbindings + "ASK { ?obs qb:dataSet/qb:structure ?dsd . FILTER NOT EXISTS { ?dsd qb:component/qb:componentProperty qb:measureType } ?dsd qb:component/qb:componentProperty ?measure . ?measure a qb:MeasureProperty; FILTER NOT EXISTS { ?obs ?measure [] }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-14. All measures present. In a qb:DataSet which does not use a Measure dimension then each individual qb:Observation must have a value for every declared measure. \n"; } else { overview += "Successful specification check: IC-14. All measures present. In a qb:DataSet which does not use a Measure dimension then each individual qb:Observation must have a value for every declared measure. \n"; } // IC-15. Measure dimension consistent <= We do // not support measureType, yet. testquery = prefixbindings + "ASK { ?obs qb:dataSet/qb:structure ?dsd ; qb:measureType ?measure . ?dsd qb:component/qb:componentProperty qb:measureType . FILTER NOT EXISTS { ?obs ?measure [] }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-15. Measure dimension consistent. In a qb:DataSet which uses a Measure dimension then each qb:Observation must have a value for the measure corresponding to its given qb:measureType. \n"; } else { overview += "Successful specification check: IC-15. Measure dimension consistent. In a qb:DataSet which uses a Measure dimension then each qb:Observation must have a value for the measure corresponding to its given qb:measureType. \n"; } // IC-16. Single measure on measure dimension // observation testquery = prefixbindings + "ASK { ?obs qb:dataSet/qb:structure ?dsd ; qb:measureType ?measure ; ?omeasure [] . ?dsd qb:component/qb:componentProperty qb:measureType ; qb:component/qb:componentProperty ?omeasure . ?omeasure a qb:MeasureProperty . FILTER (?omeasure != ?measure)}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-16. Single measure on measure dimension observation. In a qb:DataSet which uses a Measure dimension then each qb:Observation must only have a value for one measure (by IC-15 this will be the measure corresponding to its qb:measureType). \n"; } else { overview += "Successful specification check: IC-16. Single measure on measure dimension observation. In a qb:DataSet which uses a Measure dimension then each qb:Observation must only have a value for one measure (by IC-15 this will be the measure corresponding to its qb:measureType). \n"; } // IC-17. All measures present in measures dimension cube testquery = prefixbindings + "ASK { { SELECT ?numMeasures (COUNT(?obs2) AS ?count) WHERE { { SELECT ?dsd (COUNT(?m) AS ?numMeasures) WHERE { ?dsd qb:component/qb:componentProperty ?m. ?m a qb:MeasureProperty . } GROUP BY ?dsd } ?obs1 qb:dataSet/qb:structure ?dsd; qb:dataSet ?dataset ; qb:measureType ?m1 . ?obs2 qb:dataSet ?dataset ; qb:measureType ?m2 . FILTER NOT EXISTS { ?dsd qb:component/qb:componentProperty ?dim . FILTER (?dim != qb:measureType) ?dim a qb:DimensionProperty . ?obs1 ?dim ?v1 . ?obs2 ?dim ?v2. FILTER (?v1 != ?v2) } } GROUP BY ?obs1 ?numMeasures HAVING (?count != ?numMeasures) }}"; booleanQuery = con.prepareBooleanQuery(QueryLanguage.SPARQL, testquery); if (booleanQuery.evaluate() == true) { error = true; overview += "Failed specification check: IC-17. All measures present in measures dimension cube. In a qb:DataSet which uses a Measure dimension then if there is a Observation for some combination of non-measure dimensions then there must be other Observations with the same non-measure dimension values for each of the declared measures. \n"; } else { overview += "Successful specification check: IC-17. All measures present in measures dimension cube. In a qb:DataSet which uses a Measure dimension then if there is a Observation for some combination of non-measure dimensions then there must be other Observations with the same non-measure dimension values for each of the declared measures. \n"; } // Important! con.close(); // Logging Olap4ldUtil._log.config("Integrity constraints overview: " + overview); if (error) { throw new UnsupportedOperationException( "Integrity constraints overview: \n" + overview); } } catch (RepositoryException e) { throw new OlapException("Problem with repository: " + e.getMessage()); } catch (MalformedQueryException e) { throw new OlapException("Problem with malformed query: " + e.getMessage()); } catch (QueryEvaluationException e) { throw new OlapException("Problem with query evaluation: " + e.getMessage()); } } private void runNormalizationAlgorithm() throws OlapException { try { RepositoryConnection con; con = repo.getConnection(); // First, we run normalization algorithm String updateQuery = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX qb: <http://purl.org/linked-data/cube#> INSERT { ?o rdf:type qb:Observation .} WHERE { [] qb:observation ?o .}; INSERT { ?o rdf:type qb:Observation .} WHERE { ?o qb:dataSet [] .}; INSERT { ?s rdf:type qb:Slice . } WHERE { [] qb:slice ?s.}; INSERT { ?cs qb:componentProperty ?p . ?p rdf:type qb:DimensionProperty .} WHERE { ?cs qb:dimension ?p .}; INSERT { ?cs qb:componentProperty ?p . ?p rdf:type qb:MeasureProperty .} WHERE { ?cs qb:measure ?p .};INSERT { ?cs qb:componentProperty ?p . ?p rdf:type qb:AttributeProperty .} WHERE { ?cs qb:attribute ?p .}"; Update updateQueryQuery = con.prepareUpdate(QueryLanguage.SPARQL, updateQuery); updateQueryQuery.execute(); // # Dataset attachments updateQuery = "PREFIX qb: <http://purl.org/linked-data/cube#> INSERT { ?obs ?comp ?value} WHERE { ?spec qb:componentProperty ?comp ; qb:componentAttachment qb:DataSet . ?dataset qb:structure [qb:component ?spec]; ?comp ?value . ?obs qb:dataSet ?dataset.};"; con.prepareUpdate(QueryLanguage.SPARQL, updateQuery); updateQueryQuery = con.prepareUpdate(QueryLanguage.SPARQL, updateQuery); updateQueryQuery.execute(); // # Slice attachments updateQuery = "PREFIX qb: <http://purl.org/linked-data/cube#> INSERT { ?obs ?comp ?value} WHERE { ?spec qb:componentProperty ?comp; qb:componentAttachment qb:Slice . ?dataset qb:structure [qb:component ?spec]; qb:slice ?slice . ?slice ?comp ?value; qb:observation ?obs .};"; con.prepareUpdate(QueryLanguage.SPARQL, updateQuery); updateQueryQuery = con.prepareUpdate(QueryLanguage.SPARQL, updateQuery); updateQueryQuery.execute(); // # Dimension values on slices updateQuery = "PREFIX qb: <http://purl.org/linked-data/cube#> INSERT { ?obs ?comp ?value} WHERE { ?spec qb:componentProperty ?comp . ?comp a qb:DimensionProperty . ?dataset qb:structure [qb:component ?spec]; qb:slice ?slice . ?slice ?comp ?value; qb:observation ?obs .}"; con.prepareUpdate(QueryLanguage.SPARQL, updateQuery); updateQueryQuery = con.prepareUpdate(QueryLanguage.SPARQL, updateQuery); updateQueryQuery.execute(); // Important! con.close(); } catch (RepositoryException e) { throw new OlapException("Problem with repository: " + e.getMessage()); } catch (MalformedQueryException e) { throw new OlapException("Problem with malformed query: " + e.getMessage()); } catch (UpdateExecutionException e) { throw new OlapException("Problem with update execution: " + e.getMessage()); } } /** * 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) throws OlapException { checkSufficientInformationGathered(restrictions); String additionalFilters = createFilterForRestrictions(restrictions); List<Node[]> result = new ArrayList<Node[]>(); // Create header Node[] header = new Node[] { new Variable("?CATALOG_NAME"), new Variable("?SCHEMA_NAME"), new Variable("?CUBE_NAME"), new Variable("?DIMENSION_NAME"), new Variable("?DIMENSION_UNIQUE_NAME"), new Variable("?DIMENSION_CAPTION"), new Variable("?DIMENSION_ORDINAL"), new Variable("?DIMENSION_TYPE"), new Variable("?DESCRIPTION") }; result.add(header); if (!isMeasureQueriedForExplicitly(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { // Get all dimensions String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getDimensions_regular.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> myresult = executeSparqlSelectQuery(querytemplate, true); // Add all of result2 to result boolean first = true; for (Node[] nodes : myresult) { if (first) { first = false; continue; } result.add(nodes); } } // We try to find measures if (true) { // In this case, we do ask for a measure dimension. String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getDimensions_measure_dimension.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> myresult = executeSparqlSelectQuery(querytemplate, true); // List<Node[]> result2 = applyRestrictions(memberUris2, // restrictions); // Add all of result2 to result boolean first = true; for (Node[] nodes : myresult) { if (first) { first = false; continue; } result.add(nodes); } } 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) throws OlapException { checkSufficientInformationGathered(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 querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getMeasures.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> result = executeSparqlSelectQuery(querytemplate, true); // List<Node[]> result = applyRestrictions(measureUris, restrictions); return result; } /** * * Return hierarchies * * @param context * @param metadataRequest * @param restrictions * @return */ public List<Node[]> getHierarchies(Restrictions restrictions) throws OlapException { checkSufficientInformationGathered(restrictions); String additionalFilters = createFilterForRestrictions(restrictions); List<Node[]> result = new ArrayList<Node[]>(); // 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("?HIERARCHY_NAME"), new Variable("?HIERARCHY_CAPTION"), new Variable("?DESCRIPTION") }; result.add(header); if (!isMeasureQueriedForExplicitly(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { // Get all hierarchies with codeLists String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getHierarchies_regular.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> myresult = executeSparqlSelectQuery(querytemplate, true); boolean first = true; for (Node[] nodes : myresult) { if (first) { first = false; continue; } result.add(nodes); } } // List<Node[]> result = applyRestrictions(hierarchyResults, // restrictions); // Try to find measure dimensions. if (true) { // In this case, we do ask for a measure hierarchy. String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getHierarchies_measure_dimension.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> myresult = executeSparqlSelectQuery(querytemplate, true); // List<Node[]> result2 = applyRestrictions(memberUris2, // restrictions); // Add all of result2 to result boolean first = true; for (Node[] nodes : myresult) { 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 /* * * Note in spec: * "Every dimension declared in a qb:DataStructureDefinition must have a declared rdfs:range." * Note in spec: * "Every dimension with range skos:Concept must have a qb:codeList." <= * This means, we do not necessarily need a code list in many cases. * But, if we have a code list, then: "If a dimension property has a * qb:codeList, then the value of the dimension property on every * qb:Observation must be in the code list." */ if (!isMeasureQueriedForExplicitly(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getHierarchies_without_codelist.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> myresult = executeSparqlSelectQuery(querytemplate, true); // List<Node[]> result3 = applyRestrictions(memberUris3, // restrictions); // Add all of result2 to result boolean first = true; for (Node[] nodes : myresult) { if (first) { first = false; continue; } result.add(nodes); } } return result; } /** * * @param context * @param metadataRequest * @param restrictions * @return */ public List<Node[]> getLevels(Restrictions restrictions) throws OlapException { checkSufficientInformationGathered(restrictions); String additionalFilters = createFilterForRestrictions(restrictions); List<Node[]> result = new ArrayList<Node[]>(); // 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_CAPTION"), new Variable("?LEVEL_NAME"), new Variable("?DESCRIPTION"), new Variable("?LEVEL_NUMBER"), new Variable("?LEVEL_CARDINALITY"), new Variable("?LEVEL_TYPE") }; result.add(header); if (!isMeasureQueriedForExplicitly(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { // TODO: Add regularly modeled levels (without using xkos) String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getLevels_regular.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> myresult = executeSparqlSelectQuery(querytemplate, true); // Add all of result2 to result boolean first = true; for (Node[] nodes : myresult) { if (first) { first = false; continue; } result.add(nodes); } // Get all levels of code lists using xkos // TODO: LEVEL_CARDINALITY is not solved, yet. querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getLevels_xkos.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); myresult = executeSparqlSelectQuery(querytemplate, true); // Add all of result2 to result first = true; for (Node[] nodes : myresult) { 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 (true) { // In this case, we do ask for a measure dimension. String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getLevels_measure_dimension.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> myresult = executeSparqlSelectQuery(querytemplate, true); // List<Node[]> result2 = applyRestrictions(memberUris2, // restrictions); // Add all of result2 to result boolean first = true; for (Node[] nodes : myresult) { if (first) { first = false; continue; } result.add(nodes); } } // Add levels for dimensions without codelist, but only if hierarchy and // dimension names are equal if (!isMeasureQueriedForExplicitly(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getLevels_without_codelist.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); // Second, ask for the measures (which are also members) List<Node[]> myresult = executeSparqlSelectQuery(querytemplate, true); // List<Node[]> result3 = applyRestrictions(memberUris3, // restrictions); // Add all of result3 to result boolean first = true; for (Node[] nodes : myresult) { 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) throws OlapException { checkSufficientInformationGathered(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 (true) { intermediaryresult = getMeasureMembers(restrictions); addToResult(intermediaryresult, result); } // Regular members if (!isMeasureQueriedForExplicitly(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { intermediaryresult = getHasTopConceptMembers(restrictions); addToResult(intermediaryresult, result); } // Xkos members // Watch out: No square brackets if (!isMeasureQueriedForExplicitly(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { intermediaryresult = getXkosMembers(restrictions); addToResult(intermediaryresult, result); } // If we still do not have members, then we might have degenerated // members if (!isMeasureQueriedForExplicitly(restrictions.dimensionUniqueName, restrictions.hierarchyUniqueName, restrictions.levelUniqueName)) { // Members without codeList intermediaryresult = getDegeneratedMembers(restrictions); addToResult(intermediaryresult, result); } return result; } private List<Node[]> getMeasureMembers(Restrictions restrictions) { String additionalFilters = createFilterForRestrictions(restrictions); /* * 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"); } // Second, ask for the measures (which are also members) String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getMembers_measure_members.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> memberUris2 = executeSparqlSelectQuery(querytemplate, true); return memberUris2; } /** * Finds specific typical members. * * @param restrictions * * @return */ private List<Node[]> getXkosMembers(Restrictions restrictions) { String additionalFilters = createFilterForRestrictions(restrictions); /* * 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 + ">) "; } 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"); } String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getMembers_xkos.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> memberUris2 = executeSparqlSelectQuery(querytemplate, true); return memberUris2; } /** * Returns all hasTopConcept members of the cube. * * @param dimensionUniqueName * @param cubeNamePattern * * @param cubeNamePattern * @return */ private List<Node[]> getHasTopConceptMembers(Restrictions restrictions) { String additionalFilters = createFilterForRestrictions(restrictions); /* * 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"); // First, ask for all members // Get all members of hierarchies without levels, that simply // define // skos:hasTopConcept members with skos:notation. String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getMembers_topConcept.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> memberUris = executeSparqlSelectQuery(querytemplate, 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) { String additionalFilters = createFilterForRestrictions(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"); } String querytemplate = Olap4ldLinkedDataUtil .readInQueryTemplate("sesame_getMembers_degenerated.txt"); querytemplate = querytemplate.replace("{{{STANDARDFROM}}}", askForFrom(true)); querytemplate = querytemplate.replace("{{{TABLE_CAT}}}", TABLE_CAT); querytemplate = querytemplate.replace("{{{TABLE_SCHEM}}}", TABLE_SCHEM); querytemplate = querytemplate.replace("{{{FILTERS}}}", additionalFilters); List<Node[]> memberUris1 = executeSparqlSelectQuery(querytemplate, 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 && !restrictions.dimensionUniqueName .equals(Olap4ldLinkedDataUtil.MEASURE_DIMENSION_NAME)) ? " FILTER (?DIMENSION_UNIQUE_NAME = <" + restrictions.dimensionUniqueName + ">) " : ""; String hierarchyUniqueNameFilter = (restrictions.hierarchyUniqueName != null && !restrictions.hierarchyUniqueName .equals(Olap4ldLinkedDataUtil.MEASURE_DIMENSION_NAME)) ? " FILTER (?HIERARCHY_UNIQUE_NAME = <" + restrictions.hierarchyUniqueName + ">) " : ""; String levelUniqueNameFilter = (restrictions.levelUniqueName != null && !restrictions.levelUniqueName .equals(Olap4ldLinkedDataUtil.MEASURE_DIMENSION_NAME)) ? " FILTER (?LEVEL_UNIQUE_NAME = <" + restrictions.levelUniqueName + ">) " : ""; String memberUniqueNameFilter; if (restrictions.memberUniqueName != null) { Node resource = restrictions.memberUniqueName; if (isResourceAndNotLiteral(resource)) { memberUniqueNameFilter = " FILTER (?MEMBER_UNIQUE_NAME = <" + resource + ">) "; } else { memberUniqueNameFilter = " FILTER (str(?MEMBER_UNIQUE_NAME) = \"" + resource + "\") "; } } else { memberUniqueNameFilter = ""; } 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; } @Override public List<Node[]> executeOlapQuery(LogicalOlapQueryPlan queryplan) throws OlapException { // Log logical query plan Olap4ldUtil._log.config("Logical query plan to string: " + queryplan.toString()); LogicalToPhysical logicaltophysical = new LogicalToPhysical(null); PhysicalOlapIterator newRoot; newRoot = logicaltophysical.compile(queryplan._root); Olap4ldUtil._log.config("bytes iterator " + 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 List<Node[]> executeOlapQuery(Cube cube, List<Level> slicesrollups, List<Position> dices, List<Measure> projections) throws OlapException { // TODO Auto-generated method stub return null; } /** * Empties store and locationMap. */ public void rollback() { // Store try { // Rely on garbage collector // this.repo.initialize(); // this.repo.shutDown(); this.repo = new SailRepository(new MemoryStore()); repo.initialize(); // do something interesting with the values here... // con.close(); } catch (RepositoryException e) { // TODO Auto-generated catch block e.printStackTrace(); } loadedMap.clear(); } @Override public PhysicalOlapQueryPlan getExecplan() throws OlapException { // TODO Auto-generated method stub return null; } }