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 &lt;init&gt;(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