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