package de.superx.bianalysis; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import de.superx.bianalysis.metadata.Identifier; import de.superx.bianalysis.models.DimensionAttribute; import de.superx.bianalysis.models.FactTable; import de.superx.bianalysis.models.Filter; import de.superx.bianalysis.models.InfoItem; import de.superx.bianalysis.models.Measure; import de.superx.bianalysis.service.DbMetaAdapter; import de.superx.jdbc.entity.Sachgebiet; public class ReportMetadata { public final FactTable factTable; public final Sachgebiet sachgebiet; public final List leftDimensionAttributes; public final List topDimensionAttributes; public final List measures; public final List filters; public final List secondaryFacttables; public String lastBiadUpdate; public DbMetaAdapter dbMetaAdapter; public boolean hideEmptyColumns; public boolean isOuter = false; // TODO ugly public ReportMetadata(ReportDefinition reportDefinition, Identifier factTableId, DbMetaAdapter dbAdapter) { this.dbMetaAdapter = dbAdapter; this.hideEmptyColumns = reportDefinition.hideEmptyColumns; this.factTable = dbAdapter.getFactTable(factTableId); this.sachgebiet = dbAdapter.getSachgebietById(this.factTable.getSachgebiettid()); List databaseOrderedLeftDimensionAttributes = dbAdapter.getDimensionAttributeMetadata(reportDefinition.leftDimensionAttributeIds, factTableId); this.leftDimensionAttributes = reorderDimensionAttributesToReportOrder(databaseOrderedLeftDimensionAttributes, reportDefinition, false); List databaseOrderedTopDimensionAttributes = dbAdapter.getDimensionAttributeMetadata(reportDefinition.topDimensionAttributeIds, factTableId); this.topDimensionAttributes = reorderDimensionAttributesToReportOrder(databaseOrderedTopDimensionAttributes, reportDefinition, true); List databaseOrderedMeasures = dbAdapter.getMeasureMetadata(reportDefinition.measureIds); this.measures = reorderMeasuresToReportOrder(databaseOrderedMeasures, reportDefinition); if (reportDefinition.filters != null) { this.filters = dbAdapter.getFilterMetadata(reportDefinition.filters); } else { this.filters = new ArrayList(); } this.setTopDimensionAttributeValues(dbAdapter, factTableId); this.lastBiadUpdate = dbAdapter.getLastUpdate(440); this.secondaryFacttables = dbAdapter.getSecondaryFactTableMetadata(reportDefinition.secondaryFacttables, reportDefinition.leftDimensionAttributeIds); // TODO Ugly workaround to add measures to all measures from other facttables // if there is a outer join facttable we handle this elsewhere for (SecondaryFacttable sf : secondaryFacttables) { if(!hasOuter()) { for (Measure measure : sf.measures) { this.measures.add(measure); } } } } /** * Determines which dimension attribute a secondary fact table should use. * Conformed attributes are mapped to the matching role-playing attribute of the * secondary dimension. If no mapping exists, or if the attribute is not conformed, * a null/grouping copy of the provided attribute is returned instead. */ private DimensionAttribute resolveDimensionAttributeForSecondaryFacttable(DimensionAttribute attr, SecondaryFacttable secondaryFacttable) { String conformedDimensionId = attr.getDimConformedId(); if(conformedDimensionId == null) { DimensionAttribute attribute = this.dbMetaAdapter.getDimensionAttributeMetadata(attr.getId()); attribute.setNullAttribute(true); attribute.setGrouping(true); return attribute; } DimensionAttribute result = null; for (JoinCriteria joinCriteria : secondaryFacttable.joinCriterias) { String conformedDimensionIdOfSecondaryDimension = joinCriteria.secondaryDimension.getConformed(); if(conformedDimensionId.equals(conformedDimensionIdOfSecondaryDimension)) { Identifier conformedAttrId = new Identifier(attr.getAttrConformedId()); Identifier rolePlayingDimId = joinCriteria.secondaryDimension.getId(); Optional rolePlayingAttributeId = dbMetaAdapter.findRolePlayingAttributeId(conformedAttrId, rolePlayingDimId); if(rolePlayingAttributeId.isPresent()) { result = this.dbMetaAdapter.getDimensionAttributeMetadata(rolePlayingAttributeId.get()); result.setGrouping(true); } } } if(result == null) { result = this.dbMetaAdapter.getDimensionAttributeMetadata(new Identifier(attr.getId())); result.setNullAttribute(true); result.setGrouping(true); } return result; } public HashMap buildReportMetadatasForDrillAcross(){ List allMeasures = new ArrayList<>(); for (Measure m : measures) { allMeasures.add(m.getId()); } for (SecondaryFacttable sf : secondaryFacttables) { allMeasures.addAll(sf.measureIds); } // all metadata objects HashMap factMetadatas = new HashMap(); ReportMetadata primaryReportMetadata = new ReportMetadata(this, this.secondaryFacttables, allMeasures); factMetadatas.put(factTable.getId().composedId, primaryReportMetadata); // Every secondary facttable gets its own ReportMetadata for (SecondaryFacttable secondaryFacttable : secondaryFacttables) { if(!secondaryFacttable.join.equals("union")) { continue; } List leftAttributes = new ArrayList<>(); List rightAttributes = new ArrayList<>(); List filterAttributes = new ArrayList<>(); List measures2 = dbMetaAdapter.getMeasureMetadata(allMeasures); for (Measure measure : measures2) { if(!measure.getFacttableId().equals(secondaryFacttable.factId)) { measure.isNullMeasure = true; } } boolean isNullAttribute = false; for (DimensionAttribute attr : leftDimensionAttributes) { DimensionAttribute rolePlayingAttribute = resolveDimensionAttributeForSecondaryFacttable(attr, secondaryFacttable); if(isNullAttribute) { rolePlayingAttribute.setNullAttribute(true); } leftAttributes.add(rolePlayingAttribute); // if first is a null attribute all other automatically become null // attributes, because no drill down possible if(rolePlayingAttribute.isNullAttribute()) { isNullAttribute = true; } } isNullAttribute = false; for (DimensionAttribute attr : topDimensionAttributes) { DimensionAttribute rolePlayingAttribute = resolveDimensionAttributeForSecondaryFacttable(attr, secondaryFacttable); if(isNullAttribute) { rolePlayingAttribute.setNullAttribute(true); } rightAttributes.add(rolePlayingAttribute); // if first is a null attribute all other automatically become null // attributes, because no drill down possible if(rolePlayingAttribute.isNullAttribute()) { isNullAttribute = true; } } for (Filter f : filters) { Filter filter = new Filter(f); DimensionAttribute attr = dbMetaAdapter.getDimensionAttributeMetadataById(filter.dimensionAttributeId); DimensionAttribute rolePlayingAttribute = resolveDimensionAttributeForSecondaryFacttable(attr, secondaryFacttable); if(!rolePlayingAttribute.isNullAttribute()) { filter.dimensionAttributeId = rolePlayingAttribute.getId(); dbMetaAdapter.setFilterMetadata(filter); filterAttributes.add(filter); } } ReportMetadata secondaryFacttableMetadata = new ReportMetadata(secondaryFacttable.factTable, leftAttributes, rightAttributes, measures2, filterAttributes, dbMetaAdapter); // TODO all values from all facttables secondaryFacttableMetadata.setTopDimensionAttributeValues(dbMetaAdapter, secondaryFacttableMetadata.factTable.getId()); secondaryFacttableMetadata.isOuter = isOuter; factMetadatas.put(secondaryFacttable.factId.composedId, secondaryFacttableMetadata); } // set top dimension attribute filter values for (DimensionAttribute attribute : topDimensionAttributes) { DimensionAttribute confAttr = dbMetaAdapter.getDimensionAttributeMetadata(new Identifier(attribute.getAttrConformedId())); List values = dbMetaAdapter.getTopDimensionAttributeValuesForFactTables(confAttr, attribute, factTable, this.secondaryFacttables); for (ReportMetadata metadata : factMetadatas.values()) { for (DimensionAttribute attr : metadata.topDimensionAttributes) { if(attr.getAttrConformedId() == null) { continue; } if(attr.getAttrConformedId().equals(attribute.getAttrConformedId())) { attr.setDimensionAttributeValues(values); } } } } return factMetadatas; } public ReportMetadata(ReportMetadata metadata, List secondary, List measureIds) { this.dbMetaAdapter = metadata.dbMetaAdapter; this.factTable = metadata.factTable; this.sachgebiet = metadata.sachgebiet; this.topDimensionAttributes = metadata.topDimensionAttributes; List secondaryFacttables = new ArrayList<>(); for (SecondaryFacttable sf : secondary) { if(!sf.join.equals("union")) { secondaryFacttables.add(sf); } } this.measures = dbMetaAdapter.getMeasureMetadata(measureIds); for (Measure measure : this.measures) { // own facttable OR secondaryFacttable where inner or left join boolean isLeftOrInnerJoinFactTable = false; for (SecondaryFacttable sf : secondaryFacttables) { if(sf.measureIds.contains(measure.getId())) { isLeftOrInnerJoinFactTable = true; } } if(!isLeftOrInnerJoinFactTable && !measure.getFacttableId().equals(factTable.getId())) { measure.isNullMeasure = true; } } this.filters = metadata.filters; this.leftDimensionAttributes = metadata.leftDimensionAttributes; for (DimensionAttribute attribute : this.leftDimensionAttributes) { attribute.setGrouping(true); } this.hideEmptyColumns = metadata.hideEmptyColumns; this.secondaryFacttables = secondaryFacttables; } public ReportMetadata() { this.factTable = new FactTable(); this.sachgebiet = new Sachgebiet(); this.leftDimensionAttributes = new ArrayList<>(); this.topDimensionAttributes = new ArrayList<>(); this.measures = new ArrayList<>(); this.filters = new ArrayList<>(); this.secondaryFacttables = new ArrayList<>(); } public ReportMetadata(FactTable factTable, List leftDimensionAttributes2, List topDimensionAttributes, List measures2, List filterAttributes, DbMetaAdapter dbAdapter) { this.dbMetaAdapter = dbAdapter; this.factTable = factTable; this.sachgebiet = new Sachgebiet(); this.topDimensionAttributes = topDimensionAttributes; this.measures = measures2; this.leftDimensionAttributes = leftDimensionAttributes2; this.hideEmptyColumns = false; this.filters = filterAttributes; this.secondaryFacttables = new ArrayList<>(); } public List getSortOrderLeftDimensionAttributes(){ return leftDimensionAttributes.stream().filter(d -> d.getSortOrderColumn() != null).collect(Collectors.toList()); } private void setTopDimensionAttributeValues(DbMetaAdapter dbAdapter, Identifier factId) { for(DimensionAttribute attr : this.topDimensionAttributes) { List filtersInDimension = new ArrayList(); for(Filter filter : this.filters) { if(filter.tablename.equals(attr.getTablename())) { filtersInDimension.add(filter); } } attr.setDimensionAttributeValues(dbAdapter.getValuesOfAttribute(attr, factId, filtersInDimension, Boolean.valueOf(hideEmptyColumns))); } } private Filter getFilterForDimensionAttribute(Identifier id) { return this.filters .stream() .filter(f -> f.dimensionAttributeId.equals(id)) .findFirst() .orElse(null); } private static List reorderMeasuresToReportOrder(List measures, ReportDefinition reportDefinition) { List orderedMeasures = new ArrayList(); reportDefinition.measureIds.forEach(measureId -> { Measure nextMeasure = measures .stream() .filter( measure -> measure.getId().equals( measureId ) ) .findFirst() .orElse(null); orderedMeasures.add(nextMeasure); }); return orderedMeasures; } public static List reorderDimensionAttributesToReportOrder(List dimensionAttributes, ReportDefinition reportDefinition, boolean isTopAttribute) { List orderedDimensionAttributes = new ArrayList(); List attributeIds; if (isTopAttribute) { attributeIds = reportDefinition.topDimensionAttributeIds; } else { attributeIds = reportDefinition.leftDimensionAttributeIds; } attributeIds.forEach(attributeId -> { DimensionAttribute nextAttribute = dimensionAttributes .stream() .filter( dimensionAttribute -> dimensionAttribute.getId().equals( attributeId )) .findFirst() .orElse(null); orderedDimensionAttributes.add(nextAttribute); }); return orderedDimensionAttributes; } public DimensionAttribute getDimById(Identifier id) { DimensionAttribute attr = topDimensionAttributes .stream() .filter(a -> a.getDimensionId().equals(id)) .findFirst() .orElse(null); if(attr != null) { return attr; } attr = leftDimensionAttributes .stream() .filter(a -> a.getDimensionId().equals(id)) .findFirst() .orElse(null); return attr; } public DimensionAttribute getDimAttrById(Identifier id) { DimensionAttribute attr = topDimensionAttributes .stream() .filter(a -> a.getId().equals(id)) .findFirst() .orElse(null); if(attr != null) { return attr; } attr = leftDimensionAttributes .stream() .filter(a -> a.getId().equals(id)) .findFirst() .orElse(null); return attr; } /** * We want to join dimension tables only once. There we need a list of unique ids of * the dimensions, otherwise we duplicate our joins. */ public List getUniqueDimensionAttributes(){ Map joinTables = new HashMap(); for (DimensionAttribute attr : leftDimensionAttributes) { if(attr.isNullAttribute()) { continue; } if(!joinTables.containsKey(attr.getDimensionTableAlias())) { joinTables.put(attr.getDimensionTableAlias(), attr); } } for (DimensionAttribute attr : topDimensionAttributes) { if(attr.isNullAttribute()) { continue; } if(!joinTables.containsKey(attr.getDimensionTableAlias())) { joinTables.put(attr.getDimensionTableAlias(), attr); } } // join dimension if attribute occurs in filter for (Filter filter : filters) { Identifier attrId = filter.dimensionAttributeId; DimensionAttribute attr = dbMetaAdapter.getDimensionAttributeMetadataById(attrId); if(attr.isNullAttribute()) { continue; } if(!joinTables.containsKey(attr.getDimensionTableAlias())) { joinTables.put(attr.getDimensionTableAlias(), attr); } } // join dimension if measure has a filter for an attribute for (Measure measure : measures) { if(measure.filterAttributeId != null) { Identifier attrId = measure.filterAttributeId; DimensionAttribute attr = dbMetaAdapter.getDimensionAttributeMetadataById(attrId); if(!joinTables.containsKey(attr.getDimensionTableAlias())) { joinTables.put(attr.getDimensionTableAlias(), attr); } } } return new ArrayList(joinTables.values()); } public List getMeasureInfo() { if(measures != null && !measures.isEmpty()) { return measures.stream().map(m-> new InfoItem(m.getId().composedId, m.getCaption(), m.getDescription())).collect(Collectors.toList()); } return null; } public List getTopDimAttrAsInfo() { if(topDimensionAttributes != null && !topDimensionAttributes.isEmpty()) { return topDimensionAttributes.stream().map(m-> new InfoItem(m.getStringId(), m.getCaption(), m.getDescription())).collect(Collectors.toList()); } return null; } public List getLeftDimAttrAsInfo() { if(leftDimensionAttributes != null && !leftDimensionAttributes.isEmpty()) { return leftDimensionAttributes.stream().map(m-> new InfoItem(m.getStringId(), m.getCaption(), m.getDescription())).collect(Collectors.toList()); } return null; } public List getFilterAsInfo(){ ArrayList filterList = new ArrayList(); DimensionAttribute dimAttr; for (Filter filter: filters) { dimAttr = filter.getDimAttribute(this); if(dimAttr == null) { dimAttr = dbMetaAdapter.getDimensionAttributeById(filter.dimensionAttributeId); } filterList.add(" (" + dimAttr.getCaption() + ") " + filter.getValuesAsString()); } return filterList; } public List getSacondaryFacttableAsInfo(){ ArrayList secondaryFacttableList = new ArrayList(); for (SecondaryFacttable secondaryFacttable: secondaryFacttables) { String label = ""; if(secondaryFacttable.join.equals("left")) { label+= "Inklusion "; } else if(secondaryFacttable.join.equals("inner")) { label+= "Schnittmenge "; } else if(secondaryFacttable.join.equals("union")) { label+= "Vereinigung "; } label+= "mit "; label+=(secondaryFacttable.factTable.getCaption()); label+= " über "; for( int i = 0; i < secondaryFacttable.joinCriterias.size(); i++) { label+=(secondaryFacttable.joinCriterias.get(i).secondaryDimension.getCaption()); if(i < secondaryFacttable.joinCriterias.size() -1) { label+= " und "; } } secondaryFacttableList.add(label); } return secondaryFacttableList; } public boolean hasOuter() { for (SecondaryFacttable factable : this.secondaryFacttables) { if(factable.join.equals("union")) { return true; } } return false; } }