1 /*
2    Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License, version 2.0,
6    as published by the Free Software Foundation.
7 
8    This program is also distributed with certain software (including
9    but not limited to OpenSSL) that is licensed under separate terms,
10    as designated in a particular file or component or in included license
11    documentation.  The authors of MySQL hereby grant you an additional
12    permission to link the program and your derivative works with the
13    separately licensed software that they have included with MySQL.
14 
15    This program is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18    GNU General Public License, version 2.0, for more details.
19 
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 
25 package com.mysql.clusterj.openjpa;
26 
27 import com.mysql.clusterj.ClusterJFatalInternalException;
28 import com.mysql.clusterj.ClusterJHelper;
29 import com.mysql.clusterj.SessionFactory;
30 
31 import com.mysql.clusterj.core.SessionFactoryImpl;
32 
33 import com.mysql.clusterj.core.spi.DomainTypeHandler;
34 import com.mysql.clusterj.core.spi.DomainTypeHandlerFactory;
35 import com.mysql.clusterj.core.store.Dictionary;
36 import com.mysql.clusterj.core.util.I18NHelper;
37 import com.mysql.clusterj.core.util.Logger;
38 import com.mysql.clusterj.core.util.LoggerFactoryService;
39 
40 import java.lang.reflect.InvocationTargetException;
41 import java.lang.reflect.Method;
42 import java.util.HashMap;
43 import java.util.Map;
44 import java.util.Properties;
45 
46 import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl;
47 import org.apache.openjpa.jdbc.meta.ClassMapping;
48 import org.apache.openjpa.lib.conf.BooleanValue;
49 import org.apache.openjpa.lib.conf.IntValue;
50 import org.apache.openjpa.lib.conf.ProductDerivations;
51 import org.apache.openjpa.lib.conf.StringValue;
52 
53 /**
54  * Default implementation of the {@link NdbOpenJPAConfiguration} interface.
55  * This implementation extends the JDBCConfiguration so both access
56  * to MySQLd via JDBC and access to Ndb via ClusterJ are supported.
57  * Type safety: The return type Map for getCacheMarshallerInstances()
58  * from the type OpenJPAConfigurationImpl needs unchecked conversion
59  * to conform to Map<String,CacheMarshaller> from the type OpenJPAConfiguration
60  */
61 @SuppressWarnings("unchecked")
62 public class NdbOpenJPAConfigurationImpl extends JDBCConfigurationImpl
63     implements NdbOpenJPAConfiguration, com.mysql.clusterj.Constants, DomainTypeHandlerFactory {
64 
65     /** My message translator */
66     static final I18NHelper local = I18NHelper.getInstance(NdbOpenJPAConfigurationImpl.class);
67 
68     /** My logger */
69     static final Logger logger = LoggerFactoryService.getFactory().getInstance(NdbOpenJPAConfigurationImpl.class);
70 
71     public StringValue connectString;
72     public IntValue connectRetries;
73     public IntValue connectDelay;
74     public IntValue connectVerbose;
75     public IntValue connectTimeoutBefore;
76     public IntValue connectTimeoutAfter;
77     public StringValue username;
78     public StringValue password;
79     public StringValue database;
80     public IntValue maxTransactions;
81     public BooleanValue failOnJDBCPath;
82     public SessionFactoryImpl sessionFactory;
83     private final Map<Class<?>, NdbOpenJPADomainTypeHandlerImpl<?>> domainTypeHandlerMap =
84             new HashMap<Class<?>, NdbOpenJPADomainTypeHandlerImpl<?>>();
85 
86     /**
87      * These are to bridge an incompatibility between OpenJPA 1.x and 2.x in the handling of configuration
88      * values. In 1.x, the IntValue.get() method returns int and in 2.x it returns Integer.
89      * Similarly, in 1.x BooleanValue.get() returns boolean and in 2.x it returns Boolean.
90      */
91     static private Method intValueMethod;
92     static private Method booleanValueMethod;
93     static {
94         try {
95             intValueMethod = IntValue.class.getMethod("get", (Class[])null);
96             booleanValueMethod = BooleanValue.class.getMethod("get", (Class[])null);
97         } catch (SecurityException e) {
98             throw new ClusterJFatalInternalException(e);
99         } catch (NoSuchMethodException e) {
100             throw new ClusterJFatalInternalException(e);
101         }
102     }
103 
104     /** Return the int value from a configuration IntValue.
105      *
106      */
getIntValue(IntValue value)107     int getIntValue(IntValue value) {
108         try {
109             return (Integer)intValueMethod.invoke(value);
110         } catch (IllegalArgumentException e) {
111             throw new ClusterJFatalInternalException(e);
112         } catch (IllegalAccessException e) {
113             throw new ClusterJFatalInternalException(e);
114         } catch (InvocationTargetException e) {
115             throw new ClusterJFatalInternalException(e);
116         }
117     }
118     /** Return the boolean value from a configuration BooleanValue.
119      *
120      */
getBooleanValue(BooleanValue value)121     boolean getBooleanValue(BooleanValue value) {
122         try {
123             return (Boolean) booleanValueMethod.invoke(value);
124         } catch (IllegalArgumentException e) {
125             throw new ClusterJFatalInternalException(e);
126         } catch (IllegalAccessException e) {
127             throw new ClusterJFatalInternalException(e);
128         } catch (InvocationTargetException e) {
129             throw new ClusterJFatalInternalException(e);
130         }
131     }
132     /**
133      * Default constructor. Attempts to load default properties.
134      */
NdbOpenJPAConfigurationImpl()135     public NdbOpenJPAConfigurationImpl() {
136         this(true);
137     }
138 
139     /**
140      * Constructor.
141      *
142      * @param loadGlobals whether to attempt to load the global properties
143      */
NdbOpenJPAConfigurationImpl(boolean loadGlobals)144     public NdbOpenJPAConfigurationImpl(boolean loadGlobals) {
145         this(true, loadGlobals);
146     }
147 
148     /**
149      * Constructor.
150      *
151      * @param derivations whether to apply product derivations
152      * @param loadGlobals whether to attempt to load the global properties
153      */
NdbOpenJPAConfigurationImpl(boolean derivations, boolean loadGlobals)154     public NdbOpenJPAConfigurationImpl(boolean derivations, boolean loadGlobals) {
155         super(false, false);
156 
157         connectString = addString("ndb.connectString");
158         connectRetries = addInt("ndb.connectRetries");
159         connectRetries.set(4);
160         connectDelay = addInt("ndb.connectDelay");
161         connectDelay.set(5);
162         connectVerbose = addInt("ndb.connectVerbose");
163         connectVerbose.set(0);
164         connectTimeoutBefore = addInt("ndb.connectTimeoutBefore");
165         connectTimeoutBefore.set(30);
166         connectTimeoutAfter = addInt("ndb.connectTimeoutAfter");
167         connectTimeoutAfter.set(20);
168         username = addString("ndb.username");
169         password = addString("ndb.password");
170         database = addString("ndb.database");
171         database.set("test");
172         maxTransactions = addInt("ndb.maxTransactions");
173         maxTransactions.set(1024);
174         failOnJDBCPath = addBoolean("ndb.failOnJDBCPath");
175         failOnJDBCPath.set(false);
176 
177         sessionFactory = null;
178 
179         if (derivations)
180             ProductDerivations.beforeConfigurationLoad(this);
181         if (loadGlobals)
182             loadGlobals();
183     }
184 
185     /**
186      * Copy constructor
187      */
NdbOpenJPAConfigurationImpl(NdbOpenJPAConfiguration conf)188     public NdbOpenJPAConfigurationImpl(NdbOpenJPAConfiguration conf) {
189         this(true, false);
190         if (conf != null)
191             fromProperties(conf.toProperties(false));
192     }
193 
getConnectString()194     public String getConnectString() {
195         return connectString.get();
196     }
197 
setConnectString(String value)198     public void setConnectString(String value) {
199         connectString.set(value);
200     }
201 
getConnectRetries()202     public int getConnectRetries() {
203         return getIntValue(connectRetries);
204     }
205 
setConnectRetries(int value)206     public void setConnectRetries(int value) {
207         connectRetries.set(value);
208     }
209 
getConnectDelay()210     public int getConnectDelay() {
211         return getIntValue(connectDelay);
212     }
213 
setConnectDelay(int value)214     public void setConnectDelay(int value) {
215         connectDelay.set(value);
216     }
217 
getConnectVerbose()218     public int getConnectVerbose() {
219         return getIntValue(connectVerbose);
220     }
221 
setConnectVerbose(int value)222     public void setConnectVerbose(int value) {
223         connectVerbose.set(value);
224     }
225 
getConnectTimeoutBefore()226     public int getConnectTimeoutBefore() {
227         return getIntValue(connectTimeoutBefore);
228     }
229 
setConnectTimeoutBefore(int value)230     public void setConnectTimeoutBefore(int value) {
231         connectTimeoutBefore.set(value);
232     }
233 
getConnectTimeoutAfter()234     public int getConnectTimeoutAfter() {
235         return getIntValue(connectTimeoutAfter);
236     }
237 
setConnectTimeoutAfter(int value)238     public void setConnectTimeoutAfter(int value) {
239         connectTimeoutAfter.set(value);
240     }
241 
getUsername()242     public String getUsername() {
243         return username.getString();
244     }
245 
setUsername(String value)246     public void setUsername(String value) {
247         username.setString(value);
248     }
249 
getPassword()250     public String getPassword() {
251         return password.getString();
252     }
253 
setPassword(String value)254     public void setPassword(String value) {
255         password.setString(value);
256     }
257 
getDatabase()258     public String getDatabase() {
259         return database.getString();
260     }
261 
setDatabase(String value)262     public void setDatabase(String value) {
263         database.setString(value);
264     }
265 
getMaxTransactions()266     public int getMaxTransactions() {
267         return getIntValue(maxTransactions);
268     }
269 
setMaxTransactions(int value)270     public void setMaxTransactions(int value) {
271         maxTransactions.set(value);
272     }
273 
getFailOnJDBCPath()274     public boolean getFailOnJDBCPath() {
275         return getBooleanValue(failOnJDBCPath);
276     }
277 
setFailOnJDBCPath(boolean value)278     public void setFailOnJDBCPath(boolean value) {
279         failOnJDBCPath.set(value);
280     }
281 
getSessionFactory()282     public SessionFactoryImpl getSessionFactory() {
283         if (sessionFactory == null) {
284             sessionFactory = createSessionFactory();
285         }
286         return (SessionFactoryImpl) sessionFactory;
287     }
288 
setSessionFactory(SessionFactory value)289     public void setSessionFactory(SessionFactory value) {
290         sessionFactory = (SessionFactoryImpl) value;
291     }
292 
createSessionFactory()293     public SessionFactoryImpl createSessionFactory() {
294         // require connectString to be specified
295         if (connectString.get() == null) {
296             throw new IllegalStateException(
297                     local.message("ERR_Missing_Connect_String"));
298         }
299         // map OpenJPA properties to ClusterJ properties
300         Properties props = new Properties();
301         props.put(PROPERTY_CLUSTER_CONNECTSTRING,
302                 connectString.get());
303         props.put(PROPERTY_CLUSTER_CONNECT_RETRIES,
304                 connectRetries.getString());
305         props.put(PROPERTY_CLUSTER_CONNECT_DELAY,
306                 connectDelay.getString());
307         props.put(PROPERTY_CLUSTER_CONNECT_VERBOSE,
308                 connectVerbose.getString());
309         props.put(PROPERTY_CLUSTER_CONNECT_TIMEOUT_BEFORE,
310                 connectTimeoutBefore.getString());
311         props.put(PROPERTY_CLUSTER_CONNECT_TIMEOUT_AFTER,
312                 connectTimeoutAfter.getString());
313         props.put(PROPERTY_CLUSTER_DATABASE,
314                 database.getString());
315         props.put(PROPERTY_CLUSTER_MAX_TRANSACTIONS,
316                 maxTransactions.getString());
317         SessionFactoryImpl factory = (SessionFactoryImpl)
318                 ClusterJHelper.getSessionFactory(props);
319         factory.setDomainTypeHandlerFactory(this);
320         return factory;
321     }
322 
323     /** Get the domain type handler for this class mapping. A cached handler is
324      * returned if possible. Synchronize on the class-to-handler map.
325      * @param cmd the openjpa class mapping
326      * @return the domain type handler
327      */
getDomainTypeHandler( ClassMapping cmd, Dictionary dictionary)328     public NdbOpenJPADomainTypeHandlerImpl<?> getDomainTypeHandler(
329             ClassMapping cmd, Dictionary dictionary) {
330         NdbOpenJPADomainTypeHandlerImpl<?> result;
331         Class<?> domainClass = cmd.getDescribedType();
332         synchronized(domainTypeHandlerMap) {
333             result = domainTypeHandlerMap.get(domainClass);
334             if (result == null) {
335                 if (logger.isDebugEnabled()) logger.debug("domain class: " + domainClass.getName());
336                 result = createDomainTypeHandler(cmd, dictionary);
337                 domainTypeHandlerMap.put(domainClass, result);
338                 result.initializeRelations();
339                 logger.info("New domain type " + result.getName()
340                         + (result.isSupportedType()?" is supported.":
341                             " is not known to be supported because " + result.getReasons()));
342             }
343         }
344         return result;
345     }
346 
347     /** Create a new domain type handler for the class mapping.
348      *
349      * @param classMapping the openjpa class mapping
350      * @return the domain type handler
351      */
createDomainTypeHandler( ClassMapping classMapping, Dictionary dictionary)352     private NdbOpenJPADomainTypeHandlerImpl<?> createDomainTypeHandler(
353             ClassMapping classMapping, Dictionary dictionary) {
354         return new NdbOpenJPADomainTypeHandlerImpl<Object>(dictionary, classMapping, this);
355     }
356 
357     /**
358      * Free the data sources.
359      */
360     @Override
preClose()361     protected void preClose() {
362         if (sessionFactory != null) {
363             sessionFactory.close();
364         }
365         super.preClose();
366     }
367 
368     @Override
isInvalidProperty(String propName)369     protected boolean isInvalidProperty(String propName) {
370         if (super.isInvalidProperty(propName))
371             return true;
372 
373         // handle openjpa.ndb.SomeMisspelledProperty, but not
374         // openjpa.someotherimplementation.SomeProperty
375         String lowerCasePropName = propName.toLowerCase();
376         String[] prefixes = ProductDerivations.getConfigurationPrefixes();
377         for (int i = 0; i < prefixes.length; i++)
378             if (lowerCasePropName.startsWith(prefixes[i] + ".ndb"))
379                 return true;
380         return false;
381     }
382 
383     /** Get the domain type handler for the class. This method is called by the query
384      * handler when performing a clusterj query for an openjpa entity. The class
385      * must have already been registered via the openjpa clusterj path.
386      */
createDomainTypeHandler( Class<T> domainClass, Dictionary dictionary)387     public <T> DomainTypeHandler<T> createDomainTypeHandler(
388             Class<T> domainClass, Dictionary dictionary) {
389         DomainTypeHandler<T> result = (DomainTypeHandler<T>) domainTypeHandlerMap.get(domainClass);
390         if (result == null) {
391             throw new ClusterJFatalInternalException(
392                     local.message("ERR_Create_Domain_Type_Handler_First", domainClass.getName()));
393         }
394         return result;
395     }
396 
397 }
398