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.ValueHandler;
21 import com.mysql.clusterj.ClusterJDatastoreException;
22 import com.mysql.clusterj.ClusterJUserException;
23 import com.mysql.clusterj.ColumnType;
25 import com.mysql.clusterj.annotation.Column;
26 import com.mysql.clusterj.annotation.Lob;
27 import com.mysql.clusterj.annotation.NotPersistent;
28 import com.mysql.clusterj.annotation.NullValue;
29 import com.mysql.clusterj.annotation.Persistent;
31 import com.mysql.clusterj.core.store.Operation;
32 import com.mysql.clusterj.core.store.Table;
34 import java.lang.annotation.Annotation;
36 import java.lang.reflect.Method;
38 import java.math.BigDecimal;
39 import java.math.BigInteger;
41 /** An instance of this class handles a field (property)
42 * of a persistence-capable class (interface).
43 * Currently only properties (paired get and set methods) of interfaces
45 * Instances of the class bind at construction time to implementations of
46 * type-specific handlers for Ndb operations.
49 public class DomainFieldHandlerImpl extends AbstractDomainFieldHandlerImpl {
51 /** The NullValue setting of the column from the Persistent annotation. */
52 NullValue nullValue = NullValue.NONE;
54 /** The get method. */
57 /** The set method. */
58 protected Method setMethod;
60 /** The null value handler. */
61 protected NullObjectOperationHandler nullValueDelegate;
63 /** The Index annotation on the get method. */
64 protected com.mysql.clusterj.annotation.Index indexAnnotation = null;
66 /** The Persistent annotation on the get method. */
67 protected Persistent persistentAnnotation = null;
69 /** The Column annotation on the get method. */
70 protected Column columnAnnotation = null;
72 /** The AllowsNull annotation */
73 protected String columnAllowsNull;
75 /** Lob annotation is not null if annotated with @Lob. */
76 protected Lob lobAnnotation;
78 /** The NotPersistent annotation indicates that this field is not
79 * persistent, but can be used as a property that holds data not
80 * stored in the datastore.
82 protected NotPersistent notPersistentAnnotation;
84 public int compareTo(Object other) {
85 return compareTo((DomainFieldHandlerImpl)other);
88 /** Create a domain field handler for annotated interfaces.
90 * @param domainTypeHandler the domain type handler
91 * @param table the table
92 * @param fieldNumber the field number (in schema definition order)
93 * @param name the field name
94 * @param type the java type
95 * @param getMethod the get method for the field
96 * @param setMethod the set method for the field
98 public DomainFieldHandlerImpl(DomainTypeHandlerImpl<?> domainTypeHandler, Table table,
99 int fieldNumber, String name, Class<?> type,
100 Method getMethod, Method setMethod) {
101 if (logger.isDebugEnabled()) logger.debug("new DomainFieldHandlerImpl: fieldNumber: " + fieldNumber + "; name:" + name + "; getMethod: " + getMethod + "; setMethod: " + setMethod);
102 this.domainTypeHandler = domainTypeHandler;
103 this.fieldNumber = fieldNumber;
106 this.setMethod = setMethod;
107 this.getMethod = getMethod;
109 Annotation[] annotations = setMethod.getAnnotations();
110 if (annotations != null && annotations.length != 0) {
111 for (Annotation a: annotations) {
112 error(local.message("ERR_Annotate_Set_Method",
113 name, a.annotationType().getName()));
116 notPersistentAnnotation = getMethod.getAnnotation(NotPersistent.class);
117 if (isPersistent()) {
118 // process column annotation first and check the class annotation
120 // Initialize default column name; may be overridden with annotation
121 this.columnName = name.toLowerCase();
122 this.columnNames = new String[]{name};
123 columnAnnotation = getMethod.getAnnotation(Column.class);
124 if (columnAnnotation != null) {
125 if (columnAnnotation.name() != null) {
126 columnName = columnAnnotation.name();
127 this.columnNames = new String[]{columnName};
129 if (logger.isDebugEnabled())
130 logger.debug("Column name annotation for " + name + " is "
132 columnAllowsNull = columnAnnotation.allowsNull();
133 if (logger.isDebugEnabled())
134 logger.debug("Column allowsNull annotation for " + name
135 + " is " + columnAllowsNull);
136 columnDefaultValue = columnAnnotation.defaultValue();
137 // if user has not specified column defaultValue, set it to null,
138 // which makes it easier for later processing
139 if (columnDefaultValue.equals("")) {
140 columnDefaultValue = null;
142 if (logger.isDebugEnabled())
143 logger.debug("Column defaultValue annotation for " + name
144 + " is " + columnDefaultValue);
146 storeColumn = table.getColumn(columnName);
147 if (storeColumn == null) {
148 throw new ClusterJUserException(local.message("ERR_No_Column",
149 name, table.getName(), columnName));
151 initializeColumnMetadata(storeColumn);
152 if (logger.isDebugEnabled())
153 logger.debug("Column type for " + name + " is "
154 + storeColumnType.toString() + "; charset name is "
156 domainTypeHandler.registerPrimaryKeyColumn(this, columnName);
157 lobAnnotation = getMethod.getAnnotation(Lob.class);
160 if (type.equals(int.class)) {
161 objectOperationHandlerDelegate = objectOperationHandlerKeyInt;
162 } else if (type.equals(long.class)) {
163 objectOperationHandlerDelegate = objectOperationHandlerKeyLong;
164 } else if (type.equals(String.class)) {
165 objectOperationHandlerDelegate = objectOperationHandlerKeyString;
166 } else if (type.equals(byte[].class)) {
167 objectOperationHandlerDelegate = objectOperationHandlerKeyBytes;
169 objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType;
171 local.message("ERR_Primary_Field_Type", domainTypeHandler.getName(), name, printableName(type)));
173 } else if (lobAnnotation != null) {
174 // large object support for byte[]
175 if (type.equals(byte[].class)) {
176 objectOperationHandlerDelegate = objectOperationHandlerBytesLob;
177 } else if (type.equals(String.class)) {
178 objectOperationHandlerDelegate = objectOperationHandlerStringLob;
180 objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType;
182 local.message("ERR_Unsupported_Field_Type", printableName(type), name));
184 } else if (!isPersistent()) {
185 // NotPersistent field
186 if (type.equals(byte.class)) {
187 objectOperationHandlerDelegate = objectOperationHandlerNotPersistentByte;
188 } else if (type.equals(double.class)) {
189 objectOperationHandlerDelegate = objectOperationHandlerNotPersistentDouble;
190 } else if (type.equals(float.class)) {
191 objectOperationHandlerDelegate = objectOperationHandlerNotPersistentFloat;
192 } else if (type.equals(int.class)) {
193 objectOperationHandlerDelegate = objectOperationHandlerNotPersistentInt;
194 } else if (type.equals(long.class)) {
195 objectOperationHandlerDelegate = objectOperationHandlerNotPersistentLong;
196 } else if (type.equals(short.class)) {
197 objectOperationHandlerDelegate = objectOperationHandlerNotPersistentShort;
199 objectOperationHandlerDelegate = objectOperationHandlerNotPersistentObject;
202 // not a pk field; use xxxValue to set values
203 if (type.equals(byte[].class)) {
204 objectOperationHandlerDelegate = objectOperationHandlerBytes;
205 } else if (type.equals(java.util.Date.class)) {
206 objectOperationHandlerDelegate = objectOperationHandlerJavaUtilDate;
207 } else if (type.equals(BigDecimal.class)) {
208 objectOperationHandlerDelegate = objectOperationHandlerDecimal;
209 } else if (type.equals(BigInteger.class)) {
210 objectOperationHandlerDelegate = objectOperationHandlerBigInteger;
211 } else if (type.equals(double.class)) {
212 objectOperationHandlerDelegate = objectOperationHandlerDouble;
213 } else if (type.equals(float.class)) {
214 objectOperationHandlerDelegate = objectOperationHandlerFloat;
215 } else if (type.equals(int.class)) {
216 objectOperationHandlerDelegate = objectOperationHandlerInt;
217 } else if (type.equals(Integer.class)) {
218 objectOperationHandlerDelegate = objectOperationHandlerObjectInteger;
219 } else if (type.equals(Long.class)) {
220 objectOperationHandlerDelegate = objectOperationHandlerObjectLong;
221 } else if (type.equals(Short.class)) {
222 if (ColumnType.Year.equals(storeColumnType)) {
223 objectOperationHandlerDelegate = objectOperationHandlerObjectShortYear;
225 objectOperationHandlerDelegate = objectOperationHandlerObjectShort;
227 } else if (type.equals(Float.class)) {
228 objectOperationHandlerDelegate = objectOperationHandlerObjectFloat;
229 } else if (type.equals(Double.class)) {
230 objectOperationHandlerDelegate = objectOperationHandlerObjectDouble;
231 } else if (type.equals(long.class)) {
232 objectOperationHandlerDelegate = objectOperationHandlerLong;
233 } else if (type.equals(short.class)) {
234 if (ColumnType.Year.equals(storeColumnType)) {
235 objectOperationHandlerDelegate = objectOperationHandlerShortYear;
237 objectOperationHandlerDelegate = objectOperationHandlerShort;
239 } else if (type.equals(String.class)) {
240 objectOperationHandlerDelegate = objectOperationHandlerString;
241 } else if (type.equals(Byte.class)) {
242 objectOperationHandlerDelegate = objectOperationHandlerObjectByte;
243 } else if (type.equals(byte.class)) {
244 objectOperationHandlerDelegate = objectOperationHandlerByte;
245 } else if (type.equals(boolean.class)) {
246 objectOperationHandlerDelegate = objectOperationHandlerBoolean;
247 } else if (type.equals(Boolean.class)) {
248 objectOperationHandlerDelegate = objectOperationHandlerObjectBoolean;
249 } else if (type.equals(java.sql.Date.class)) {
250 objectOperationHandlerDelegate = objectOperationHandlerJavaSqlDate;
251 } else if (type.equals(java.sql.Time.class)) {
252 objectOperationHandlerDelegate = objectOperationHandlerJavaSqlTime;
253 } else if (type.equals(java.sql.Timestamp.class)) {
254 objectOperationHandlerDelegate = objectOperationHandlerJavaSqlTimestamp;
256 objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType;
258 local.message("ERR_Unsupported_Field_Type", type.getName()));
261 // Handle indexes. One index can be annotated on this field.
262 // Other indexes including the column mapped to this field
263 // are annotated on the class.
264 // TODO: indexes are ignored since they are handled by reading the column metadata
265 indexAnnotation = getMethod.getAnnotation(
266 com.mysql.clusterj.annotation.Index.class);
267 String indexName = null;
268 if (indexAnnotation != null) {
269 indexName = indexAnnotation.name();
270 if (indexAnnotation.columns().length != 0) {
271 throw new ClusterJUserException(
272 local.message("ERR_Index_Annotation_Columns", domainTypeHandler.getName(), name));
275 registerIndices(domainTypeHandler);
277 persistentAnnotation = getMethod.getAnnotation(Persistent.class);
278 if (persistentAnnotation != null) {
279 nullValue = persistentAnnotation.nullValue();
280 logger.debug("Persistent nullValue annotation for " + name + " is " + nullValue);
282 // convert the string default value to type-specific value
283 defaultValue = objectOperationHandlerDelegate.getDefaultValueFor(this, columnDefaultValue);
284 logger.debug("Default null value for " + name + " is " + defaultValue);
286 // set up the null value handler based on the annotation
289 // value is null and user has specified a default value
290 nullValueDelegate = nullValueDEFAULT;
293 // value is null and user wants a ClusterJ exception
294 nullValueDelegate = nullValueEXCEPTION;
297 // value is null and no special handling
298 nullValueDelegate = nullValueNONE;
304 /** Create a domain field handler for dynamic objects.
306 * @param domainTypeHandler the domain type handler
307 * @param table the table
308 * @param i the field number
309 * @param storeColumn the store column definition
311 public DomainFieldHandlerImpl(
312 DomainTypeHandlerImpl<?> domainTypeHandler, Table table, int i,
313 com.mysql.clusterj.core.store.Column storeColumn) {
314 if (logger.isDebugEnabled()) logger.debug("new dynamic DomainFieldHandlerImpl: " +
315 "fieldNumber: " + fieldNumber + "; name:" + name);
316 this.domainTypeHandler = domainTypeHandler;
317 this.fieldNumber = i;
318 this.storeColumn = storeColumn;
319 initializeColumnMetadata(storeColumn);
320 this.name = this.columnName;
321 this.columnNames = new String[]{columnName};
323 domainTypeHandler.registerPrimaryKeyColumn(this, columnName);
324 switch (this.storeColumnType) {
327 this.objectOperationHandlerDelegate = objectOperationHandlerKeyInt;
328 this.type = Integer.class;
332 this.objectOperationHandlerDelegate = objectOperationHandlerKeyString;
333 this.type = String.class;
337 this.objectOperationHandlerDelegate = objectOperationHandlerKeyLong;
338 this.type = Long.class;
343 this.objectOperationHandlerDelegate = objectOperationHandlerKeyBytes;
344 this.type = byte[].class;
347 error(local.message("ERR_Primary_Column_Type", domainTypeHandler.getName(), name, this.storeColumnType));
350 switch (this.storeColumnType) {
353 this.objectOperationHandlerDelegate = objectOperationHandlerObjectLong;
354 this.type = Long.class;
357 this.objectOperationHandlerDelegate = objectOperationHandlerBytes;
358 this.type = byte[].class;
361 this.objectOperationHandlerDelegate = objectOperationHandlerObjectLong;
362 this.type = Long.class;
365 this.objectOperationHandlerDelegate = objectOperationHandlerBytesLob;
366 this.type = byte[].class;
369 this.objectOperationHandlerDelegate = objectOperationHandlerString;
370 this.type = String.class;
373 this.objectOperationHandlerDelegate = objectOperationHandlerJavaSqlDate;
374 this.type = java.sql.Date.class;
377 this.objectOperationHandlerDelegate = objectOperationHandlerJavaSqlTimestamp;
378 this.type = java.sql.Timestamp.class;
381 case Decimalunsigned:
382 this.objectOperationHandlerDelegate = objectOperationHandlerDecimal;
383 this.type = BigDecimal.class;
386 this.objectOperationHandlerDelegate = objectOperationHandlerObjectDouble;
387 this.type = Double.class;
390 this.objectOperationHandlerDelegate = objectOperationHandlerObjectFloat;
391 this.type = Float.class;
394 this.objectOperationHandlerDelegate = objectOperationHandlerObjectInteger;
395 this.type = Integer.class;
398 this.objectOperationHandlerDelegate = objectOperationHandlerBytes;
399 this.type = byte[].class;
402 this.objectOperationHandlerDelegate = objectOperationHandlerString;
403 this.type = String.class;
407 this.objectOperationHandlerDelegate = objectOperationHandlerObjectInteger;
408 this.type = Integer.class;
411 error(local.message("ERR_Unsupported_Field_Type", "Olddecimal", name));
412 objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType;
414 case Olddecimalunsigned:
415 error(local.message("ERR_Unsupported_Field_Type", "Olddecimalunsigned", name));
416 objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType;
420 this.objectOperationHandlerDelegate = objectOperationHandlerObjectShort;
421 this.type = Short.class;
424 this.objectOperationHandlerDelegate = objectOperationHandlerStringLob;
425 this.type = String.class;
428 this.objectOperationHandlerDelegate = objectOperationHandlerJavaSqlTime;
429 this.type = java.sql.Time.class;
432 this.objectOperationHandlerDelegate = objectOperationHandlerJavaSqlTimestamp;
433 this.type = java.sql.Timestamp.class;
437 this.objectOperationHandlerDelegate = objectOperationHandlerObjectByte;
438 this.type = Byte.class;
441 error(local.message("ERR_Unsupported_Field_Type", "Undefined"));
442 objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType;
445 this.objectOperationHandlerDelegate = objectOperationHandlerObjectInteger;
446 this.type = Integer.class;
449 this.objectOperationHandlerDelegate = objectOperationHandlerBytes;
450 this.type = byte[].class;
453 this.objectOperationHandlerDelegate = objectOperationHandlerString;
454 this.type = String.class;
457 this.objectOperationHandlerDelegate = objectOperationHandlerObjectShortYear;
458 this.type = Short.class;
461 error(local.message("ERR_Unsupported_Field_Type", this.storeColumnType));
462 objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType;
465 nullValueDelegate = nullValueNONE;
466 registerIndices(domainTypeHandler);
470 public boolean isPersistent() {
471 return notPersistentAnnotation == null;
474 protected void registerIndices(DomainTypeHandlerImpl<?> domainTypeHandler) {
475 this.indices = domainTypeHandler.registerIndices(this, columnName);
476 this.indexNames = domainTypeHandler.getIndexNames(indices);
477 if (logger.isDebugEnabled()) logger.debug("Index names for " + name + " are " + indexNames);
478 if (logger.isDebugEnabled()) logger.debug("Indices for " + name + " are " + printIndices());
482 public void operationSetValue(ValueHandler handler, Operation op) {
483 // handle NullValue here
484 boolean isNull = handler.isNull(fieldNumber);
485 if (logger.isDetailEnabled()) logger.detail("Column: " + columnName + " field: " + name + " isNull: " + isNull + " type: " + type + " delegate " + objectOperationHandlerDelegate.handler());
488 // value is null; let delegate see what to do
489 if (nullValueDelegate.operationSetValue(this, op)) {
493 objectOperationHandlerDelegate.operationSetValue(this, handler, op);
494 } catch (ClusterJDatastoreException ex) {
495 throw new ClusterJDatastoreException(local.message("ERR_Value_Delegate", name, columnName, objectOperationHandlerDelegate.handler(), "setValue"), ex);
499 protected interface NullObjectOperationHandler {
500 /** Handle null values on operationSetValue. This method is called if the
501 * value to be set in the handler is null. The execution depends on
502 * the null value handling defined for the field.
504 * @param fmd the FieldHandler for the field
505 * @param op the NDB Operation
506 * @return true if the operationSetValue has been handled
507 * @throws com.mysql.cluster.ndbj.NdbApiException
509 boolean operationSetValue(DomainFieldHandlerImpl fmd, Operation op);
512 static NullObjectOperationHandler nullValueDEFAULT = new NullObjectOperationHandler() {
513 public boolean operationSetValue(DomainFieldHandlerImpl fmd, Operation op) {
514 // set the default value and then return
515 fmd.operationSetValue(fmd, fmd.defaultValue, op);
520 static NullObjectOperationHandler nullValueEXCEPTION = new NullObjectOperationHandler() {
521 public boolean operationSetValue(DomainFieldHandlerImpl fmd, Operation op) {
522 // always throw an exception
523 throw new ClusterJUserException(
524 local.message("ERR_Null_Value_Exception",
525 fmd.domainTypeHandler.getName(), fmd.name));
529 static NullObjectOperationHandler nullValueNONE = new NullObjectOperationHandler() {
530 public boolean operationSetValue(DomainFieldHandlerImpl fmd, Operation op) {
531 // don't do anything here but do the standard processing