2 Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 package com.mysql.clusterj.core.metadata;
20 import com.mysql.clusterj.core.spi.DomainFieldHandler;
21 import com.mysql.clusterj.core.spi.ValueHandler;
22 import com.mysql.clusterj.ClusterJException;
23 import com.mysql.clusterj.ClusterJFatalInternalException;
24 import com.mysql.clusterj.ClusterJUserException;
25 import com.mysql.clusterj.DynamicObject;
26 import com.mysql.clusterj.ColumnMetadata;
27 import com.mysql.clusterj.DynamicObjectDelegate;
29 import com.mysql.clusterj.annotation.PersistenceCapable;
31 import com.mysql.clusterj.core.CacheManager;
33 import com.mysql.clusterj.core.store.Column;
34 import com.mysql.clusterj.core.store.Index;
35 import com.mysql.clusterj.core.store.Dictionary;
36 import com.mysql.clusterj.core.store.Operation;
40 import java.lang.reflect.Constructor;
41 import java.lang.reflect.InvocationHandler;
42 import java.lang.reflect.InvocationTargetException;
43 import java.lang.reflect.Method;
44 import java.lang.reflect.Proxy;
46 import java.util.ArrayList;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.List;
53 /** This instance manages a persistence-capable type.
54 * Currently, only interfaces can be persistence-capable. Persistent
55 * properties consist of a pair of bean-pattern methods for which the
56 * get method returns the same type as the parameter of the
57 * similarly-named set method.
58 * @param T the class of the persistence-capable type
60 public class DomainTypeHandlerImpl<T> extends AbstractDomainTypeHandlerImpl<T> {
62 /** The domain class. */
65 /** Dynamic class indicator */
66 boolean dynamic = false;
68 /** The methods of the properties. */
69 private Map<String, Method> unmatchedGetMethods = new HashMap<String, Method>();
70 private Map<String, Method> unmatchedSetMethods = new HashMap<String, Method>();
72 /** The Proxy class for the Domain Class. */
73 protected Class<T> proxyClass;
75 /** The constructor for the Proxy class. */
78 /** The PersistenceCapable annotation for this class. */
79 PersistenceCapable persistenceCapable;
81 /** Helper parameter for constructor. */
82 protected static final Class<?>[] invocationHandlerClassArray =
83 new Class[]{InvocationHandler.class};
85 /** Initialize DomainTypeHandler for a class.
87 * @param cls the domain class (this is the only class
88 * known to the rest of the implementation)
89 * @param dictionary NdbDictionary instance used for metadata access
91 @SuppressWarnings( "unchecked" )
92 public DomainTypeHandlerImpl(Class<T> cls, Dictionary dictionary) {
94 this.name = cls.getName();
95 this.dynamic = DynamicObject.class.isAssignableFrom(cls);
97 // Dynamic object has a handler but no proxy
98 this.tableName = getTableNameForDynamicObject((Class<DynamicObject>)cls);
100 // Create a proxy class for the domain class
101 proxyClass = (Class<T>)Proxy.getProxyClass(
102 cls.getClassLoader(), new Class[]{cls});
103 ctor = getConstructorForInvocationHandler (proxyClass);
104 persistenceCapable = cls.getAnnotation(PersistenceCapable.class);
105 if (persistenceCapable == null) {
106 throw new ClusterJUserException(local.message(
107 "ERR_No_Persistence_Capable_Annotation", name));
109 this.tableName = persistenceCapable.table();
111 this.table = getTable(dictionary);
113 throw new ClusterJUserException(local.message("ERR_Get_NdbTable", name, tableName));
115 if (logger.isDebugEnabled()) logger.debug("Found Table for " + tableName);
117 // the id field handlers will be initialized via registerPrimaryKeyColumn
118 this.primaryKeyColumnNames = table.getPrimaryKeyColumnNames();
119 this.numberOfIdFields = primaryKeyColumnNames.length;
120 this.idFieldHandlers = new DomainFieldHandlerImpl[numberOfIdFields];
121 this.idFieldNumbers = new int[numberOfIdFields];
123 // the partition key field handlers will be initialized via registerPrimaryKeyColumn
124 this.partitionKeyColumnNames = table.getPartitionKeyColumnNames();
125 this.numberOfPartitionKeyColumns = partitionKeyColumnNames.length;
126 this.partitionKeyFieldHandlers = new DomainFieldHandlerImpl[numberOfPartitionKeyColumns];
128 // Process indexes for the table. There might not be a field associated with the index.
129 // The first entry in indexHandlerImpls is for the mandatory hash primary key,
130 // which is not really an index but is treated as an index by query.
131 Index primaryIndex = dictionary.getIndex("PRIMARY$KEY", tableName, "PRIMARY");
132 IndexHandlerImpl primaryIndexHandler =
133 new IndexHandlerImpl(this, dictionary, primaryIndex, primaryKeyColumnNames);
134 indexHandlerImpls.add(primaryIndexHandler);
136 String[] indexNames = table.getIndexNames();
137 for (String indexName: indexNames) {
138 // the index alias is the name as known by the user (without the $unique suffix)
139 String indexAlias = removeUniqueSuffix(indexName);
140 Index index = dictionary.getIndex(indexName, tableName, indexAlias);
141 String[] columnNames = index.getColumnNames();
142 IndexHandlerImpl imd = new IndexHandlerImpl(this, dictionary, index, columnNames);
143 indexHandlerImpls.add(imd);
147 // for each column in the database, create a field
148 List<String> fieldNameList = new ArrayList<String>();
149 for (String columnName: table.getColumnNames()) {
150 Column storeColumn = table.getColumn(columnName);
151 DomainFieldHandlerImpl domainFieldHandler = null;
153 new DomainFieldHandlerImpl(this, table, numberOfFields++, storeColumn);
154 String fieldName = domainFieldHandler.getName();
155 fieldNameList.add(fieldName);
156 fieldNameToNumber.put(domainFieldHandler.getName(), domainFieldHandler.getFieldNumber());
157 persistentFieldHandlers.add(domainFieldHandler);
158 if (!storeColumn.isPrimaryKey()) {
159 nonPKFieldHandlers.add(domainFieldHandler);
162 fieldNames = fieldNameList.toArray(new String[fieldNameList.size()]);
164 // Iterate the fields (names and types based on get/set methods) in the class
165 List<String> fieldNameList = new ArrayList<String>();
166 Method[] methods = cls.getMethods();
167 for (Method method: methods) {
168 // remember get methods
169 String methodName = method.getName();
170 String name = convertMethodName(methodName);
171 Class type = getType(method);
172 DomainFieldHandlerImpl domainFieldHandler = null;
173 if (methodName.startsWith("get")) {
174 Method unmatched = unmatchedSetMethods.get(name);
175 if (unmatched == null) {
176 // get is first of the pair; put it into the unmatched map
177 unmatchedGetMethods.put(name, method);
179 // found the potential match
180 if (getType(unmatched).equals(type)) {
181 // method names and types match
182 unmatchedSetMethods.remove(name);
183 domainFieldHandler = new DomainFieldHandlerImpl(this, table,
184 numberOfFields++, name, type, method, unmatched);
186 // both unmatched because of type mismatch
187 unmatchedGetMethods.put(name, method);
190 } else if (methodName.startsWith("set")) {
191 Method unmatched = unmatchedGetMethods.get(name);
192 if (unmatched == null) {
193 // set is first of the pair; put it into the unmatched map
194 unmatchedSetMethods.put(name, method);
196 // found the potential match
197 if (getType(unmatched).equals(type)) {
198 // method names and types match
199 unmatchedGetMethods.remove(name);
200 domainFieldHandler = new DomainFieldHandlerImpl(this, table,
201 numberOfFields++, name, type, unmatched, method);
203 // both unmatched because of type mismatch
204 unmatchedSetMethods.put(name, method);
208 if (domainFieldHandler != null) {
209 // found matching methods
210 // set up field name to number map
211 String fieldName = domainFieldHandler.getName();
212 fieldNameList.add(fieldName);
213 fieldNameToNumber.put(domainFieldHandler.getName(), domainFieldHandler.getFieldNumber());
214 // put field into either persistent or not persistent list
215 if (domainFieldHandler.isPersistent()) {
216 persistentFieldHandlers.add(domainFieldHandler);
217 if (!domainFieldHandler.isPrimaryKey()) {
218 nonPKFieldHandlers.add(domainFieldHandler);
221 if (domainFieldHandler.isPrimitive()) {
222 primitiveFieldHandlers.add(domainFieldHandler);
226 fieldNames = fieldNameList.toArray(new String[fieldNameList.size()]);
227 // done with methods; if anything in unmatched we have a problem
228 if ((!unmatchedGetMethods.isEmpty()) || (!unmatchedSetMethods.isEmpty())) {
229 throw new ClusterJUserException(
230 local.message("ERR_Unmatched_Methods",
231 unmatchedGetMethods, unmatchedSetMethods));
235 // Check that all index columnNames have corresponding fields
236 // indexes without fields will be unusable for query
237 for (IndexHandlerImpl indexHandler:indexHandlerImpls) {
238 indexHandler.assertAllColumnsHaveFields();
241 if (logger.isDebugEnabled()) {
242 logger.debug(toString());
243 logger.debug("DomainTypeHandlerImpl " + name + "Indices " + indexHandlerImpls);
247 protected <O extends DynamicObject> String getTableNameForDynamicObject(Class<O> cls) {
248 DynamicObject dynamicObject;
249 PersistenceCapable persistenceCapable = cls.getAnnotation(PersistenceCapable.class);
250 String tableName = null;
252 dynamicObject = cls.newInstance();
253 tableName = dynamicObject.table();
254 if (tableName == null && persistenceCapable != null) {
255 tableName = persistenceCapable.table();
257 } catch (InstantiationException e) {
258 throw new ClusterJUserException(local.message("ERR_Dynamic_Object_Instantiation", cls.getName()), e);
259 } catch (IllegalAccessException e) {
260 throw new ClusterJUserException(local.message("ERR_Dynamic_Object_Illegal_Access", cls.getName()), e);
262 if (tableName == null) {
263 throw new ClusterJUserException(local.message("ERR_Dynamic_Object_Null_Table_Name",
269 /** Is this type supported? */
270 public boolean isSupportedType() {
271 // if unsupported, throw an exception
275 public ValueHandler getValueHandler(Object instance)
276 throws IllegalArgumentException {
277 if (instance instanceof ValueHandler) {
278 return (ValueHandler)instance;
279 } else if (instance instanceof DynamicObject) {
280 return (ValueHandler)((DynamicObject)instance).delegate();
282 ValueHandler handler = (ValueHandler)
283 Proxy.getInvocationHandler(instance);
288 public void objectMarkModified(ValueHandler handler, String fieldName) {
289 int fieldNumber = fieldNameToNumber.get(fieldName);
290 handler.markModified(fieldNumber);
293 public Class<T> getProxyClass() {
297 public Class<T> getDomainClass() {
301 public void operationSetValues(Object instance, Operation op) {
302 ValueHandler handler = getValueHandler(instance);
303 for (DomainFieldHandler fmd: persistentFieldHandlers) {
304 fmd.operationSetValue(handler, op);
308 public void objectSetKeys(Object keys, Object instance) {
309 ValueHandler handler = getValueHandler(instance);
310 int size = idFieldHandlers.length;
312 // single primary key; store value in key field
313 for (DomainFieldHandler fmd: idFieldHandlers) {
314 fmd.objectSetKeyValue(keys, handler);
316 } else if (keys instanceof java.lang.Object[]) {
317 if (logger.isDetailEnabled()) logger.detail(keys.toString());
318 // composite primary key; store values in key fields
319 for (int i = 0; i < idFieldHandlers.length; ++i) {
320 idFieldHandlers[i].objectSetKeyValue(((Object[])keys)[i], handler);
323 // composite key but parameter is not Object[]
324 throw new ClusterJUserException(
325 local.message("ERR_Composite_Key_Parameter"));
329 public void objectResetModified(ValueHandler handler) {
330 handler.resetModified();
333 @SuppressWarnings("unchecked")
334 public void objectSetCacheManager(CacheManager cm, Object instance) {
335 InvocationHandlerImpl<T> handler =
336 (InvocationHandlerImpl<T>)getValueHandler(instance);
337 handler.setCacheManager(cm);
340 public T newInstance() {
343 InvocationHandlerImpl<T> handler = new InvocationHandlerImpl<T>(this);
345 instance = cls.newInstance();
346 ((DynamicObject)instance).delegate((DynamicObjectDelegate)handler);
348 instance = ctor.newInstance(new Object[] {handler});
349 handler.setProxy(instance);
352 } catch (InstantiationException ex) {
353 throw new ClusterJException(
354 local.message("ERR_Create_Instance", cls.getName()), ex);
355 } catch (IllegalAccessException ex) {
356 throw new ClusterJException(
357 local.message("ERR_Create_Instance", cls.getName()), ex);
358 } catch (IllegalArgumentException ex) {
359 throw new ClusterJException(
360 local.message("ERR_Create_Instance", cls.getName()), ex);
361 } catch (InvocationTargetException ex) {
362 throw new ClusterJException(
363 local.message("ERR_Create_Instance", cls.getName()), ex);
364 } catch (SecurityException ex) {
365 throw new ClusterJException(
366 local.message("ERR_Create_Instance", cls.getName()), ex);
371 public void initializeNotPersistentFields(InvocationHandlerImpl<T> handler) {
372 for (DomainFieldHandler fmd:primitiveFieldHandlers) {
373 ((AbstractDomainFieldHandlerImpl) fmd).objectSetDefaultValue(handler);
377 /** Convert a method name to a javabeans property name.
378 * This is done by removing the leading "get" or "set" and upper-casing the
380 * @param methodName the name of a get or set method
381 * @return the property name
383 private String convertMethodName(String methodName) {
384 String head = methodName.substring(3, 4).toLowerCase();
385 String tail = methodName.substring(4);
389 @SuppressWarnings( "unchecked" )
390 public T getInstance(ValueHandler handler) {
391 return (T)((InvocationHandlerImpl)handler).getProxy();
394 private Class<?> getType(Method method) {
395 Class<?> result = null;
396 if (method.getName().startsWith("get")) {
397 result = method.getReturnType();
398 } else if (method.getName().startsWith("set")) {
399 Class<?>[] types = method.getParameterTypes();
400 if (types.length != 1) {
401 throw new ClusterJUserException(
402 local.message("ERR_Set_Method_Parameters",
403 method.getName(), types.length));
407 throw new ClusterJFatalInternalException(
408 local.message("ERR_Method_Name", method.getName()));
410 if (result == null) {
411 throw new ClusterJUserException(
412 local.message("ERR_Unmatched_Method" + method.getName()));
417 /** TODO: Protect with doPrivileged. */
418 protected Constructor<T> getConstructorForInvocationHandler(
421 return cls.getConstructor(invocationHandlerClassArray);
422 } catch (NoSuchMethodException ex) {
423 throw new ClusterJFatalInternalException(
424 local.message("ERR_Get_Constructor", cls), ex);
425 } catch (SecurityException ex) {
426 throw new ClusterJFatalInternalException(
427 local.message("ERR_Get_Constructor", cls), ex);
431 public ValueHandler createKeyValueHandler(Object keys) {
433 throw new ClusterJUserException(
434 local.message("ERR_Key_Must_Not_Be_Null", getName(), "unknown"));
436 Object[] keyValues = new Object[numberOfFields];
437 // check the cardinality of the keys with the number of key fields
438 if (numberOfIdFields == 1) {
439 Class<?> keyType = idFieldHandlers[0].getType();
440 DomainFieldHandler fmd = idFieldHandlers[0];
441 checkKeyType(fmd.getName(), keyType, keys);
442 int keyFieldNumber = fmd.getFieldNumber();
443 keyValues[keyFieldNumber] = keys;
445 if (!(keys.getClass().isArray())) {
446 throw new ClusterJUserException(
447 local.message("ERR_Key_Must_Be_An_Object_Array",
450 Object[]keyObjects = (Object[])keys;
451 for (int i = 0; i < numberOfIdFields; ++i) {
452 DomainFieldHandler fmd = idFieldHandlers[i];
453 int index = fmd.getFieldNumber();
454 Object keyObject = keyObjects[i];
455 Class<?> keyType = fmd.getType();
456 checkKeyType(fmd.getName(), keyType, keyObject);
457 keyValues[index] = keyObjects[i];
460 return new KeyValueHandlerImpl(keyValues);
463 /** Check that the key value matches the key type. Keys that are part
464 * of a compound key can be null as long as they are not part of the
467 * @param name the name of the field
468 * @param keyType the type of the key field
469 * @param keys the value for the key field
471 public void checkKeyType(String name, Class<?> keyType, Object keys)
472 throws ClusterJUserException {
476 Class<?> valueType = keys.getClass();
477 if (keyType.isAssignableFrom(valueType) ||
478 (keyType == int.class && valueType == Integer.class) ||
479 (keyType == Integer.class & valueType == int.class) ||
480 (keyType == Long.class & valueType == long.class) ||
481 (keyType == long.class & valueType == Long.class)) {
484 throw new ClusterJUserException(
485 local.message("ERR_Incorrect_Key_Type",
486 name, valueType.getName(), keyType.getName()));
490 public Class<?> getOidClass() {
491 throw new ClusterJFatalInternalException(local.message("ERR_Implementation_Should_Not_Occur"));
494 protected ColumnMetadata[] columnMetadata() {
495 ColumnMetadata[] result = new ColumnMetadata[numberOfFields];
496 return persistentFieldHandlers.toArray(result);