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