package org.olap4j.driver.olap4ld.linkeddata; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.olap4j.OlapException; import org.olap4j.driver.olap4ld.helper.Olap4ldLinkedDataUtil; import org.olap4j.metadata.Member; import org.semanticweb.yars.nx.Node; import org.semanticweb.yars.nx.Resource; import org.semanticweb.yars.nx.Variable; /** * Aim should be to have this for all SPARQL engines (with possibly smaller modifications via if-else for dialects. * @author kaempgen * */ public class Olap2SparqlAlgorithmIterator implements PhysicalOlapIterator { private List<Node[]> cubes = new ArrayList<Node[]>(); private List<Node[]> measures = new ArrayList<Node[]>(); private List<Node[]> dimensions = new ArrayList<Node[]>(); private List<Node[]> newdimensions; private List<Node[]> hierarchies; private List<Node[]> levels = new ArrayList<Node[]>(); private List<Node[]> members; // We collect necessary parts of the SPARQL query. String selectClause = " "; String whereClause = " "; String groupByClause = " "; String orderByClause = " "; private LinkedDataCubesEngine engine; private String query; private List<Node[]> result; private Iterator<Node[]> outputiterator; private HashMap<Integer, Integer> levelHeightMap; private ArrayList<Node[]> newmeasures; private PhysicalOlapIterator inputiterator; public Olap2SparqlAlgorithmIterator( PhysicalOlapIterator inputiterator, LinkedDataCubesEngine engine, List<Node[]> slicesrollups, List<Integer> levelheights, List<Node[]> projections, List<List<Node[]>> membercombinations, List<Node[]> hierarchysignature) { this.inputiterator = inputiterator; this.engine = engine; Restrictions restrictions = new Restrictions(); try { this.cubes = inputiterator.getCubes(restrictions); this.measures = inputiterator.getMeasures(restrictions); this.dimensions = inputiterator.getDimensions(restrictions); this.hierarchies = inputiterator.getHierarchies(restrictions); this.levels = inputiterator.getLevels(restrictions); // One problem could be that this may be a huge number of members. this.members = inputiterator.getCubes(restrictions); } catch (OlapException e) { // TODO Auto-generated catch block e.printStackTrace(); } // Maybe first check that every metadata element at least has one // header? // Prepare inputs // Initialise triple store with dataset URI // Evaluate cube evaluateCube(); // Evaluate SlicesRollups // HashMaps for level height of dimension this.levelHeightMap = new HashMap<Integer, Integer>(); /* * At Roll-up, all dimensions are mentioned which are not sliced. At * Slice, all sliced dimensions are mentioned. Thus, we assume * SlicesRollups to contain all dimensions that are not sliced (even if * no actual roll-up is done). */ evaluateSlicesRollups(slicesrollups, levelheights); // Evaluate Dices evaluateDices(membercombinations, hierarchysignature); // Evaluate Projections evaluateProjections(projections); // Query triple store with SPARQL query // Now that we consider DSD in queries, we need to consider DSD // locations if (groupByClause.equals(" ")) { groupByClause = ""; } else { groupByClause = " group by " + groupByClause; } if (orderByClause.equals(" ")) { orderByClause = ""; } else { orderByClause = " order by " + orderByClause; } this.query = Olap4ldLinkedDataUtil.getStandardPrefixes() + "select " + selectClause + askForFrom(false) + askForFrom(true) + "where { " + whereClause + "}" + groupByClause + orderByClause; // At initialisation, we execute the sparql query. // XXX: Should we not do this only if next() or hasNext()? } /** * Should correspond to OLAP-2-SPARQL algorithm. * * 1) Add named measures. * * We use aggregation functions as defined by QB4OLAP. * * Different from original OLAP-2-SPARQL algorithm, we also create implicit * measures. * */ private void evaluateProjections(List<Node[]> projections) { // Now, for each measure, we create a measure value column. // Any measure should be contained only once, unless calculated // measures HashMap<Integer, Boolean> projectedMeasureMap = new HashMap<Integer, Boolean>(); boolean first = true; Map<String, Integer> cubemap = Olap4ldLinkedDataUtil .getNodeResultFields(cubes.get(0)); Map<String, Integer> measuremap = Olap4ldLinkedDataUtil .getNodeResultFields(measures.get(0)); // At the same time, we can update the metadata. // Since we do a projection here, we should change the metadata. Only // keep those // measures that 1) are contained in projections and in measures. this.newmeasures = new ArrayList<Node[]>(); for (Node[] measure : projections) { if (first) { newmeasures.add(projections.get(0)); first = false; continue; } // XXX Should actually not make a difference anymore, since we do // not add them to projections in the first place. // Make sure that the projected measure actually is contained in // measures and add if so boolean contained = false; for (Node[] aMeasure : measures) { // add to newmeasures if 1) are contained in measures if (aMeasure[measuremap.get("?MEASURE_UNIQUE_NAME")].toString() .equals(measure[measuremap.get("?MEASURE_UNIQUE_NAME")] .toString())) { contained = true; } } if (!contained) { continue; } newmeasures.add(measure); // For formulas, this is different // XXX: Needs to be put before creating the Logical Olap Operator // Tree // Will probably not work since MEASURE_AGGREGATOR if (measure[measuremap.get("?MEASURE_AGGREGATOR")].toString() .equals("http://purl.org/olap#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 at the end, // e.g., "AVG". // String measureProperty1 = Olap4ldLinkedDataUtil // .convertMDXtoURI(measure1.getUniqueName()).replace( // "AGGFUNC" + measure.getAggregator().name(), ""); // We do not encode the aggregation function in the measure, // any // more. // String measurePropertyVariable1 = makeUriToParameter(measure1 // .getUniqueName()); // // String measureProperty2 = Olap4ldLinkedDataUtil // .convertMDXtoURI(measure2.getUniqueName()).replace( // "AGGFUNC" + measure.getAggregator().name(), ""); // String measurePropertyVariable2 = makeUriToParameter(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 { // As always, remove Aggregation Function from Measure Name // And now, see below String measureProperty = measure[measuremap .get("?MEASURE_UNIQUE_NAME")] .toString() .replace( "AGGFUNC" + measure[measuremap.get("?MEASURE_AGGREGATOR")] .toString() .replace( "http://purl.org/olap#", "").toUpperCase(), ""); // And now, also (possibly) remove Dataset Name from Measure // Name measureProperty = measureProperty.replace( cubes.get(1)[cubemap.get("?CUBE_NAME")].toString(), ""); // We also remove aggregation function from Measure Property // Variable so // that the same property is not selected twice. String measurePropertyVariableString = measure[measuremap .get("?MEASURE_UNIQUE_NAME")] .toString() .replace( "AGGFUNC" + measure[measuremap.get("?MEASURE_AGGREGATOR")] .toString() .replace( "http://purl.org/olap#", "").toUpperCase(), ""); // Also, we remove dataset name measurePropertyVariableString = measurePropertyVariableString .replace(cubes.get(1)[cubemap.get("?CUBE_NAME")] .toString(), ""); Node measurePropertyVariable = Olap4ldLinkedDataUtil .makeUriToVariable(new Resource( measurePropertyVariableString)); // Unique name for variable Node uniqueMeasurePropertyVariable = Olap4ldLinkedDataUtil .makeUriToVariable(measure[measuremap .get("?MEASURE_UNIQUE_NAME")]); String aggregationfunction; if (measure[measuremap.get("?MEASURE_AGGREGATOR")].toString() .equals("null") || measure[measuremap.get("?MEASURE_AGGREGATOR")].toString() .equals("")) { // In this case, we can only use top aggregationfunction = "GROUP_CONCAT"; } else { aggregationfunction = measure[measuremap .get("?MEASURE_AGGREGATOR")].toString().replace( "http://purl.org/olap#", ""); } // 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 += " (" + aggregationfunction + "(?" + measurePropertyVariable + ") as ?" + uniqueMeasurePropertyVariable + "_new )"; // According to spec, every measure needs to be set for every // observation only once if (!projectedMeasureMap .containsKey(measureProperty.hashCode())) { whereClause += "?obs <" + measureProperty + "> ?" + measurePropertyVariable + "."; projectedMeasureMap.put(measureProperty.hashCode(), true); } } } } /** * Should correspond to OLAP-to-SPARQL algorithm in SSB paper. There: * * 1) We take the first position as a template of what hierarchies / * dimensions are filtered for. (XXX: Properly align that with description * of CellSet in olap4ld paper) * * 2) Check whether new graph patterns needed * * 3) Add filters * * @param membercombinations * */ private void evaluateDices(List<List<Node[]>> membercombinations, List<Node[]> hierarchysignature) { // For each filter tuple if (membercombinations != null && !membercombinations.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 Map<String, Integer> map = Olap4ldLinkedDataUtil .getNodeResultFields(membercombinations.get(0).get(0)); Map<String, Integer> signaturemap = Olap4ldLinkedDataUtil .getNodeResultFields(hierarchysignature.get(0)); // Check whether new graph patterns needed // First is header for (int i = 1; i < membercombinations.get(0).size(); i++) { int levelnumber = new Integer( membercombinations.get(0).get(i)[map .get("?LEVEL_NUMBER")].toString()); int levelmaxnumber = new Integer( hierarchysignature.get(i)[signaturemap .get("?HIERARCHY_MAX_LEVEL_NUMBER")].toString()); // We should be testing whether diceslevelHeight higher than // slicesRollupsLevelHeight // Since level_number counts from the root (ALL level) to // compute Dices Level Height, we need the levelmaxnumber of // the hierarchy. Thus, we also have the hierarchysignature. Integer diceslevelHeight = levelmaxnumber - levelnumber; Node dimensionProperty = membercombinations.get(0).get(i)[map .get("?DIMENSION_UNIQUE_NAME")]; Integer slicesRollupsLevelHeight = levelHeightMap .get(dimensionProperty.hashCode()); // Check on possibly existing graph patterns if (slicesRollupsLevelHeight == null) { String levelURI = membercombinations.get(0).get(i)[map .get("?LEVEL_UNIQUE_NAME")].toString(); // Also considers equivalence classes whereClause += addLevelPropertyPath(0, diceslevelHeight, dimensionProperty, levelURI); } else if (new Integer( membercombinations.get(0).get(i)[map .get("?MEMBER_TYPE")].toString()) != Member.Type.MEASURE .ordinal() && diceslevelHeight > slicesRollupsLevelHeight) { String levelURI = membercombinations.get(0).get(i)[map .get("?LEVEL_UNIQUE_NAME")].toString(); // Also considers equivalence classes whereClause += addLevelPropertyPath( slicesRollupsLevelHeight, diceslevelHeight, dimensionProperty, levelURI); } } // Add filter whereClause += " FILTER ("; List<String> orList = new ArrayList<String>(); for (List<Node[]> membercombination : membercombinations) { // Each position is an OR List<String> andList = new ArrayList<String>(); Map<String, Integer> map1 = Olap4ldLinkedDataUtil .getNodeResultFields(membercombination.get(0)); Map<String, Integer> signaturemap1 = Olap4ldLinkedDataUtil .getNodeResultFields(hierarchysignature.get(0)); // First is header for (int i = 1; i < membercombination.size(); i++) { // We need to know the variable to filter // First, we need to convert it to URI representation. Node dimensionPropertyVariable = Olap4ldLinkedDataUtil .makeUriToVariable(membercombination.get(i)[map1 .get("?DIMENSION_UNIQUE_NAME")]); // We need to know the member to filter Node memberResource = membercombination.get(i)[map1 .get("?MEMBER_UNIQUE_NAME")]; int levelnumber = new Integer( membercombination.get(i)[map1.get("?LEVEL_NUMBER")] .toString()); int levelmaxnumber = new Integer( hierarchysignature.get(i)[signaturemap1 .get("?HIERARCHY_MAX_LEVEL_NUMBER")] .toString()); // Need to know the level of the member Integer diceslevelHeight = levelmaxnumber - levelnumber; // How about: Here adding a variable for the // memberResource and later, for // each memberResource adding a new filter considering // equivalences. // andList.add(" ?" + dimensionPropertyVariable // + diceslevelHeight + " = " + "?" // + memberResourceVariable + " "); // andList.add(this.engine.createConditionConsiderEquivalences( // memberResource, new Variable( // dimensionPropertyVariable.toString() // + diceslevelHeight.toString()))); andList.add("?" + dimensionPropertyVariable.toString() + diceslevelHeight.toString() + " = " + "<" + memberResource.toString() + ">"); } // For sesame, instead of AND is && orList.add(Olap4ldLinkedDataUtil.implodeArray( andList.toArray(new String[0]), " && ")); } // For sesame, instead of OR is || whereClause += Olap4ldLinkedDataUtil.implodeArray( orList.toArray(new String[0]), " || "); 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 + ")."; // } // } // } // } } private void evaluateSlicesRollups(List<Node[]> slicesrollups, List<Integer> levelheights) { // First is header for (int i = 1; i < slicesrollups.size(); i++) { Map<String, Integer> map = Olap4ldLinkedDataUtil .getNodeResultFields(slicesrollups.get(0)); // 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 = slicesrollups.get(i)[map .get("?DIMENSION_UNIQUE_NAME")]; // XXX Why exactly do I include the level? Probably, because I need // the level depth from the hierarchy. String levelURI = slicesrollups.get(i)[map .get("?LEVEL_UNIQUE_NAME")].toString(); /* * 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. If max depth == level depth => levelHeight == 0 If max * depth > level depth => levelHeight == max depth - level depth * * Relationship between level_number, hierarchy_max_level_number and * levelHeight: * * See: http://www.linked-data-cubes.org/index.php/RollupOp# * Relationship_between_level_number * .2C_hierarchy_max_level_number_and_levelHeight * * TODO: THis is quite a simple approach, does not consider possible * access restrictions. */ // Header int levelHeight = levelheights.get(i); // Note as inserted. levelHeightMap.put(dimensionProperty.hashCode(), levelHeight); // Since level_number = 0 means a slice, we start with whereClause += addLevelPropertyPath(0, levelHeight, dimensionProperty, levelURI); Node dimensionPropertyVariable = Olap4ldLinkedDataUtil .makeUriToVariable(dimensionProperty); selectClause += " ?" + dimensionPropertyVariable + levelHeight + " "; groupByClause += " ?" + dimensionPropertyVariable + levelHeight + " "; orderByClause += " ?" + dimensionPropertyVariable + levelHeight + " "; } // Since we do a slice here, we should change the metadata. Map<String, Integer> dimensionmap = Olap4ldLinkedDataUtil .getNodeResultFields(dimensions.get(0)); Map<String, Integer> levelmap = Olap4ldLinkedDataUtil .getNodeResultFields(levels.get(0)); newdimensions = new ArrayList<Node[]>(); boolean first = true; for (Node[] dimension : this.dimensions) { // Only if a level is contained in slicesrollups add // Is header added automatically? if (first) { newdimensions.add(dimensions.get(0)); first = false; continue; } // Measure dimension should also be added. if (dimension[dimensionmap.get("?DIMENSION_UNIQUE_NAME")] .toString().equals( Olap4ldLinkedDataUtil.MEASURE_DIMENSION_NAME)) { newdimensions.add(dimension); } for (Node[] slicesrollup : slicesrollups) { if (dimension[dimensionmap.get("?DIMENSION_UNIQUE_NAME")] .toString().equals( slicesrollup[levelmap .get("?DIMENSION_UNIQUE_NAME")] .toString())) { newdimensions.add(dimension); } } } } private void evaluateCube() { Map<String, Integer> map = Olap4ldLinkedDataUtil .getNodeResultFields(this.cubes.get(0)); String ds = this.cubes.get(1)[map.get("?CUBE_NAME")].toString(); whereClause += "?obs qb:dataSet <" + ds + ">. "; // TODO One possibility is it to directly create the tree // Add physical operator that retrieves data from URI and stores it // in triple store // ExecIterator bi = null; // _root = bi; } /** * This method returns graph patterns for a dimension assignment as well as * 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, String levelURI) { String whereClause = ""; Node dimensionPropertyVariable = Olap4ldLinkedDataUtil .makeUriToVariable(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. /* * We need to extend this with entity consolidation. For that, we * use an additional variable for the dimensionProperty (canonical * name) */ // The variable we adapt from the dimensionProperty variable Variable dimensionPropertyDimensionVariable = Olap4ldLinkedDataUtil .makeUriToVariable(new Resource(dimensionProperty + "Dimension")); // String condition = engine.createConditionConsiderEquivalences( // dimensionProperty, dimensionPropertyDimensionVariable); String condition = "?" + dimensionPropertyDimensionVariable.toString() + " = <" + dimensionProperty + ">"; whereClause += "?obs ?" + dimensionPropertyDimensionVariable + " ?" + dimensionPropertyVariable + levelHeight + ". "; whereClause += " filter(" + condition + ") "; } 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. /* * XXX Not done for this if branch, yet: We need to extend this with * entity consolidation. For that, we use an additional variable for * the dimensionProperty (canonical name) */ } return whereClause; } public boolean hasNext() { // Init? if (result == null) { try { init(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return outputiterator.hasNext(); } /** * We use the iterator of the sparql query result. * * The sparql query result returns for each non-empty fact a node array * (List<Node[]>) with each */ public Object next() { // Init? if (result == null) { try { init(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return outputiterator.next(); } @Override public void remove() { // TODO Auto-generated method stub } @Override public void init() throws Exception { if (result == null) { // We have to init also the input iterators. this.inputiterator.init(); this.result = engine.executeSparqlSelectQuery(query, false); // Not done, anymore. // After evaluation, we do "entity-consolidation" // this.result = this.engine // .replaceIdentifiersWithCanonical(this.result); } // Does not have input operators, therefore no other init necessary. this.outputiterator = this.result.iterator(); } @Override public void close() throws Exception { ; } @Override public void accept(LogicalOlapOperatorQueryPlanVisitor v) throws QueryException { // Needed in case we have as arguments further exec iterators. ; } /** * Returns String representation of op. */ public String toString() { return "OLAP-2-SPARQL query (SparqlSesame): " + query; } @Override public List<Node[]> getCubes(Restrictions restrictions) throws OlapException { return cubes; } @Override public List<Node[]> getDimensions(Restrictions restrictions) throws OlapException { return newdimensions; } @Override public List<Node[]> getMeasures(Restrictions restrictions) throws OlapException { return newmeasures; } @Override public List<Node[]> getHierarchies(Restrictions restrictions) throws OlapException { return hierarchies; } @Override public List<Node[]> getLevels(Restrictions restrictions) throws OlapException { return levels; } @Override public List<Node[]> getMembers(Restrictions restrictions) throws OlapException { return members; } /** * 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) { return " "; } }