001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2005-2005 Julian Hyde 008// Copyright (C) 2005-2009 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap.aggmatcher; 012 013import mondrian.olap.MondrianProperties; 014import mondrian.recorder.*; 015import mondrian.resource.MondrianResource; 016import mondrian.rolap.RolapStar; 017 018import org.apache.log4j.Logger; 019 020import org.eigenbase.util.property.Property; 021import org.eigenbase.util.property.Trigger; 022import org.eigenbase.xom.*; 023 024import java.io.*; 025import java.net.MalformedURLException; 026import java.net.URL; 027import java.util.HashMap; 028import java.util.Map; 029 030/** 031 * Container for the default aggregate recognition rules. 032 * It is generated by parsing the default rule xml information found 033 * in the {@link MondrianProperties#AggregateRules} value which normally is 034 * a resource in the jar file (but can be a url). 035 * 036 * <p>It is a singleton since it is used to recognize tables independent of 037 * database connection (each {@link mondrian.rolap.RolapSchema} uses the same 038 * instance). 039 * 040 * @author Richard M. Emberson 041 */ 042public class DefaultRules { 043 044 private static final Logger LOGGER = Logger.getLogger(DefaultRules.class); 045 046 private static final MondrianResource mres = MondrianResource.instance(); 047 /** 048 * There is a single instance of the {@link DefaultRecognizer} and the 049 * {@link DefaultRules} class is a container of that instance. 050 */ 051 public static synchronized DefaultRules getInstance() { 052 if (instance == null) { 053 InputStream inStream = getAggRuleInputStream(); 054 if (inStream == null) { 055 return null; 056 } 057 058 DefaultDef.AggRules defs = makeAggRules(inStream); 059 060 // validate the DefaultDef.AggRules object 061 ListRecorder reclists = new ListRecorder(); 062 try { 063 defs.validate(reclists); 064 } catch (RecorderException e) { 065 // ignore 066 } 067 068 reclists.logWarningMessage(LOGGER); 069 reclists.logErrorMessage(LOGGER); 070 071 if (reclists.hasErrors()) { 072 reclists.throwRTException(); 073 } 074 075 076 // make sure the tag name exists 077 String tag = MondrianProperties.instance().AggregateRuleTag.get(); 078 DefaultDef.AggRule aggrule = defs.getAggRule(tag); 079 if (aggrule == null) { 080 throw mres.MissingDefaultAggRule.ex(tag); 081 } 082 083 DefaultRules rules = new DefaultRules(defs); 084 rules.setTag(tag); 085 instance = rules; 086 } 087 return instance; 088 } 089 090 private static InputStream getAggRuleInputStream() { 091 String aggRules = MondrianProperties.instance().AggregateRules.get(); 092 093 InputStream inStream = DefaultRules.class.getResourceAsStream(aggRules); 094 if (inStream == null) { 095 try { 096 URL url = new URL(aggRules); 097 inStream = url.openStream(); 098 } catch (MalformedURLException e) { 099 // ignore 100 } catch (IOException e) { 101 // ignore 102 } 103 } 104 if (inStream == null) { 105 LOGGER.warn(mres.CouldNotLoadDefaultAggregateRules.str(aggRules)); 106 } 107 return inStream; 108 } 109 private static DefaultRules instance = null; 110 111 static { 112 // When the value of the AggregateRules property is changed, force 113 // system to reload the DefaultRules. 114 // There is no need to provide equals/hashCode methods for this 115 // Trigger since it is a singleton and is never removed. 116 Trigger trigger = 117 new Trigger() { 118 public boolean isPersistent() { 119 return true; 120 } 121 public int phase() { 122 return Trigger.PRIMARY_PHASE; 123 } 124 public void execute(Property property, String value) { 125 synchronized (DefaultRules.class) { 126 DefaultRules oldInstance = DefaultRules.instance; 127 DefaultRules.instance = null; 128 129 DefaultRules newInstance = null; 130 Exception ex = null; 131 try { 132 newInstance = DefaultRules.getInstance(); 133 } catch (Exception e) { 134 ex = e; 135 } 136 if (ex != null) { 137 DefaultRules.instance = oldInstance; 138 139 throw new Trigger.VetoRT(ex); 140 141 } else if (newInstance == null) { 142 DefaultRules.instance = oldInstance; 143 144 String msg = 145 mres.FailedCreateNewDefaultAggregateRules.str( 146 property.getPath(), 147 value); 148 throw new Trigger.VetoRT(msg); 149 150 } else { 151 instance = newInstance; 152 } 153 } 154 } 155 }; 156 157 final MondrianProperties properties = MondrianProperties.instance(); 158 properties.AggregateRules.addTrigger(trigger); 159 properties.AggregateRuleTag.addTrigger(trigger); 160 } 161 162 protected static DefaultDef.AggRules makeAggRules(final File file) { 163 DOMWrapper def = makeDOMWrapper(file); 164 try { 165 DefaultDef.AggRules rules = new DefaultDef.AggRules(def); 166 return rules; 167 } catch (XOMException e) { 168 throw mres.AggRuleParse.ex(file.getName(), e); 169 } 170 } 171 172 protected static DefaultDef.AggRules makeAggRules(final URL url) { 173 DOMWrapper def = makeDOMWrapper(url); 174 try { 175 DefaultDef.AggRules rules = new DefaultDef.AggRules(def); 176 return rules; 177 } catch (XOMException e) { 178 throw mres.AggRuleParse.ex(url.toString(), e); 179 } 180 } 181 182 protected static DefaultDef.AggRules makeAggRules( 183 final InputStream inStream) 184 { 185 DOMWrapper def = makeDOMWrapper(inStream); 186 try { 187 DefaultDef.AggRules rules = new DefaultDef.AggRules(def); 188 return rules; 189 } catch (XOMException e) { 190 throw mres.AggRuleParse.ex("InputStream", e); 191 } 192 } 193 194 protected static DefaultDef.AggRules makeAggRules( 195 final String text, 196 final String name) 197 { 198 DOMWrapper def = makeDOMWrapper(text, name); 199 try { 200 DefaultDef.AggRules rules = new DefaultDef.AggRules(def); 201 return rules; 202 } catch (XOMException e) { 203 throw mres.AggRuleParse.ex(name, e); 204 } 205 } 206 207 protected static DOMWrapper makeDOMWrapper(final File file) { 208 try { 209 return makeDOMWrapper(file.toURL()); 210 } catch (MalformedURLException e) { 211 throw mres.AggRuleParse.ex(file.getName(), e); 212 } 213 } 214 215 protected static DOMWrapper makeDOMWrapper(final URL url) { 216 try { 217 final Parser xmlParser = XOMUtil.createDefaultParser(); 218 DOMWrapper def = xmlParser.parse(url); 219 return def; 220 } catch (XOMException e) { 221 throw mres.AggRuleParse.ex(url.toString(), e); 222 } 223 } 224 225 protected static DOMWrapper makeDOMWrapper(final InputStream inStream) { 226 try { 227 final Parser xmlParser = XOMUtil.createDefaultParser(); 228 DOMWrapper def = xmlParser.parse(inStream); 229 return def; 230 } catch (XOMException e) { 231 throw mres.AggRuleParse.ex("InputStream", e); 232 } 233 } 234 235 protected static DOMWrapper makeDOMWrapper( 236 final String text, 237 final String name) 238 { 239 try { 240 final Parser xmlParser = XOMUtil.createDefaultParser(); 241 DOMWrapper def = xmlParser.parse(text); 242 return def; 243 } catch (XOMException e) { 244 throw mres.AggRuleParse.ex(name, e); 245 } 246 } 247 248 249 private final DefaultDef.AggRules rules; 250 private final Map<String, Recognizer.Matcher> factToPattern; 251 private final Map<String, Recognizer.Matcher> foreignKeyMatcherMap; 252 private Recognizer.Matcher ignoreMatcherMap; 253 private Recognizer.Matcher factCountMatcher; 254 private String tag; 255 256 private DefaultRules(final DefaultDef.AggRules rules) { 257 this.rules = rules; 258 this.factToPattern = new HashMap<String, Recognizer.Matcher>(); 259 this.foreignKeyMatcherMap = new HashMap<String, Recognizer.Matcher>(); 260 this.tag = 261 MondrianProperties.instance().AggregateRuleTag.getDefaultValue(); 262 } 263 264 public void validate(MessageRecorder msgRecorder) { 265 rules.validate(msgRecorder); 266 } 267 268 /** 269 * Sets the name (tag) of this rule. 270 * 271 * @param tag 272 */ 273 private void setTag(final String tag) { 274 this.tag = tag; 275 } 276 277 /** 278 * Gets the tag of this rule (this is the value of the 279 * {@link MondrianProperties#AggregateRuleTag} property). 280 */ 281 public String getTag() { 282 return this.tag; 283 } 284 285 286 /** 287 * Returns the {@link mondrian.rolap.aggmatcher.DefaultDef.AggRule} whose 288 * tag equals this rule's tag. 289 */ 290 public DefaultDef.AggRule getAggRule() { 291 return getAggRule(getTag()); 292 } 293 294 /** 295 * Returns the {@link mondrian.rolap.aggmatcher.DefaultDef.AggRule} whose 296 * tag equals the parameter tag, or null if not found. 297 * 298 * @param tag 299 * @return the AggRule with tag value equal to tag parameter, or null. 300 */ 301 public DefaultDef.AggRule getAggRule(final String tag) { 302 return this.rules.getAggRule(tag); 303 } 304 305 /** 306 * Gets the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for this 307 * tableName. 308 * 309 * @param tableName 310 */ 311 public Recognizer.Matcher getTableMatcher(final String tableName) { 312 Recognizer.Matcher matcher = factToPattern.get(tableName); 313 if (matcher == null) { 314 // get default AggRule 315 DefaultDef.AggRule rule = getAggRule(); 316 DefaultDef.TableMatch tableMatch = rule.getTableMatch(); 317 matcher = tableMatch.getMatcher(tableName); 318 factToPattern.put(tableName, matcher); 319 } 320 return matcher; 321 } 322 323 /** 324 * Gets the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for the 325 * fact count column. 326 */ 327 public Recognizer.Matcher getIgnoreMatcher() { 328 if (ignoreMatcherMap == null) { 329 // get default AggRule 330 DefaultDef.AggRule rule = getAggRule(); 331 DefaultDef.IgnoreMap ignoreMatch = rule.getIgnoreMap(); 332 if (ignoreMatch == null) { 333 ignoreMatcherMap = new Recognizer.Matcher() { 334 public boolean matches(String name) { 335 return false; 336 } 337 }; 338 } else { 339 ignoreMatcherMap = ignoreMatch.getMatcher(); 340 } 341 } 342 return ignoreMatcherMap; 343 } 344 345 /** 346 * Gets the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for 347 * columns that should be ignored. 348 * 349 * @return the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for 350 * columns that should be ignored. 351 */ 352 public Recognizer.Matcher getFactCountMatcher() { 353 if (factCountMatcher == null) { 354 // get default AggRule 355 DefaultDef.AggRule rule = getAggRule(); 356 DefaultDef.FactCountMatch factCountMatch = 357 rule.getFactCountMatch(); 358 factCountMatcher = factCountMatch.getMatcher(); 359 } 360 return factCountMatcher; 361 } 362 363 /** 364 * Gets the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for this 365 * foreign key column name. 366 * 367 * @param foreignKeyName Name of a foreign key column 368 */ 369 public Recognizer.Matcher getForeignKeyMatcher(String foreignKeyName) { 370 Recognizer.Matcher matcher = 371 foreignKeyMatcherMap.get(foreignKeyName); 372 if (matcher == null) { 373 // get default AggRule 374 DefaultDef.AggRule rule = getAggRule(); 375 DefaultDef.ForeignKeyMatch foreignKeyMatch = 376 rule.getForeignKeyMatch(); 377 matcher = foreignKeyMatch.getMatcher(foreignKeyName); 378 foreignKeyMatcherMap.put(foreignKeyName, matcher); 379 } 380 return matcher; 381 } 382 383 /** 384 * Returns true if this candidate aggregate table name "matches" the 385 * factTableName. 386 * 387 * @param factTableName Name of the fact table 388 * @param name candidate aggregate table name 389 */ 390 public boolean matchesTableName( 391 final String factTableName, 392 final String name) 393 { 394 Recognizer.Matcher matcher = getTableMatcher(factTableName); 395 return matcher.matches(name); 396 } 397 398 /** 399 * Creates a {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for the 400 * given measure name (symbolic name), column name and aggregate name 401 * (sum, count, etc.). 402 */ 403 public Recognizer.Matcher getMeasureMatcher( 404 final String measureName, 405 final String measureColumnName, 406 final String aggregateName) 407 { 408 DefaultDef.AggRule rule = getAggRule(); 409 Recognizer.Matcher matcher = 410 rule.getMeasureMap().getMatcher( 411 measureName, 412 measureColumnName, 413 aggregateName); 414 return matcher; 415 } 416 417 /** 418 * Gets a {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for a given 419 * level's hierarchy's name, level name and column name. 420 */ 421 public Recognizer.Matcher getLevelMatcher( 422 final String usagePrefix, 423 final String hierarchyName, 424 final String levelName, 425 final String levelColumnName) 426 { 427 DefaultDef.AggRule rule = getAggRule(); 428 Recognizer.Matcher matcher = 429 rule.getLevelMap().getMatcher( 430 usagePrefix, 431 hierarchyName, 432 levelName, 433 levelColumnName); 434 return matcher; 435 } 436 437 /** 438 * Uses the {@link DefaultRecognizer} Recognizer to determine if the 439 * given aggTable's columns all match upto the dbFactTable's columns (where 440 * present) making the column usages as a result. 441 */ 442 public boolean columnsOK( 443 final RolapStar star, 444 final JdbcSchema.Table dbFactTable, 445 final JdbcSchema.Table aggTable, 446 final MessageRecorder msgRecorder) 447 { 448 Recognizer cb = new DefaultRecognizer( 449 this, 450 star, 451 dbFactTable, 452 aggTable, 453 msgRecorder); 454 return cb.check(); 455 } 456} 457 458// End DefaultRules.java