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) 2010-2012 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.server;
011
012import mondrian.olap.*;
013import mondrian.olap4j.MondrianOlap4jDriver;
014import mondrian.rolap.*;
015import mondrian.spi.CatalogLocator;
016import mondrian.tui.XmlaSupport;
017import mondrian.util.*;
018import mondrian.xmla.DataSourcesConfig;
019
020import org.apache.log4j.Logger;
021
022import org.olap4j.*;
023import org.olap4j.impl.Olap4jUtil;
024
025import java.sql.SQLException;
026import java.util.*;
027import java.util.concurrent.*;
028
029/**
030 * Implementation of {@link mondrian.server.Repository} that reads
031 * from a {@code datasources.xml} file.
032 *
033 * <p>Note that for legacy reasons, the datasources.xml file's root
034 * element is called DataSource whereas the olap4j standard calls them
035 * Databases. This is why those two concepts are linked, as in
036 * {@link FileRepository#getDatabaseNames(RolapConnection)} for example.
037 *
038 * @author Julian Hyde, Luc Boudreau
039 */
040public class FileRepository implements Repository {
041    private static final Object SERVER_INFO_LOCK = new Object();
042    private final RepositoryContentFinder repositoryContentFinder;
043
044    private static final Logger LOGGER = Logger.getLogger(MondrianServer.class);
045
046    private static final ScheduledExecutorService executorService =
047        Util.getScheduledExecutorService(
048            1,
049            "mondrian.server.DynamicContentFinder$executorService");
050
051    private ServerInfo serverInfo;
052    private final ScheduledFuture<?> scheduledFuture;
053    private final CatalogLocator locator;
054
055    public FileRepository(
056        RepositoryContentFinder repositoryContentFinder,
057        CatalogLocator locator)
058    {
059        this.repositoryContentFinder = repositoryContentFinder;
060        this.locator = locator;
061        assert repositoryContentFinder != null;
062        final Pair<Long, TimeUnit> interval =
063            Util.parseInterval(
064                String.valueOf(
065                    MondrianProperties.instance()
066                        .XmlaSchemaRefreshInterval.get()),
067                TimeUnit.MILLISECONDS);
068        scheduledFuture = executorService.scheduleWithFixedDelay(
069            new Runnable() {
070                public void run() {
071                    synchronized (SERVER_INFO_LOCK) {
072                        serverInfo = null;
073                    }
074                }
075            },
076            0,
077            interval.left,
078            interval.right);
079    }
080
081    public List<Map<String, Object>> getDatabases(
082        RolapConnection connection)
083    {
084        final List<Map<String, Object>> propsList =
085            new ArrayList<Map<String, Object>>();
086        for (DatabaseInfo dsInfo : getServerInfo().datasourceMap.values()) {
087            propsList.add(dsInfo.properties);
088        }
089        return propsList;
090    }
091
092    public OlapConnection getConnection(
093        MondrianServer server,
094        String databaseName,
095        String catalogName,
096        String roleName,
097        Properties props)
098        throws SQLException
099    {
100        final ServerInfo serverInfo = getServerInfo();
101        final DatabaseInfo datasourceInfo;
102        if (databaseName == null) {
103            if (serverInfo.datasourceMap.size() == 0) {
104                throw new OlapException(
105                    "No databases configured on this server");
106            }
107            datasourceInfo =
108                serverInfo
109                    .datasourceMap
110                    .values()
111                    .iterator()
112                    .next();
113        } else {
114            datasourceInfo =
115                serverInfo.datasourceMap.get(databaseName);
116        }
117        if (datasourceInfo == null) {
118            throw Util.newError("Unknown database '" + databaseName + "'");
119        }
120
121        final CatalogInfo catalogInfo;
122        if (catalogName == null) {
123            if (datasourceInfo.catalogMap.size() == 0) {
124                throw new OlapException(
125                    "No catalogs in the database named "
126                    + datasourceInfo.name);
127            }
128            catalogInfo =
129                datasourceInfo
130                    .catalogMap
131                    .values()
132                    .iterator()
133                    .next();
134        } else {
135            catalogInfo =
136                datasourceInfo.catalogMap.get(catalogName);
137        }
138        if (catalogInfo == null) {
139            throw Util.newError("Unknown catalog '" + catalogName + "'");
140        }
141        String connectString = catalogInfo.olap4jConnectString;
142
143        // Save the server for the duration of the call to 'getConnection'.
144        final LockBox.Entry entry =
145            MondrianServerRegistry.INSTANCE.lockBox.register(server);
146
147        final Properties properties = new Properties();
148        properties.setProperty(
149            RolapConnectionProperties.Instance.name(),
150            entry.getMoniker());
151        if (roleName != null) {
152            properties.setProperty(
153                RolapConnectionProperties.Role.name(),
154                roleName);
155        }
156        properties.putAll(props);
157        // Make sure we load the Mondrian driver into
158        // the ClassLoader.
159        try {
160          ClassResolver.INSTANCE.forName(
161              MondrianOlap4jDriver.class.getName(), true);
162        } catch (ClassNotFoundException e) {
163            throw new OlapException("Cannot find mondrian olap4j driver.");
164        }
165        // Now create the connection
166        final java.sql.Connection connection =
167            java.sql.DriverManager.getConnection(connectString, properties);
168        return ((OlapWrapper) connection).unwrap(OlapConnection.class);
169    }
170
171    public void shutdown() {
172        scheduledFuture.cancel(false);
173        repositoryContentFinder.shutdown();
174    }
175
176    private ServerInfo getServerInfo() {
177        synchronized (SERVER_INFO_LOCK) {
178            if (this.serverInfo != null) {
179                return this.serverInfo;
180            }
181
182            final String content = repositoryContentFinder.getContent();
183            DataSourcesConfig.DataSources xmlDataSources =
184                XmlaSupport.parseDataSources(content, LOGGER);
185            ServerInfo serverInfo = new ServerInfo();
186
187            for (DataSourcesConfig.DataSource xmlDataSource
188                : xmlDataSources.dataSources)
189            {
190                final Map<String, Object> dsPropsMap =
191                    Olap4jUtil.<String, Object>mapOf(
192                        "DataSourceName",
193                        xmlDataSource.getDataSourceName(),
194                        "DataSourceDescription",
195                        xmlDataSource.getDataSourceDescription(),
196                        "URL",
197                        xmlDataSource.getURL(),
198                        "DataSourceInfo",
199                        xmlDataSource.getDataSourceName(),
200                        "ProviderName",
201                        xmlDataSource.getProviderName(),
202                        "ProviderType",
203                        xmlDataSource.providerType,
204                        "AuthenticationMode",
205                        xmlDataSource.authenticationMode);
206                final DatabaseInfo databaseInfo =
207                    new DatabaseInfo(
208                        xmlDataSource.name,
209                        dsPropsMap);
210                serverInfo.datasourceMap.put(
211                    xmlDataSource.name,
212                    databaseInfo);
213                for (DataSourcesConfig.Catalog xmlCatalog
214                    : xmlDataSource.catalogs.catalogs)
215                {
216                    if (databaseInfo.catalogMap.containsKey(xmlCatalog.name)) {
217                        throw Util.newError(
218                            "more than one DataSource object has name '"
219                            + xmlCatalog.name + "'");
220                    }
221                    String connectString =
222                        xmlCatalog.dataSourceInfo != null
223                            ? xmlCatalog.dataSourceInfo
224                            : xmlDataSource.dataSourceInfo;
225                    // Check if the catalog is part of the connect
226                    // string. If not, add it.
227                    final Util.PropertyList connectProperties =
228                        Util.parseConnectString(connectString);
229                    if (connectProperties
230                        .get(RolapConnectionProperties.Catalog.name()) == null)
231                    {
232                        connectString +=
233                            ";"
234                            + RolapConnectionProperties.Catalog.name()
235                            + "="
236                            + xmlCatalog.definition;
237                    }
238                    final CatalogInfo catalogInfo =
239                        new CatalogInfo(
240                            xmlCatalog.name,
241                            connectString,
242                            locator);
243                    databaseInfo.catalogMap.put(
244                        xmlCatalog.name,
245                        catalogInfo);
246                }
247            }
248            this.serverInfo = serverInfo;
249            return serverInfo;
250        }
251    }
252
253    public List<String> getCatalogNames(
254        RolapConnection connection,
255        String databaseName)
256    {
257        return new ArrayList<String>(
258            getServerInfo().datasourceMap.get(databaseName)
259                .catalogMap.keySet());
260    }
261
262    public List<String> getDatabaseNames(
263        RolapConnection connection)
264    {
265        return new ArrayList<String>(
266            getServerInfo().datasourceMap.keySet());
267    }
268
269    public Map<String, RolapSchema> getRolapSchemas(
270        RolapConnection connection,
271        String databaseName,
272        String catalogName)
273    {
274        final RolapSchema schema =
275            getServerInfo()
276                .datasourceMap.get(databaseName)
277                    .catalogMap.get(catalogName)
278                        .getRolapSchema();
279        return Collections.singletonMap(
280            schema.getName(),
281            schema);
282    }
283
284    private static class ServerInfo {
285        private Map<String, DatabaseInfo> datasourceMap =
286            new HashMap<String, DatabaseInfo>();
287    }
288
289    private static class DatabaseInfo {
290        private final String name;
291        private final Map<String, Object> properties;
292        private Map<String, CatalogInfo> catalogMap =
293            new HashMap<String, CatalogInfo>();
294
295        DatabaseInfo(String name, Map<String, Object> properties) {
296            this.name = name;
297            this.properties = properties;
298        }
299    }
300
301    private static class CatalogInfo {
302        private final String connectString;
303        private RolapSchema rolapSchema; // populated on demand
304        private final String olap4jConnectString;
305        private final CatalogLocator locator;
306
307        CatalogInfo(
308            String name,
309            String connectString,
310            CatalogLocator locator)
311        {
312            this.connectString = connectString;
313            this.locator = locator;
314            this.olap4jConnectString =
315                connectString.startsWith("jdbc:")
316                    ? connectString
317                    : "jdbc:mondrian:" + connectString;
318        }
319
320        private RolapSchema getRolapSchema() {
321            if (rolapSchema == null) {
322                RolapConnection rolapConnection = null;
323                try {
324                    rolapConnection =
325                        (RolapConnection)
326                            DriverManager.getConnection(
327                                connectString, this.locator);
328                    rolapSchema = rolapConnection.getSchema();
329                } finally {
330                    if (rolapConnection != null) {
331                        rolapConnection.close();
332                    }
333                }
334            }
335            return rolapSchema;
336        }
337    }
338}
339
340// End FileRepository.java