Changeset 14502 in main
- Timestamp:
- 08/22/17 22:39:28 (5 years ago)
- Location:
- trunk
- Files:
-
- 24 added
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/ibisph/src/main/java/org/ibisph/querymodule/modelmap/AddMissingQueryResultRecordsToQueryModule.java
r14457 r14502 2 2 3 3 import java.util.ArrayList; 4 import java.util. LinkedHashMap;4 import java.util.HashMap; 5 5 import java.util.List; 6 6 import java.util.Map; … … 16 16 17 17 import org.ibisph.modelmap.ProcessModelMap; 18 import org.ibisph. querymodule.service.ArrayIndexIncrementer;18 import org.ibisph.util.StrLib; 19 19 import org.ibisph.util.XMLLib; 20 20 21 21 22 /** 22 * Adds the missing value dimension records to the supplied query module model 23 * if the QM's configuration has the ADD_MISSING_QUERY_RESULT_RECORDS struct 24 * defined. This provides a similar effect as using the 25 * QUERY_APPLICATION_REQUEST_ADD_NON_FILTERED_GROUP_BY_DIMENSION_VALUES_FLAG. 26 * The IBISQ processing is typically a better solution as SAS can do additional 27 * checks on the data and provide more approp data values. This feature (if 28 * wired up) has is useful for survey type data or if an adopter needs to use 29 * on some data that is not supported via the backend IBISQ/SAS data frame etc. 30 * 31 * NOTE: This modelmap update code will ONLY supplement missing value type 32 * records. It does not create records if no records are returned from the 33 * query. 23 * Adds the missing dimension value records to the supplied query module's 24 * IBISQ request result records under these conditions: 25 * A) The QM's configuration has the ADD_MISSING_QUERY_RESULT_RECORDS struct 26 * defined (wired up). This provides a similar effect as using the 27 * QUERY_APPLICATION_REQUEST_ADD_NON_FILTERED_GROUP_BY_DIMENSION_VALUES_FLAG. 28 * The IBISQ processing is typically a better solution as SAS can do 29 * additional checks on the data and provide more approp data values. This 30 * feature (if wired up) is useful for survey type data or if an adopter 31 * needs to use on some data that is not supported via the backend IBISQ/SAS 32 * data frame etc. 33 * B) The specified, associated DIM VALUES (via the DIMENSION_NAME or the 34 * ALL_FLAG) are NOT filtered. 35 * C) Does NOT create/supplement missing value type records if no records. 36 * 37 * IMPORTANT: This modelmap update code MUST be ran after the IBISQ query result 38 * records are inserted into the QM XML doc. 39 * 40 * SECURITY NOTE: Don't need to worry about non user auth values as these are 41 * a) blank, and b) auto filtered by the security related code. 34 42 * 35 43 * STRUCTURE: … … 73 81 74 82 Node queryModuleConfigurationNode = XMLLib.getNode( 75 queryModule, 83 queryModule, 76 84 "/QUERY_MODULE/CONFIGURATIONS/CONFIGURATION[NAME = /QUERY_MODULE/REQUEST/CONFIGURATION_NAME]" 77 85 ); … … 81 89 throw new Exception("Requested query module configuration [" + configurationName + "] does NOT exist in query module: " + queryModuleName); 82 90 } 91 92 // if no missing values specified for this config return. 83 93 Node addMissingResultRecordsNode = XMLLib.getNode( 84 94 queryModuleConfigurationNode, 85 95 "ADD_MISSING_QUERY_RESULT_RECORDS" 86 96 ); 87 88 // If add missing values element exists then proceed. 97 if(null == addMissingResultRecordsNode) return; 98 99 // if no records return; 100 Node queryResultRecords = XMLLib.getNode( 101 queryModule, 102 "/QUERY_MODULE/IBISQ_QUERY_RESULT/RECORDS/RECORD" 103 ); 104 if(null == queryResultRecords) { 105 logger.debug(".addMissingQueryResultRecordElements - no result records NOT able to process for missing values - model NOT modified."); 106 return; 107 } 108 109 // Add missing values element exists - proceed. 89 110 // First check for the ALL_FLAG. If add all does not exists then get and 90 111 // put all the DIMENSION_NAME elements into a list. The sub processing 91 112 // code uses the list to determine if process all (null list) or to check 92 113 // for against the specific dimension names specified (to be matched - 93 // deals with actual and proxy dimension names). If not add all or any 94 // dimension names then missing values is not processed. 95 if(null != addMissingResultRecordsNode) { 96 List<String> missingRecordsDimensionNameList = null; 97 Node addAllFlagNode = XMLLib.getNode(addMissingResultRecordsNode, "ALL_FLAG"); 98 if(null == addAllFlagNode) { 99 List<Node> missingRecordsDimensionNamesElements = XMLLib.getNodes(addMissingResultRecordsNode, ".//DIMENSION_NAME"); 100 missingRecordsDimensionNameList = new ArrayList<String>(); 101 for(int i=0; i<missingRecordsDimensionNamesElements.size(); i++) { 102 String dimensionName = missingRecordsDimensionNamesElements.get(i).getText().trim(); 103 missingRecordsDimensionNameList.add(dimensionName); 114 // deals with actual and proxy dimension names). If not add all or no dim 115 // names specified then missing values is not processed - exit. 116 List<String> missingRecordsDimensionNameList = null; 117 Node addAllFlagNode = XMLLib.getNode(addMissingResultRecordsNode, "ALL_FLAG"); 118 if(null == addAllFlagNode) { 119 List<Node> missingRecordsDimensionNamesElements = XMLLib.getNodes(addMissingResultRecordsNode, ".//DIMENSION_NAME"); 120 missingRecordsDimensionNameList = new ArrayList<String>(); 121 for(int i=0; i<missingRecordsDimensionNamesElements.size(); i++) { 122 String dimensionName = missingRecordsDimensionNamesElements.get(i).getText().trim(); 123 missingRecordsDimensionNameList.add(dimensionName); 124 } 125 } 126 if((null == addAllFlagNode) && 127 ((null == missingRecordsDimensionNameList) || (0 == missingRecordsDimensionNameList.size())) 128 ) return; 129 130 // null missingRecordsDimensionNameList == add all. 131 // If no dimensions matches then return - don't process. 132 List<String> actualDimensionNameList = getRequestedActualDimensionNames(queryModule); 133 Map<String, List<String>> dimensionNameValuesMap = getDimensionValuesMap( 134 queryModule, 135 actualDimensionNameList, 136 missingRecordsDimensionNameList 137 ); 138 if(null == dimensionNameValuesMap) { 139 logger.debug(".addMissingQueryResultRecordElements - result did not contain any dimensions that match the criteria - model not modified."); 140 return; 141 } 142 143 // if here then the dim name matched the criteria so process for missing values. 144 logger.debug(".addMissingQueryResultRecordElements - processing for missing records - dimension map size: {}", dimensionNameValuesMap.size()); 145 Node measure = XMLLib.getNode(queryModuleConfigurationNode, ".//MEASURE[1]"); 146 String measuresName = XMLLib.getText(measure, "NAME"); 147 String missingMeasureValue = XMLLib.getText(measure, "MISSING_QUERY_RESULT_RECORD_VALUE"); 148 if(null == missingMeasureValue) missingMeasureValue = this.defaultMissingValue; 149 Node ancillaryValueNames = XMLLib.getNode(queryModuleConfigurationNode, "ANCILLARY_VALUE_NAMES"); 150 addMissingResultRecordElements( 151 queryModule, 152 dimensionNameValuesMap, 153 measuresName, 154 missingMeasureValue, 155 ancillaryValueNames 156 ); 157 } //-------------------------- End of Method ------------------------------ 158 159 160 /** 161 * Builds a list of all dimension values keyed by a dimension name to be used 162 * for checking and creating missing query result RECORD elements. If the 163 * missingValuesDimensionNameList is null then ALL dimension values are tested 164 * and included (unless filtered). If a dimension is filtered (has a selected 165 * dimension) then that dimension is used only for xpath record lookup. That 166 * filtered dimension is not included as part of the "add missing record" dim 167 * cube/determination rule. 168 * 169 * This code also handles if the dim list name is a 170 * proxy to the actual query result. 171 * @param queryModuleDocument 172 * @param missingValuesDimensionNameList optional list of dimension names to 173 * be processed. If null then all dim names and values will be built. 174 * @return map of all dimension name/values IF criteria is matched otherwise 175 * a null map is returned - which is a flag to not process. 176 */ 177 protected Map<String, List<String>> getDimensionValuesMap ( 178 Node queryModuleDocument, 179 List<String> actualDimensionNameList, 180 List<String> missingValuesDimensionNameList 181 ) { 182 boolean addAllDimensions = (null == missingValuesDimensionNameList); 183 184 // make sure that the dimension names are needing to be checked. Start 185 // with the actual dims and if no match get that dims proxy and test it. 186 // if no matches AND not the ALL dimensions then no add missing processing 187 // is needed - return a null map which tells the caller nothing to add. 188 ArrayList<String> dimensionNamesToBeUsedForMissingValues = new ArrayList<String>(); 189 for(String dimensionName : actualDimensionNameList) { 190 if(addAllDimensions || missingValuesDimensionNameList.contains(dimensionName)) { 191 dimensionNamesToBeUsedForMissingValues.add(dimensionName); 192 } 193 else { 194 String proxyDimensionName = XMLLib.getText( 195 queryModuleDocument, 196 "/QUERY_MODULE/DIMENSIONS/DIMENSION[NAME='" + dimensionName + "']/PROXY_DIMENSION_NAME", 197 "" 198 ); 199 if(missingValuesDimensionNameList.contains(proxyDimensionName)) { 200 dimensionNamesToBeUsedForMissingValues.add(dimensionName); 104 201 } 105 202 } 106 107 // Process for missing values. To proceed either need the all flag set 108 // or some dimension names MUST be specified. 109 if((null != addAllFlagNode) || (0 < missingRecordsDimensionNameList.size())) { 110 111 // Next get all the valid dim names and their values. This map is 112 // built based on the exiting query result record set and the missing 113 // values list. If the dim name list doesn't match anything then 114 // the map returned is null which acts as a flag to not process for 115 // any missing value. 116 Map<String, List<String>> dimensionNameValuesMap = getDimensionValuesMap( 117 queryModule, 118 missingRecordsDimensionNameList 119 ); 120 121 // If result does not match the criteria then return - don't process. 122 if(null == dimensionNameValuesMap) { 123 logger.debug(".addMissingQueryResultRecordElements - result did not contain any dimensions that match the criteria - model not modified."); 124 return; 125 } 126 if(0 == dimensionNameValuesMap.size()) { 127 logger.debug(".addMissingQueryResultRecordElements - no result dimensions/records - not adding missing values so user will see Query Too Specific message."); 128 return; 129 } 130 131 // if here then the dim name matched the criteria so process for missing values. 132 logger.debug(".addMissingQueryResultRecordElements - processing for missing records - dimension map size: {}", dimensionNameValuesMap.size()); 133 buildQueryResultRecordElements( 134 queryModuleConfigurationNode, 135 dimensionNameValuesMap 136 ); 137 } 138 } 139 140 } //-------------------------- End of Method ------------------------------ 203 } 204 if(dimensionNamesToBeUsedForMissingValues.isEmpty()) { 205 logger.debug(".getDimensionValuesMap, no dimensions matched - exiting."); 206 return(null); 207 } 208 209 // If here then ALL or some dimension matched. Build a map with the filtered 210 // dimension values. If the filter map size matches the dimension name 211 // size then everything is filtered. Return a null map as nothing needs 212 // to be checked for/added. 213 HashMap<String, List<String>> dimensionValuesMap = new HashMap<String, List<String>>(); 214 boolean allFiltered = true; 215 for(String dimensionName : actualDimensionNameList) { 216 List<String> dimensionValues = getFilteredDimensionValues(queryModuleDocument, dimensionName); 217 if(0 == dimensionValues.size()) { 218 allFiltered = false; 219 } 220 else { 221 dimensionNamesToBeUsedForMissingValues.remove(dimensionName); 222 dimensionValuesMap.put(dimensionName, dimensionValues); 223 } 224 } 225 if(dimensionNamesToBeUsedForMissingValues.isEmpty() || allFiltered) { 226 logger.debug(".getDimensionValuesMap, all dimensions filtered - exiting."); 227 return(null); 228 } 229 230 // lastly if here then some dimension values need to be added. Loop the 231 // dimension names and if not in the map as a filtered dimension value list 232 // then add all of the dimension's values and return the map. 233 for(String dimensionName : actualDimensionNameList) { 234 List<String> dimensionValues = dimensionValuesMap.get(dimensionName); 235 if((null == dimensionValues) || (0 == dimensionValues.size())) { 236 dimensionValues = getAllDimensionValues(queryModuleDocument, dimensionName); 237 dimensionValuesMap.put(dimensionName, dimensionValues); 238 } 239 } 240 return(dimensionValuesMap); 241 } //-------------------------- End of Method ------------------------------ 242 243 244 protected List<String> getRequestedActualDimensionNames(Node queryModuleDocument) { 245 ArrayList<String> dimensionNamesList = new ArrayList<String>(); 246 List<Node> dimensionNamesNodeList = XMLLib.getNodes(queryModuleDocument, "/QUERY_MODULE/REQUEST/ACTUAL_GROUP_BY/*"); 247 if(null != dimensionNamesNodeList) { 248 for(int j=0; j<dimensionNamesNodeList.size(); j++) { 249 String dimensionName = dimensionNamesNodeList.get(j).getText().trim(); 250 dimensionNamesList.add(dimensionName); 251 } 252 } 253 return(dimensionNamesList); 254 } //-------------------------- End of Method ------------------------------ 255 256 protected List<String> getFilteredDimensionValues(Node queryModuleDocument, String dimensionName) { 257 ArrayList<String> dimensionValuesList = null; 258 259 List<Node> dimensionValuesNodeList = XMLLib.getNodes( 260 queryModuleDocument, 261 "/QUERY_MODULE/CRITERIA/SECTIONS/SECTION/SELECTED_DIMENSIONS/SELECTED_DIMENSION[NAME = '" + dimensionName + "']/VALUES/VALUE" 262 ); 263 if(null != dimensionValuesNodeList) { 264 dimensionValuesList = new ArrayList<String>(); 265 for(int j=0; j<dimensionValuesNodeList.size(); j++) { 266 String dimensionValue = dimensionValuesNodeList.get(j).getText().trim(); 267 dimensionValuesList.add(dimensionValue); 268 } 269 } 270 return(dimensionValuesList); 271 } //-------------------------- End of Method ------------------------------ 272 273 protected List<String> getAllDimensionValues(Node queryModuleDocument, String dimensionName) { 274 ArrayList<String> dimensionValuesList = null; 275 List<Node> dimensionValuesNodeList = XMLLib.getNodes( 276 queryModuleDocument, 277 "/QUERY_MODULE/DIMENSIONS/DIMENSION[NAME = '" + dimensionName + "']/VALUES/VALUE[not(NOT_SELECTABLE_FLAG)]" 278 ); 279 if(null != dimensionValuesNodeList) { 280 dimensionValuesList = new ArrayList<String>(); 281 for(int j=0; j<dimensionValuesNodeList.size(); j++) { 282 String dimensionValue = dimensionValuesNodeList.get(j).getText().trim(); 283 dimensionValuesList.add(dimensionValue); 284 } 285 } 286 return(dimensionValuesList); 287 } //-------------------------- End of Method ------------------------------ 288 141 289 142 290 143 291 /** 144 * Main service method that loops through the supplied document and adds145 * the IBIS-Q result XML elements for dimension/measure values that are146 * missing from the complete set of possible dimension values.147 * @param queryModule ConfigurationNodeselected query module configuration element.292 * Loops through all the dimensions/values and locates the matching IBIS-Q 293 * result record. If record not found then a new RECORD element is created 294 * and added. 295 * @param queryModuleDocument selected query module configuration element. 148 296 * @param missingValuesDimensionNameList optional list of dimension names 149 297 * to be processed. If null then ALL dimensions are processed. 150 298 */ 151 public void buildQueryResultRecordElements( 152 Node queryModuleConfigurationNode, 153 Map<String, List<String>> dimensionNameValuesMap 299 protected void addMissingResultRecordElements( 300 Node queryModuleDocument, 301 Map<String, List<String>> dimensionNameValuesMap, 302 String measureName, 303 String missingMeasureValue, 304 Node ancillaryValueNames 154 305 ) { 155 306 156 // Before we start building value records (with approp missing values) we 157 // need to init some xpath related objects. Due to issues with the Dom4j 158 // xpath processing, we need to convert to a standard DOM and use a non 159 // dom4j xpath lookup to test for the missing dimension value. 160 // ISSUE: some weird size/positional DOM4J tree issue was experienced when 161 // doing a missing values for InfMort. If 2009 and 1990 is selected then 307 // Init w3c xpath related objects. Due to issues with the Dom4j xpath 308 // processing, we need to convert to a standard DOM and use a non dom4j 309 // xpath lookup to test for the missing dimension value. 310 // 311 // OBSERVED ISSUES: 312 // 1) some weird size/positional DOM4J tree issue was experienced when 313 // missing values for UT InfMort. If 2009 and 1990 is selected then 162 314 // configured for GeoSarea to be missing and all SAs selected and grouped by 163 315 // one of the IBIS-Q RECORD elements would not be found via xpath. If the … … 172 324 // without any diff. 173 325 // Test URL: http://localhost/ibisph-view/query/result/infmort/InfMortMainSarea/InfMortRate.html 174 // Committed several versions of this code to the repo 6/4/2011 svn: 2784. 326 // 2) For the HI prams module using the w3c doc and xpath some records would 327 // be removed. This was intermittent and was based on the XML doc and weird 328 // combinations of filtering and grouping. The IBISQ XML result was correct 329 // but XPATH processing resulted in strange output. The issue could never 330 // be replicated in a dev enviro but was repeatable in the HI production. 331 // 332 // Committed several versions of this code to the repo 6/4/2011 svn: 2784. 333 // and then the orig w3c code prior to 8/2017 commit of this code. 175 334 org.w3c.dom.Document w3cQueryModuleDocument = null; 176 335 try { 177 w3cQueryModuleDocument = XMLLib.getW3CDocument(queryModule ConfigurationNode.getDocument());336 w3cQueryModuleDocument = XMLLib.getW3CDocument(queryModuleDocument.getDocument()); 178 337 } 179 338 catch(TransformerException te) { … … 184 343 XPathFactory factory = new net.sf.saxon.xpath.XPathFactoryImpl(); 185 344 XPath xpath = factory.newXPath(); 186 187 // Now add/populate any missing RECORD elements if not found in the query188 // results XML/doc. This is done by looping through all of the available189 // dimension names and assoc values and querying for that record. If the190 // record is NOT found then create a new one and add it.191 192 // setup the structs that provides all possible dimension name combinations193 // to be looped.194 int dimensionCount = dimensionNameValuesMap.size();195 int[] maxIndexSize = new int [dimensionCount];196 String[] dimensionName = new String[dimensionCount];197 dimensionCount = 0;198 for(String name : dimensionNameValuesMap.keySet()) {199 maxIndexSize [dimensionCount] = dimensionNameValuesMap.get(name).size();200 dimensionName[dimensionCount] = name;201 dimensionCount++;202 }203 ArrayIndexIncrementer arrayIndexIncrementer = new ArrayIndexIncrementer(maxIndexSize);204 int[] currentDimensionIndexes = arrayIndexIncrementer.getCurrentIndex();205 345 206 346 // loop through all the possible dimension values. To try and preserve … … 210 350 // to the new records element. Else not found, add the newly created 211 351 // missing value element to the RECORDS container. 212 Node newRecordsElement = XMLLib.newNode("RECORDS"); 213 while(null != currentDimensionIndexes) { 214 215 // get the xpath for the current ibisq query result RECORD element - 216 // dimension name/value (based on the indexes) to be queried. 217 String queryResultRecordXPath = getQueryResultRecordXPath( 218 dimensionNameValuesMap, 219 dimensionName, 220 currentDimensionIndexes 221 ); 222 223 // test if that record exists. If it doesn't then add it. If it does 224 // exist then get xpath the doc's record and detach it so it can be 225 // added to the new set. 226 int missingValueRecordCount = 0; 227 try { 228 XPathExpression xPathExpression = xpath.compile(queryResultRecordXPath); 229 NodeList nodeList = (NodeList)xPathExpression.evaluate(w3cQueryModuleDocument, XPathConstants.NODESET); 230 missingValueRecordCount = nodeList.getLength(); 231 } 232 catch(Exception e) { 233 missingValueRecordCount = 0; 234 logger.error(".addMissingValues - xpath compile/evaluate cause: {}", e.getCause().getLocalizedMessage()); 235 } 236 if(0 == missingValueRecordCount) { 237 logger.debug(".addMissingQueryResultRecordElements - adding missing record for dimension: {}.", currentDimensionIndexes); 238 XMLLib.addNode(newRecordsElement, 239 createNewMissingValueRecordElement( 240 queryModuleConfigurationNode, 241 dimensionNameValuesMap, 242 dimensionName, 243 currentDimensionIndexes 244 ) 245 ); 246 } 247 else { 248 logger.debug(".addMissingQueryResultRecordElements - found record for dimension: {}, xpath:{}.", currentDimensionIndexes, queryResultRecordXPath); 249 Node nodeToAdd = XMLLib.getNode(queryModuleConfigurationNode, queryResultRecordXPath); 250 if(null != nodeToAdd) { 251 XMLLib.addNode(newRecordsElement, nodeToAdd.detach()); 252 } 253 else { 254 logger.error(".addMissingQueryResultRecordElements - found record but xpath returned NULL node. xpath:{}.", queryResultRecordXPath); 255 } 256 } 257 258 // Increment the indexes/counters and continue until done. 259 currentDimensionIndexes = arrayIndexIncrementer.incrementCurrentIndex(); 260 } 261 262 // finally, replace the existing RECORDS element with the newly created version. 263 Node ibisQResultContainer = XMLLib.getNode(queryModuleConfigurationNode, "/QUERY_MODULE/IBISQ_QUERY_RESULT"); 264 XMLLib.replaceNode(ibisQResultContainer, "RECORDS", newRecordsElement); 265 } //-------------------------- End of Method ------------------------------ 266 267 268 269 /** 270 * Builds a valid values map of all query result dimension name/values based 271 * on missingValuesDimensionNameList and the actual query result record's dim 272 * names and values. The map entries built have a dimension name as its key 273 * with the value of the map entry being a List of associated dimension values. 274 * 275 * This valid list of values is all query result dim values for those dims that 276 * do not match one of the dims passed in in the list. If in the list then 277 * dim and its values are checked for filtering. If not being filtered then 278 * the entire dim's values are included. If filtered then only the filtered 279 * values are included. This code also handles if the dim list name is a 280 * proxy to the actual query result. 281 * @param queryModuleDocument 282 * @param missingValuesDimensionNameList optional list of dimension names to 283 * be processed. If null then all dim names and values will be built. 284 * @return map of all dimension name/values IF criteria is matched otherwise 285 * a null map is returned - which is a flag to not process. 286 */ 287 protected Map<String, List<String>> getDimensionValuesMap ( 288 Node queryModuleDocument, 289 List<String> missingValuesDimensionNameList 290 ) { 291 292 293 get the QM/REQUEST/ 294 SELECTED_GROUP_BY 295 CATEGORY_DIMENSION_NAME 296 SERIES_DIMENSION_NAME 297 OTHER_DIMENSION_NAME 298 ACTUAL_GROUP_BY 299 CATEGORY_DIMENSION_NAME 300 SERIES_DIMENSION_NAME 301 OTHER_DIMENSION_NAME 302 303 if all then use the ACTUAL group by dim names to get the values 304 305 else need to test the ADD MISSING DIM NAME 306 check all SELECTED_GROUP_BY first. 307 if matches MISSING DIM NAME then get associated ACTUAL and use 308 else check MISSING DIM NAME against ACTUAL 309 if matches then use 310 311 build the list of valid DIM VALUES by checking filtering. If filtered then 312 dim values to use are the filtered ones. Else - use all dim values that 313 do NOT have a NOT_SELECTABLE_FLAG. E.g. Only process missing dim values that 314 DO NOT have the NOT_SELE flag. 352 Node recordsContainerElement = XMLLib.getNode(queryModuleDocument, "/QUERY_MODULE/IBISQ_QUERY_RESULT/RECORDS"); 353 String[] dimensionNameKey = dimensionNameValuesMap.keySet().toArray(new String[3]); 354 String[] dimensionValues = new String[3]; 355 List<String> dimension1Values = dimensionNameValuesMap.get(dimensionNameKey[0]); 356 List<String> dimension2Values = (1 < dimensionNameValuesMap.size()) ? dimensionNameValuesMap.get(dimensionNameKey[1]) : null; 357 List<String> dimension3Values = (2 < dimensionNameValuesMap.size()) ? dimensionNameValuesMap.get(dimensionNameKey[2]) : null; 358 List<Node> ancillaryValueNameList = (null != ancillaryValueNames) ? XMLLib.getNodes(ancillaryValueNames, "ANCILLARY_VALUE_NAME") : null; 315 359 316 317 318 319 320 321 322 // First, get the dimension names and associated values (map - contains 323 // the dim name and a list of the associated dim values). If the dim is 324 // one of the "populate missing values" list then 1) if filtering on that 325 // dim use the filtered values else 2) use ALL the dimension values from 326 // the defined dimension list. If dimension is not missing values then 327 // get the unique, actual dim values from the query result. 328 // NOTE: tried doing an xpath of ::preceeding-sibling but did NOT work so 329 // went with brute force of looping all and keeping track of what was 330 // processed. XSLT 2.0 supports distinct-values but this is xpath so NA. 331 // NOTE 2: Using a linked hash map to retain ordering. 332 333 // global flag that if false means that nothing matched so leave result as is - return a null list. 334 boolean processForMissingValues = false; 335 336 List<Node> queryResultDimensionNameNodeList = XMLLib.getNodes(queryModuleDocument, "/QUERY_MODULE/IBISQ_QUERY_RESULT//DIMENSION/NAME"); 337 List<String> processedDimensionNames = new ArrayList<String>(); 338 Map<String, List<String>> dimensionNameValuesMap = new LinkedHashMap<String, List<String>>(); 339 for(int i=0; i<queryResultDimensionNameNodeList.size(); i++) { 340 List<Node> dimensionValuesNodeList; 341 List<String> dimensionValuesList = new ArrayList<String>(); 342 String dimensionName = queryResultDimensionNameNodeList.get(i).getText().trim(); 343 if(!processedDimensionNames.contains(dimensionName)) { 344 345 // Process for missing values if: 346 // 1) the QM config has the ALL_FLAG set e.g. null missingValuesDimensionNameList 347 // 2) if the result has a dimension name that matches a missingValuesDimensionNameList. 348 // 3) if the result has a dim name that matches a QM dim name and that QM dim has 349 // a proxy name that is contained in the missingValuesDimensionNameList. 350 boolean addMissingValues = (null == missingValuesDimensionNameList); 351 if(!addMissingValues) addMissingValues = missingValuesDimensionNameList.contains(dimensionName); 352 if(!addMissingValues){ 353 String proxyDimensionName = XMLLib.getText( 354 queryModuleDocument, 355 "/QUERY_MODULE/DIMENSIONS/DIMENSION[NAME='" + dimensionName + "']/PROXY_DIMENSION_NAME", 356 "" 357 ); 358 addMissingValues = missingValuesDimensionNameList.contains(proxyDimensionName); 359 } 360 361 if(addMissingValues){ 362 logger.debug(".getMissingValueDimensionValuesMap - processForMissingValues, IBIS-Q XML: " + XMLLib.getNode(queryModuleDocument, "/QUERY_MODULE/IBISQ_QUERY_RESULT").asXML()); 363 processForMissingValues = true; // set the global flag... 364 365 // first try and select "selected" dimension values. If there are 366 // not any for the given dimension, then select ALL dimension values 367 // from the core DIMENSION definition. IF there are any result 368 // values that are NOT_ASSOCITED those need to be included. 369 dimensionValuesNodeList = XMLLib.getNodes( 370 queryModuleDocument, 371 "/QUERY_MODULE/CRITERIA/SECTIONS/SECTION/SELECTED_DIMENSIONS/SELECTED_DIMENSION[NAME = '" + dimensionName + "']/VALUES/VALUE" 372 ); 373 if(dimensionValuesNodeList.size() < 1) { 374 dimensionValuesNodeList = XMLLib.getNodes( 375 queryModuleDocument, 376 "/QUERY_MODULE/DIMENSIONS/DIMENSION[NAME = '" + dimensionName + "']/VALUES/VALUE[not(NOT_ASSOCIATED_FLAG)]" 377 ); 360 int k = (null != dimension3Values) ? dimension3Values.size() : 1; 361 while(0 < k) { 362 k--; 363 dimensionValues[2] = (null != dimension3Values) ? dimension3Values.get(k) : null; 364 365 int j = (null != dimension2Values) ? dimension2Values.size() : 1; 366 while(0 < j) { 367 j--; 368 dimensionValues[1] = (null != dimension2Values) ? dimension2Values.get(j) : null; 369 370 int i = dimension1Values.size(); 371 while(0 < i) { 372 i--; 373 dimensionValues[0] = dimension1Values.get(i); 374 375 // get the xpath for the current ibisq query result RECORD element - 376 // dimension name/value (based on the indexes) to be queried. 377 String queryResultRecordXPath = getQueryResultRecordXPath(dimensionNameKey, dimensionValues); 378 379 // test if that record exists. If it doesn't then add it. If it does 380 // exist then get xpath the doc's record and detach it so it can be 381 // added to the new set. 382 int missingValueRecordCount = 0; 383 try { 384 XPathExpression xPathExpression = xpath.compile(queryResultRecordXPath); 385 NodeList nodeList = (NodeList)xPathExpression.evaluate(w3cQueryModuleDocument, XPathConstants.NODESET); 386 missingValueRecordCount = nodeList.getLength(); 378 387 } 379 380 for(int j=0; j<dimensionValuesNodeList.size(); j++) { 381 String dimensionValue = dimensionValuesNodeList.get(j).getText().trim(); 382 dimensionValuesList.add(dimensionValue); 388 catch(Exception e) { 389 missingValueRecordCount = 0; 390 logger.error(".addMissingResultRecordElements - xpath compile/evaluate cause: {}", e.getCause().getLocalizedMessage()); 383 391 } 384 385 // need to process any other values returned - like NOT_ASSOCIATEDs 386 // or any other value the query returned and is not in the dim list. 387 // So, grab all actual values and if the list doesn't contain then 388 // add it to the list... 389 dimensionValuesNodeList = XMLLib.getNodes(queryModuleDocument, 390 "/QUERY_MODULE/IBISQ_QUERY_RESULT//DIMENSION" + 391 "[NAME = '" + dimensionName + "']/VALUE" 392 ); 393 for(int j=0; j<dimensionValuesNodeList.size(); j++) { 394 String dimensionValue = dimensionValuesNodeList.get(j).getText().trim(); 395 if(!dimensionValuesList.contains(dimensionValue)) { 396 dimensionValuesList.add(dimensionValue); 397 } 392 if(0 == missingValueRecordCount) { 393 logger.debug(".addMissingResultRecordElements - adding missing record for dimension names: {}, values: {}.", dimensionNameKey, dimensionValues); 394 XMLLib.addNode(recordsContainerElement, 395 createNewMissingValueRecordElement( 396 dimensionNameKey, dimensionValues, 397 measureName, missingMeasureValue, 398 ancillaryValueNameList 399 ) 400 ); 398 401 } 399 402 } 400 401 // Else this dim name (and its values) are not part of the missing name 402 // mechanism - add all the values... 403 else { 404 dimensionValuesNodeList = XMLLib.getNodes(queryModuleDocument, 405 "/QUERY_MODULE/IBISQ_QUERY_RESULT//DIMENSION" + 406 "[NAME = '" + dimensionName + "']/VALUE" 407 ); 408 for(int j=0; j<dimensionValuesNodeList.size(); j++) { 409 String dimensionValue = dimensionValuesNodeList.get(j).getText().trim(); 410 if(!dimensionValuesList.contains(dimensionValue)) { 411 dimensionValuesList.add(dimensionValue); 412 } 413 } 414 } 415 416 // now add the dim values to the dim map and add the processed dim name 417 // to the list so that it won't be processed twice. 418 dimensionNameValuesMap.put(dimensionName, dimensionValuesList); 419 processedDimensionNames.add(dimensionName); 420 } 421 } 422 423 // if missing values exist to be processed then return the map. Else null. 424 if(processForMissingValues) 425 return(dimensionNameValuesMap); 426 else 427 return(null); 403 } 404 } 405 428 406 } //-------------------------- End of Method ------------------------------ 429 407 … … 433 411 * Creates a stubbed out IBIS_QUERY_RESULT//RECORD element based on the 434 412 * dimensions and CONFIGURATION//MEASURE/MISSING_VALUE value. 435 * @param queryModuleConfigurationNode436 * @param dimensionNameValuesMap437 * @param dimensionName438 * @param currentDimensionIndexes439 413 * @return complete IBIS_QUERY_RESULT//RECORD element. 440 414 */ 441 415 protected Node createNewMissingValueRecordElement( 442 Node queryModuleConfigurationNode, 443 Map<String, List<String>> dimensionNameValuesMap, 444 String[] dimensionName, 445 int[] currentDimensionIndexes 416 String[] dimensionName, String[] dimensionValue, 417 String measureName, String missingValue, 418 List<Node> ancillaryValueNameList 446 419 ) { 420 Node recordElement = XMLLib.newNode("RECORD"); 421 447 422 // create the DIMENSIONS container and add all dims according to the index. 448 423 Node dimensionsContainerElement = XMLLib.newNode("DIMENSIONS"); 449 for(int i=0; i<dimensionName.length; i++) { 450 Node dimensionElement = XMLLib.newNode("DIMENSION"); 451 XMLLib.addNode(dimensionElement, XMLLib.newNode("NAME", dimensionName[i])); 452 453 List<String> dimensionValues = dimensionNameValuesMap.get(dimensionName[i]); 454 String value = (String)dimensionValues.get(currentDimensionIndexes[i]); 455 XMLLib.addNode(dimensionElement, XMLLib.newNode("VALUE", value)); 456 XMLLib.addNode(dimensionsContainerElement, dimensionElement); 457 } 458 459 // create the MEASURES container and add all MEASURE elements according to 460 // the CONFIGURATION passed in. 461 Node measuresContainerElement = XMLLib.newNode("MEASURES"); 462 List<Node> measuresList = XMLLib.getNodes(queryModuleConfigurationNode, ".//MEASURE"); 463 for(int i=0; i < measuresList.size(); i++) { 464 Node nameElement = XMLLib.getNode(measuresList.get(i), "NAME"); 465 Node missingValueElement = XMLLib.getNode(measuresList.get(i), "MISSING_QUERY_RESULT_RECORD_VALUE"); 466 String missingValue = this.defaultMissingValue; 467 if(null != missingValueElement) missingValue = XMLLib.getText(missingValueElement).trim(); 468 469 Node measureElement = XMLLib.newNode("MEASURE"); 470 XMLLib.addNode(measureElement, nameElement); 471 XMLLib.addNode(measureElement, XMLLib.newNode("VALUE", missingValue)); 472 XMLLib.addNode(measuresContainerElement, measureElement); 473 } 474 475 // finally add the DIMENSIONS and MEASURES to a new RECORD element and return. 476 Node recordElement = XMLLib.newNode("RECORD"); 424 for(int i=0; i< dimensionName.length; i++) { 425 addNewMissingValueDimensionElement(dimensionsContainerElement, dimensionName[i], dimensionValue[i]); 426 } 477 427 XMLLib.addNode(recordElement, dimensionsContainerElement); 478 XMLLib.addNode(recordElement, measuresContainerElement); 428 429 // create the MEASURE element. 430 Node measureElement = XMLLib.newNode("MEASURE"); 431 XMLLib.addNode(measureElement, XMLLib.newNode("NAME", measureName)); 432 XMLLib.addNode(measureElement, XMLLib.newNode("VALUE", missingValue)); 433 XMLLib.addNode(recordElement, measureElement); 434 435 // create the ANCILLARY container and add all the supplied AVs. 436 if(null != ancillaryValueNameList) { 437 Node ancillaryValueContainerElement = XMLLib.newNode("ANCILLARY_VALUES"); 438 for(Node ancillaryValueName : ancillaryValueNameList) { 439 Node ancillaryValueElement = XMLLib.newNode("ANCILLARY_VALUE"); 440 XMLLib.addNode(ancillaryValueElement, XMLLib.newNode("NAME", XMLLib.getText(ancillaryValueName))); 441 XMLLib.addNode(ancillaryValueElement, XMLLib.newNode("VALUE", missingValue)); 442 XMLLib.addNode(ancillaryValueContainerElement, ancillaryValueElement); 443 } 444 XMLLib.addNode(recordElement, ancillaryValueContainerElement); 445 } 479 446 480 447 logger.debug(".createNewMissingValueRecordElement - XML Created: " + recordElement.asXML()); … … 482 449 } //-------------------------- End of Method ------------------------------ 483 450 451 452 protected void addNewMissingValueDimensionElement( 453 Node dimensionsContainerElement, 454 String dimensionName, 455 String dimensionValue 456 ) { 457 if(StrLib.isSomething(dimensionName)) { 458 Node dimensionElement = XMLLib.newNode("DIMENSION"); 459 XMLLib.addNode(dimensionElement, XMLLib.newNode("NAME", dimensionName)); 460 XMLLib.addNode(dimensionElement, XMLLib.newNode("VALUE", dimensionValue)); 461 XMLLib.addNode(dimensionsContainerElement, dimensionElement); 462 } 463 } //-------------------------- End of Method ------------------------------ 484 464 485 465 … … 489 469 * @return xpath for the RECORD to be retrieved. 490 470 */ 491 protected String getQueryResultRecordXPath( 492 Map<String, List<String>> dimensionNameValuesMap, 493 String[] dimensionName, 494 int[] dimensionIndex 495 ) { 471 protected String getQueryResultRecordXPath(String[] dimensionName, String[] dimensionValue) { 496 472 StringBuffer xpath = new StringBuffer(); 473 boolean addAnAnd = false; 497 474 xpath.append("/QUERY_MODULE/IBISQ_QUERY_RESULT/RECORDS/RECORD[DIMENSIONS["); 498 for(int i=0; i<dimensionName.length;) { 499 xpath.append("(DIMENSION[NAME='"); 500 xpath.append(dimensionName[i]); 501 xpath.append("' and VALUE/text()='"); 502 List<String> dimensionValues = dimensionNameValuesMap.get(dimensionName[i]); 503 xpath.append(dimensionValues.get(dimensionIndex[i])); 504 xpath.append("'])"); 505 i++; 506 if(i < dimensionName.length) xpath.append(" and "); 475 for(int i=0; i<dimensionName.length; i++) { 476 if((null != dimensionName[i]) && (null != dimensionValue[i])) { 477 if(addAnAnd) xpath.append(" and "); 478 xpath.append("(DIMENSION[NAME='"); 479 xpath.append(dimensionName[i]); 480 xpath.append("' and VALUE/text()='"); 481 xpath.append(dimensionValue[i]); 482 xpath.append("'])"); 483 addAnAnd = true; 484 } 507 485 } 508 486 xpath.append("]]"); … … 512 490 513 491 } //============================ END OF CLASS ================================= 514
Note: See TracChangeset
for help on using the changeset viewer.