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) 2006-2013 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.server;
011
012import mondrian.olap.*;
013import mondrian.rolap.RolapSchema;
014import mondrian.tui.XmlaSupport;
015import mondrian.util.Pair;
016import mondrian.xmla.DataSourcesConfig;
017
018import org.apache.log4j.Logger;
019
020import java.util.HashMap;
021import java.util.Map;
022import java.util.Timer;
023import java.util.TimerTask;
024import java.util.concurrent.TimeUnit;
025
026/**
027 * Implementation of
028 * {@link RepositoryContentFinder} that
029 * periodically reloads the content of the repository.
030 *
031 * <p>The updates are performed by a background thread.
032 * It is important to call {@link DynamicContentFinder#shutdown()}
033 * once this object can be disposed of.
034 *
035 * @author Thiyagu Ajit
036 * @author Luc Boudreau
037 * @author Julian Hyde
038 */
039public class DynamicContentFinder
040    extends UrlRepositoryContentFinder
041{
042    protected String lastDataSourcesConfigString;
043
044    protected DataSourcesConfig.DataSources dataSources;
045
046    private static final Logger LOGGER =
047        Logger.getLogger(MondrianServer.class);
048
049    private final Timer timer;
050
051    /**
052     * Creates a DynamicContentFinder.
053     * @param dataSourcesConfigUrl URL of repository
054     */
055    public DynamicContentFinder(
056        String dataSourcesConfigUrl)
057    {
058        super(dataSourcesConfigUrl);
059        reloadDataSources();
060        timer = Util.newTimer(
061            "mondrian.server.DynamicContentFinder$timer",
062            true);
063        final Pair<Long, TimeUnit> interval =
064            Util.parseInterval(
065                String.valueOf(
066                    MondrianProperties.instance()
067                        .XmlaSchemaRefreshInterval.get()),
068                TimeUnit.MILLISECONDS);
069        final long period = interval.right.toMillis(interval.left);
070        timer.schedule(
071            new TimerTask() {
072                public void run() {
073                    reloadDataSources();
074                }
075            },
076            period,
077            period);
078    }
079
080    /**
081     * Cleans up all background updating jobs.
082     */
083    public void shutdown() {
084        super.shutdown();
085        timer.cancel();
086    }
087
088    /**
089     * Checks for updates to datasources content, flushes obsolete catalogs.
090     */
091    public synchronized void reloadDataSources() {
092        try {
093            String dataSourcesConfigString = getContent();
094            if (!hasDataSourcesContentChanged(dataSourcesConfigString)) {
095                return;
096            }
097            DataSourcesConfig.DataSources newDataSources =
098                XmlaSupport.parseDataSources(
099                    dataSourcesConfigString, LOGGER);
100            if (newDataSources == null) {
101                return;
102            }
103            flushObsoleteCatalogs(newDataSources);
104            this.dataSources = newDataSources;
105            this.lastDataSourcesConfigString = dataSourcesConfigString;
106        } catch (Exception e) {
107            throw Util.newError(
108                e,
109                "Failed to parse data sources config '" + url + "'");
110        }
111    }
112
113    protected boolean hasDataSourcesContentChanged(
114        String dataSourcesConfigString)
115    {
116        return dataSourcesConfigString != null
117            && !dataSourcesConfigString.equals(
118                this.lastDataSourcesConfigString);
119    }
120
121    private Map<
122        String,
123        Pair<DataSourcesConfig.DataSource, DataSourcesConfig.Catalog>>
124    createCatalogMap(
125        DataSourcesConfig.DataSources newDataSources)
126    {
127        Map<
128            String,
129            Pair<DataSourcesConfig.DataSource, DataSourcesConfig.Catalog>>
130            newDatasourceCatalogNames =
131                new HashMap<String,
132                    Pair<DataSourcesConfig.DataSource,
133                        DataSourcesConfig.Catalog>>();
134        for (DataSourcesConfig.DataSource dataSource
135            : newDataSources.dataSources)
136        {
137            for (DataSourcesConfig.Catalog catalog
138                : dataSource.catalogs.catalogs)
139            {
140                if (catalog.dataSourceInfo == null) {
141                    catalog.dataSourceInfo = dataSource.dataSourceInfo;
142                }
143                newDatasourceCatalogNames.put(
144                    catalog.name, Pair.of(dataSource, catalog));
145            }
146        }
147        return newDatasourceCatalogNames;
148    }
149
150    public synchronized void flushObsoleteCatalogs(
151        DataSourcesConfig.DataSources newDataSources)
152    {
153        if (dataSources == null) {
154            return;
155        }
156
157        Map<String,
158            Pair<DataSourcesConfig.DataSource,
159                DataSourcesConfig.Catalog>> newDatasourceCatalogs =
160            createCatalogMap(newDataSources);
161
162        for (DataSourcesConfig.DataSource oldDataSource
163            : dataSources.dataSources)
164        {
165            for (DataSourcesConfig.Catalog oldCatalog
166                : oldDataSource.catalogs.catalogs)
167            {
168                Pair<DataSourcesConfig.DataSource, DataSourcesConfig.Catalog>
169                    pair =
170                        newDatasourceCatalogs.get(oldCatalog.name);
171                if (pair == null
172                    || !areCatalogsEqual(
173                        oldDataSource, oldCatalog, pair.left, pair.right))
174                {
175                    flushCatalog(oldCatalog.name);
176                }
177            }
178        }
179    }
180
181    protected void flushCatalog(String catalogName) {
182        for (RolapSchema schema : RolapSchema.getRolapSchemas()) {
183            if (schema.getName().equals(catalogName)) {
184                schema.getInternalConnection().getCacheControl(null)
185                    .flushSchema(schema);
186            }
187        }
188    }
189
190    public static boolean areCatalogsEqual(
191        DataSourcesConfig.DataSource dataSource1,
192        DataSourcesConfig.Catalog catalog1,
193        DataSourcesConfig.DataSource dataSource2,
194        DataSourcesConfig.Catalog catalog2)
195    {
196        return
197            Util.equals(dsi(dataSource1, catalog1), dsi(dataSource2, catalog2))
198            && catalog1.name.equals(catalog2.name)
199            && catalog1.definition.equals(catalog2.definition);
200    }
201
202    public static String dsi(
203        DataSourcesConfig.DataSource dataSource,
204        DataSourcesConfig.Catalog catalog)
205    {
206        return catalog.dataSourceInfo == null && dataSource != null
207            ? dataSource.dataSourceInfo
208            : catalog.dataSourceInfo;
209    }
210}
211
212// End DynamicContentFinder.java