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