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