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) 2001-2005 Julian Hyde and others 008// Copyright (C) 2005-2012 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.olap.Util; 014import mondrian.resource.MondrianResource; 015import mondrian.rolap.aggmatcher.JdbcSchema; 016import mondrian.spi.DynamicSchemaProcessor; 017import mondrian.util.*; 018 019import org.apache.log4j.Logger; 020 021import java.io.IOException; 022import java.lang.ref.*; 023import java.lang.reflect.Constructor; 024import java.util.*; 025 026import javax.sql.DataSource; 027 028/** 029 * A collection of schemas, identified by their connection properties 030 * (catalog name, JDBC URL, and so forth). 031 * 032 * <p>To lookup a schema, call 033 * <code>RolapSchemaPool.{@link #instance}().{@link #get}</code>.</p> 034 */ 035class RolapSchemaPool { 036 static final Logger LOGGER = Logger.getLogger(RolapSchemaPool.class); 037 038 private static final RolapSchemaPool INSTANCE = new RolapSchemaPool(); 039 040 private final Map<SchemaKey, ExpiringReference<RolapSchema>> 041 mapKeyToSchema = 042 new HashMap<SchemaKey, ExpiringReference<RolapSchema>>(); 043 044 // REVIEW: This map is now considered unsafe. If two schemas have identical 045 // metadata but a different underlying database connection, we should not 046 // share a cache. Since SchemaContentKey is now a hash of the schema 047 // definition, this field can probably be removed. 048 private final Map<ByteString, ExpiringReference<RolapSchema>> 049 mapMd5ToSchema = 050 new HashMap<ByteString, ExpiringReference<RolapSchema>>(); 051 052 private RolapSchemaPool() { 053 } 054 055 static RolapSchemaPool instance() { 056 return INSTANCE; 057 } 058 059 synchronized RolapSchema get( 060 final String catalogUrl, 061 final String connectionKey, 062 final String jdbcUser, 063 final String dataSourceStr, 064 final Util.PropertyList connectInfo) 065 { 066 return get( 067 catalogUrl, 068 connectionKey, 069 jdbcUser, 070 dataSourceStr, 071 null, 072 connectInfo); 073 } 074 075 synchronized RolapSchema get( 076 final String catalogUrl, 077 final DataSource dataSource, 078 final Util.PropertyList connectInfo) 079 { 080 return get( 081 catalogUrl, 082 null, 083 null, 084 null, 085 dataSource, 086 connectInfo); 087 } 088 089 private RolapSchema get( 090 final String catalogUrl, 091 final String connectionKey, 092 final String jdbcUser, 093 final String dataSourceStr, 094 final DataSource dataSource, 095 final Util.PropertyList connectInfo) 096 { 097 final String connectionUuidStr = connectInfo.get( 098 RolapConnectionProperties.JdbcConnectionUuid.name()); 099 final boolean useSchemaPool = 100 Boolean.parseBoolean( 101 connectInfo.get( 102 RolapConnectionProperties.UseSchemaPool.name(), 103 "true")); 104 final String pinSchemaTimeout = 105 connectInfo.get( 106 RolapConnectionProperties.PinSchemaTimeout.name(), 107 "-1s"); 108 final boolean useContentChecksum = 109 Boolean.parseBoolean( 110 connectInfo.get( 111 RolapConnectionProperties.UseContentChecksum.name())); 112 if (LOGGER.isDebugEnabled()) { 113 LOGGER.debug( 114 "get: catalog=" + catalogUrl 115 + ", connectionKey=" + connectionKey 116 + ", jdbcUser=" + jdbcUser 117 + ", dataSourceStr=" + dataSourceStr 118 + ", dataSource=" + dataSource 119 + ", jdbcConnectionUuid=" + connectionUuidStr 120 + ", useSchemaPool=" + useSchemaPool 121 + ", useContentChecksum=" + useContentChecksum 122 + ", map-size=" + mapKeyToSchema.size() 123 + ", md5-map-size=" + mapMd5ToSchema.size()); 124 } 125 final ConnectionKey connectionKey1 = 126 ConnectionKey.create( 127 connectionUuidStr, 128 dataSource, 129 catalogUrl, 130 connectionKey, 131 jdbcUser, 132 dataSourceStr); 133 134 final String catalogStr = getSchemaContent(connectInfo, catalogUrl); 135 final SchemaContentKey schemaContentKey = 136 SchemaContentKey.create(connectInfo, catalogUrl, catalogStr); 137 final SchemaKey key = 138 new SchemaKey( 139 schemaContentKey, 140 connectionKey1); 141 142 // Use the schema pool unless "UseSchemaPool" is explicitly false. 143 RolapSchema schema = null; 144 if (!useSchemaPool) { 145 schema = 146 new RolapSchema( 147 key, 148 null, 149 catalogUrl, 150 catalogStr, 151 connectInfo, 152 dataSource); 153 if (LOGGER.isDebugEnabled()) { 154 LOGGER.debug( 155 "create (no pool): schema-name=" + schema.getName() 156 + ", schema-id=" 157 + Integer.toHexString(System.identityHashCode(schema))); 158 } 159 return schema; 160 } 161 162 if (useContentChecksum) { 163 final ByteString md5Bytes = 164 new ByteString(Util.digestMd5(catalogStr)); 165 final ExpiringReference<RolapSchema> ref = 166 mapMd5ToSchema.get(md5Bytes); 167 if (LOGGER.isDebugEnabled()) { 168 LOGGER.debug( 169 "get(key=" + key 170 + ") returned " + toString(ref)); 171 } 172 173 if (ref != null) { 174 schema = ref.get(pinSchemaTimeout); 175 if (schema == null) { 176 // clear out the reference since schema is null 177 mapKeyToSchema.remove(key); 178 mapMd5ToSchema.remove(md5Bytes); 179 } 180 } 181 182 if (schema == null) { 183 schema = new RolapSchema( 184 key, 185 md5Bytes, 186 catalogUrl, 187 catalogStr, 188 connectInfo, 189 dataSource); 190 if (LOGGER.isDebugEnabled()) { 191 LOGGER.debug( 192 "create: schema-name=" + schema.getName() 193 + ", schema-id=" + System.identityHashCode(schema)); 194 } 195 putSchema(schema, md5Bytes, pinSchemaTimeout); 196 } 197 return schema; 198 } 199 200 ExpiringReference<RolapSchema> ref = mapKeyToSchema.get(key); 201 if (LOGGER.isDebugEnabled()) { 202 LOGGER.debug( 203 "get(key=" + key 204 + ") returned " + toString(ref)); 205 } 206 if (ref != null) { 207 schema = ref.get(pinSchemaTimeout); 208 if (schema == null) { 209 mapKeyToSchema.remove(key); 210 } 211 } 212 213 if (schema == null) { 214 schema = new RolapSchema( 215 key, 216 null, 217 catalogUrl, 218 catalogStr, 219 connectInfo, 220 dataSource); 221 if (LOGGER.isDebugEnabled()) { 222 LOGGER.debug("create: " + schema); 223 } 224 putSchema(schema, null, pinSchemaTimeout); 225 } 226 227 return schema; 228 } 229 230 private void putSchema( 231 final RolapSchema schema, 232 final ByteString md5Bytes, 233 final String pinTimeout) 234 { 235 final ExpiringReference<RolapSchema> reference = 236 new ExpiringReference<RolapSchema>( 237 schema, pinTimeout); 238 if (md5Bytes != null) { 239 mapMd5ToSchema.put(md5Bytes, reference); 240 } 241 mapKeyToSchema.put(schema.key, reference); 242 243 if (LOGGER.isDebugEnabled()) { 244 LOGGER.debug( 245 "put: schema=" + schema 246 + ", key=" + schema.key 247 + ", checksum=" + md5Bytes 248 + ", map-size=" + mapKeyToSchema.size() 249 + ", md5-map-size=" + mapMd5ToSchema.size()); 250 } 251 } 252 253 private static String getSchemaContent( 254 final Util.PropertyList connectInfo, 255 final String catalogUrl) 256 { 257 // We will return the first of the following: 258 // 1. CatalogContent property if set 259 // 2. DynamicSchemaProcessor#processSchema if set 260 // 3. Util.readVirtualFileAsString(catalogUrl) 261 262 String catalogStr = connectInfo.get( 263 RolapConnectionProperties.CatalogContent.name()); 264 265 if (Util.isEmpty(catalogStr)) { 266 if (Util.isEmpty(catalogUrl)) { 267 throw MondrianResource.instance() 268 .ConnectStringMandatoryProperties.ex( 269 RolapConnectionProperties.Catalog.name(), 270 RolapConnectionProperties.CatalogContent.name()); 271 } 272 // check for a DynamicSchemaProcessor 273 String dynProcName = connectInfo.get( 274 RolapConnectionProperties.DynamicSchemaProcessor.name()); 275 if (!Util.isEmpty(dynProcName)) { 276 catalogStr = 277 processDynamicSchema( 278 dynProcName, catalogUrl, connectInfo); 279 } 280 281 if (Util.isEmpty(catalogStr)) { 282 // read schema from file 283 try { 284 catalogStr = Util.readVirtualFileAsString(catalogUrl); 285 } catch (IOException e) { 286 throw Util.newError( 287 e, 288 "loading schema from url " + catalogUrl); 289 } 290 } 291 } 292 293 return catalogStr; 294 } 295 296 private static String processDynamicSchema( 297 final String dynProcName, 298 final String catalogUrl, 299 final Util.PropertyList connectInfo) 300 { 301 if (RolapSchema.LOGGER.isDebugEnabled()) { 302 RolapSchema.LOGGER.debug( 303 "Pool.get: create schema \"" + catalogUrl 304 + "\" using dynamic processor"); 305 } 306 try { 307 final DynamicSchemaProcessor dynProc = 308 ClassResolver.INSTANCE.instantiateSafe(dynProcName); 309 return dynProc.processSchema(catalogUrl, connectInfo); 310 } catch (Exception e) { 311 throw Util.newError( 312 e, 313 "loading DynamicSchemaProcessor " + dynProcName); 314 } 315 } 316 317 synchronized void remove( 318 final String catalogUrl, 319 final String connectionKey, 320 final String jdbcUser, 321 final String dataSourceStr) 322 { 323 final SchemaContentKey schemaContentKey = 324 SchemaContentKey.create( 325 new Util.PropertyList(), 326 catalogUrl, 327 null); 328 final ConnectionKey connectionUuid = 329 ConnectionKey.create( 330 null, 331 null, 332 catalogUrl, 333 connectionKey, 334 jdbcUser, 335 dataSourceStr); 336 final SchemaKey key = 337 new SchemaKey(schemaContentKey, connectionUuid); 338 if (RolapSchema.LOGGER.isDebugEnabled()) { 339 RolapSchema.LOGGER.debug( 340 "Pool.remove: schema \"" + catalogUrl 341 + "\" and datasource string \"" + dataSourceStr + "\""); 342 } 343 remove(key); 344 } 345 346 synchronized void remove( 347 final String catalogUrl, 348 final DataSource dataSource) 349 { 350 final SchemaContentKey schemaContentKey = 351 SchemaContentKey.create( 352 new Util.PropertyList(), 353 catalogUrl, 354 null); 355 final ConnectionKey connectionKey = 356 ConnectionKey.create( 357 null, 358 dataSource, 359 catalogUrl, 360 null, 361 null, 362 null); 363 final SchemaKey key = 364 new SchemaKey(schemaContentKey, connectionKey); 365 if (RolapSchema.LOGGER.isDebugEnabled()) { 366 RolapSchema.LOGGER.debug( 367 "Pool.remove: schema \"" + catalogUrl 368 + "\" and datasource object"); 369 } 370 remove(key); 371 } 372 373 synchronized void remove(RolapSchema schema) { 374 if (schema != null) { 375 if (RolapSchema.LOGGER.isDebugEnabled()) { 376 RolapSchema.LOGGER.debug( 377 "Pool.remove: schema \"" + schema.getName() 378 + "\" and datasource object"); 379 } 380 remove(schema.key); 381 } 382 } 383 384 private void remove(SchemaKey key) { 385 Reference<RolapSchema> ref = mapKeyToSchema.get(key); 386 if (ref != null) { 387 RolapSchema schema = ref.get(); 388 if (schema != null) { 389 mapMd5ToSchema.remove(schema.getChecksum()); 390 schema.finalCleanUp(); 391 } 392 } 393 mapKeyToSchema.remove(key); 394 } 395 396 synchronized void clear() { 397 if (RolapSchema.LOGGER.isDebugEnabled()) { 398 RolapSchema.LOGGER.debug("Pool.clear: clearing all RolapSchemas"); 399 } 400 401 for (Reference<RolapSchema> ref : mapKeyToSchema.values()) { 402 if (ref != null) { 403 RolapSchema schema = ref.get(); 404 if (schema != null) { 405 schema.finalCleanUp(); 406 } 407 } 408 } 409 mapKeyToSchema.clear(); 410 mapMd5ToSchema.clear(); 411 JdbcSchema.clearAllDBs(); 412 } 413 414 /** 415 * Returns a list of schemas in this pool. 416 * 417 * @return List of schemas in this pool 418 */ 419 synchronized List<RolapSchema> getRolapSchemas() { 420 List<RolapSchema> list = new ArrayList<RolapSchema>(); 421 for (RolapSchema schema 422 : Util.GcIterator.over(mapKeyToSchema.values())) 423 { 424 list.add(schema); 425 } 426 return list; 427 } 428 429 synchronized boolean contains(RolapSchema rolapSchema) { 430 return mapKeyToSchema.containsKey(rolapSchema.key); 431 } 432 433 private static <T> String toString(Reference<T> ref) { 434 if (ref == null) { 435 return "null"; 436 } else { 437 T t = ref.get(); 438 if (t == null) { 439 return "ref(null)"; 440 } else { 441 return "ref(" + t 442 + ", id=" + Integer.toHexString(System.identityHashCode(t)) 443 + ")"; 444 } 445 } 446 } 447} 448 449// End RolapSchemaPool.java