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) 2009-2013 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.spi; 011 012import mondrian.olap.Util; 013import mondrian.spi.impl.JdbcDialectFactory; 014import mondrian.spi.impl.JdbcDialectImpl; 015import mondrian.util.ClassResolver; 016import mondrian.util.ServiceDiscovery; 017 018import java.lang.reflect.*; 019import java.sql.Connection; 020import java.sql.SQLException; 021import java.util.*; 022import javax.sql.DataSource; 023 024/** 025 * Manages {@link mondrian.spi.Dialect} and {@link mondrian.spi.DialectFactory} 026 * objects. 027 * 028 * @author jhyde 029 * @since Jan 13, 2009 030 */ 031public abstract class DialectManager { 032 /** 033 * The singleton instance of the implementation class. 034 */ 035 private static final DialectManagerImpl IMPL = new DialectManagerImpl(); 036 037 /** 038 * DialectManager is not instantiable. 039 */ 040 private DialectManager() { 041 throw new IllegalArgumentException(); 042 } 043 044 /** 045 * Registers a DialectFactory. 046 * 047 * @param factory Dialect factory 048 */ 049 public static void register(DialectFactory factory) { 050 IMPL.register(factory); 051 } 052 053 /** 054 * Registers a Dialect class. 055 * 056 * @param dialectClass Dialect class 057 */ 058 public static void register(Class<? extends Dialect> dialectClass) { 059 IMPL.register(dialectClass); 060 } 061 062 /** 063 * Creates a Dialect from a JDBC connection. 064 * 065 * <p>If the dialect cannot handle this connection, throws. Never returns 066 * null. 067 * 068 * @param dataSource Data source 069 * 070 * @param connection JDBC connection 071 * 072 * @return dialect for this connection 073 * 074 * @throws RuntimeException if underlying systems give an error, 075 * or if cannot create dialect 076 */ 077 public static Dialect createDialect( 078 DataSource dataSource, 079 Connection connection) 080 { 081 return createDialect(dataSource, connection, null); 082 } 083 084 /** 085 * Creates a Dialect from a JDBC connection, optionally specifying 086 * the name of the dialect class. 087 * 088 * <p>If the dialect cannot handle this connection, throws. Never returns 089 * null. 090 * 091 * @param dataSource Data source 092 * 093 * @param connection JDBC connection 094 * 095 * @param dialectClassName Name of class that implements {@link Dialect}, 096 * or null 097 * 098 * @return dialect for this connection 099 * 100 * @throws RuntimeException if underlying systems give an error, 101 * or if cannot create dialect 102 */ 103 public static Dialect createDialect( 104 DataSource dataSource, 105 Connection connection, 106 String dialectClassName) 107 { 108 return IMPL.createDialect(dataSource, connection, dialectClassName); 109 } 110 111 /** 112 * Creates a factory that calls a public constructor of a dialect class. 113 * 114 * @param dialectClass Dialect class 115 * @return Factory, or null if the class has no suitable constructor. 116 */ 117 static DialectFactory createFactoryForDialect( 118 Class<? extends Dialect> dialectClass) 119 { 120 // If there is a public, static member called FACTORY, 121 // use it. 122 for (Field field : dialectClass.getFields()) { 123 if (Modifier.isPublic(field.getModifiers()) 124 && Modifier.isStatic(field.getModifiers()) 125 && field.getName().equals("FACTORY") 126 && DialectFactory.class.isAssignableFrom(field.getType())) 127 { 128 try { 129 final DialectFactory 130 factory = (DialectFactory) field.get(null); 131 if (factory != null) { 132 return factory; 133 } 134 } catch (IllegalAccessException e) { 135 throw Util.newError( 136 e, 137 "Error while accessing field " + field); 138 } 139 } 140 } 141 // Otherwise, create a factory that calls the 142 // 'public <init>(Connection)' constructor. 143 try { 144 final Constructor<? extends Dialect> constructor = 145 dialectClass.getConstructor(Connection.class); 146 if (Modifier.isPublic(constructor.getModifiers())) { 147 return new ConstructorDialectFactory(constructor); 148 } 149 } catch (NoSuchMethodException e) { 150 // ignore 151 } 152 153 // No suitable constructor or factory. 154 return null; 155 } 156 157 /** 158 * Implementation class for {@link mondrian.spi.DialectManager}. 159 * 160 * <p><code>DialectManagerImpl</code> has a non-static method for each 161 * public static method in <code>DialectManager</code>. 162 */ 163 private static class DialectManagerImpl { 164 private final ChainDialectFactory registeredFactory; 165 private final DialectFactory factory; 166 167 /** 168 * Creates a DialectManagerImpl. 169 * 170 * <p>Loads all dialects that can be found on the classpath according to 171 * the JAR service provider specification. (See 172 * {@link mondrian.util.ServiceDiscovery} for more details.) 173 */ 174 DialectManagerImpl() { 175 final List<DialectFactory> 176 list = new ArrayList<DialectFactory>(); 177 final List<Class<Dialect>> dialectClasses = 178 ServiceDiscovery.forClass(Dialect.class).getImplementor(); 179 for (Class<Dialect> dialectClass : dialectClasses) { 180 DialectFactory factory = 181 createFactoryForDialect(dialectClass); 182 if (factory != null) { 183 list.add(factory); 184 } 185 } 186 registeredFactory = new ChainDialectFactory(list); 187 188 final DialectFactory fallbackFactory = 189 new DialectFactory() { 190 public Dialect createDialect( 191 DataSource dataSource, 192 Connection connection) 193 { 194 // If connection is null, create a temporary connection 195 // and recursively call this method. 196 if (connection == null) { 197 return JdbcDialectFactory.createDialectHelper( 198 this, dataSource); 199 } 200 try { 201 return new JdbcDialectImpl(connection); 202 } catch (SQLException e) { 203 throw Util.newError( 204 e, 205 "Error while creating a generic dialect for" 206 + " JDBC connection" + connection); 207 } 208 } 209 }; 210 // The system dialect factory first walks the chain of registered 211 // dialect factories (registered implicitly based on service 212 // discovery, or explicitly by calling register), then uses the JDBC 213 // dialect factory as a fallback. 214 // 215 // It caches based on data source. 216 factory = 217 new CachingDialectFactory( 218 new ChainDialectFactory( 219 Arrays.asList( 220 registeredFactory, 221 fallbackFactory))); 222 } 223 224 /** 225 * Implements {@link DialectManager#register(DialectFactory)}. 226 * 227 * @param factory Dialect factory 228 */ 229 synchronized void register(DialectFactory factory) { 230 if (factory == null) { 231 throw new IllegalArgumentException(); 232 } 233 registeredFactory.dialectFactoryList.add(factory); 234 } 235 236 /** 237 * Implements {@link DialectManager#register(Class)}. 238 * 239 * @param dialectClass Dialect class 240 */ 241 synchronized void register(Class<? extends Dialect> dialectClass) { 242 if (dialectClass == null) { 243 throw new IllegalArgumentException(); 244 } 245 register(createFactoryForDialect(dialectClass)); 246 } 247 248 /** 249 * Implements {@link DialectManager#createDialect(javax.sql.DataSource,java.sql.Connection)}. 250 * 251 * <p>The method synchronizes on a singleton class, so prevents two 252 * threads from accessing any dialect factory simultaneously. 253 * 254 * @param dataSource Data source 255 * @param connection Connection 256 * @return Dialect, never null 257 */ 258 synchronized Dialect createDialect( 259 DataSource dataSource, 260 Connection connection, 261 String dialectClassName) 262 { 263 if (dataSource == null && connection == null) { 264 throw new IllegalArgumentException(); 265 } 266 final DialectFactory factory; 267 if (dialectClassName != null) { 268 // Instantiate explicit dialect class. 269 try { 270 Class<? extends Dialect> dialectClass = 271 ClassResolver.INSTANCE.forName(dialectClassName, true) 272 .asSubclass(Dialect.class); 273 factory = createFactoryForDialect(dialectClass); 274 } catch (ClassCastException e) { 275 throw new RuntimeException( 276 "Dialect class " + dialectClassName 277 + " does not implement interface " + Dialect.class); 278 } catch (Exception e) { 279 throw new RuntimeException( 280 "Cannot instantiate dialect class '" 281 + dialectClassName + "'", 282 e); 283 } 284 } else { 285 // Use factory of dialects registered in services file. 286 factory = this.factory; 287 } 288 final Dialect dialect = 289 factory.createDialect(dataSource, connection); 290 if (dialect == null) { 291 throw Util.newError( 292 "Cannot create dialect for JDBC connection" + connection); 293 } 294 return dialect; 295 } 296 } 297 298 /** 299 * Implementation of {@link DialectFactory} that tries to 300 * create a Dialect using a succession of underlying factories. 301 */ 302 static class ChainDialectFactory implements DialectFactory { 303 private final List<DialectFactory> dialectFactoryList; 304 305 /** 306 * Creates a ChainDialectFactory. 307 * 308 * @param dialectFactoryList List of underlying factories 309 */ 310 ChainDialectFactory(List<DialectFactory> dialectFactoryList) { 311 this.dialectFactoryList = dialectFactoryList; 312 } 313 314 public Dialect createDialect( 315 DataSource dataSource, 316 Connection connection) 317 { 318 // Make sure that there is a connection. 319 // If connection is null, create a temporary connection and 320 // recursively call this method. 321 // It's more efficient to create the connection here than to 322 // require each chained factory to create a connection. 323 if (connection == null) { 324 return JdbcDialectFactory.createDialectHelper(this, dataSource); 325 } 326 327 for (DialectFactory factory : dialectFactoryList) { 328 // REVIEW: If createDialect throws, should we carry on? 329 final Dialect dialect = 330 factory.createDialect( 331 dataSource, 332 connection); 333 if (dialect != null) { 334 return dialect; 335 } 336 } 337 return null; 338 } 339 } 340 341 /** 342 * Implementation of {@link DialectFactory} that calls 343 * a class's {@code public <init>(Connection connection)} constructor. 344 */ 345 static class ConstructorDialectFactory implements DialectFactory { 346 private final Constructor<? extends Dialect> constructor; 347 348 /** 349 * Creates a ConstructorDialectFactory. 350 * 351 * @param constructor Constructor 352 */ 353 ConstructorDialectFactory( 354 Constructor<? extends Dialect> constructor) 355 { 356 assert constructor != null; 357 assert constructor.getParameterTypes().length == 1; 358 assert constructor.getParameterTypes()[0] 359 == java.sql.Connection.class; 360 this.constructor = constructor; 361 } 362 363 public Dialect createDialect( 364 DataSource dataSource, 365 Connection connection) 366 { 367 // If connection is null, create a temporary connection 368 // and recursively call this method. 369 if (connection == null) { 370 return JdbcDialectFactory.createDialectHelper( 371 this, dataSource); 372 } 373 374 // Connection is not null. Invoke the constructor. 375 try { 376 return constructor.newInstance(connection); 377 } catch (InstantiationException e) { 378 throw Util.newError( 379 e, 380 "Error while instantiating dialect of class " 381 + constructor.getClass()); 382 } catch (IllegalAccessException e) { 383 throw Util.newError( 384 e, 385 "Error while instantiating dialect of class " 386 + constructor.getClass()); 387 } catch (InvocationTargetException e) { 388 throw Util.newError( 389 e, 390 "Error while instantiating dialect of class " 391 + constructor.getClass()); 392 } 393 } 394 } 395 396 /** 397 * Implementation of {@link mondrian.spi.DialectFactory} that caches 398 * dialects based on data source. 399 * 400 * @see mondrian.spi.Dialect#allowsDialectSharing() 401 */ 402 static class CachingDialectFactory implements DialectFactory { 403 private final DialectFactory factory; 404 private final Map<DataSource, Dialect> dataSourceDialectMap = 405 new WeakHashMap<DataSource, Dialect>(); 406 407 /** 408 * Creates a CachingDialectFactory. 409 * 410 * @param factory Underlying factory 411 */ 412 CachingDialectFactory(DialectFactory factory) { 413 this.factory = factory; 414 } 415 416 public Dialect createDialect( 417 DataSource dataSource, 418 Connection connection) 419 { 420 if (dataSource != null) { 421 Dialect dialect = dataSourceDialectMap.get(dataSource); 422 if (dialect != null) { 423 return dialect; 424 } 425 } 426 427 // No cached dialect. Get a dialect from the underlying factory. 428 final Dialect dialect = 429 factory.createDialect(dataSource, connection); 430 431 // Put the dialect into the cache if it is sharable. 432 if (dialect != null 433 && dataSource != null 434 && dialect.allowsDialectSharing()) 435 { 436 dataSourceDialectMap.put(dataSource, dialect); 437 } 438 return dialect; 439 } 440 } 441} 442 443// End DialectManager.java