/*******************************************************************************
* Copyright (c) 1998, 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
* 05/16/2008-1.0M8 Guy Pelletier
* - 218084: Implement metadata merging functionality between mapping files
* 06/20/2008-1.0 Guy Pelletier
* - 232975: Failure when attribute type is generic
* 09/23/2008-1.1 Guy Pelletier
* - 241651: JPA 2.0 Access Type support
******************************************************************************/
package org.eclipse.persistence.internal.jpa.metadata.accessors.mappings;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import javax.persistence.Enumerated;
import javax.persistence.EnumType;
import javax.persistence.FetchType;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAccessibleObject;
import org.eclipse.persistence.internal.jpa.metadata.columns.ColumnMetadata;
import org.eclipse.persistence.internal.jpa.metadata.converters.AbstractConverterMetadata;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.helper.DatabaseTable;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.mappings.converters.EnumTypeConverter;
import org.eclipse.persistence.mappings.converters.SerializedObjectConverter;
import org.eclipse.persistence.mappings.converters.ClassInstanceConverter;
import org.eclipse.persistence.mappings.converters.TypeConversionConverter;
/**
* A direct accessor.
*
* Subclasses: BasicAccessor, BasicCollectionAccessor, BasicMapAccessor.
*
* @author Guy Pelletier
* @since TopLink 11g
*/
public abstract class DirectAccessor extends MappingAccessor {
// Reserved converter names
private static final String CONVERT_NONE = "none";
private static final String CONVERT_SERIALIZED = "serialized";
private static final String CONVERT_CLASS_INSTANCE = "class-instance";
private final static String DEFAULT_MAP_KEY_COLUMN_SUFFIX = "_KEY";
private boolean m_lob;
private Boolean m_optional;
private Enum m_fetch;
private Enum m_enumerated;
private Enum m_temporal;
private String m_convert;
/**
* INTERNAL:
*/
protected DirectAccessor(String xmlElement) {
super(xmlElement);
}
/**
* INTERNAL:
*/
protected DirectAccessor(Annotation annotation, MetadataAccessibleObject accessibleObject, ClassAccessor classAccessor) {
super(annotation, accessibleObject, classAccessor);
// Set the lob if one is present.
m_lob = isAnnotationPresent(Lob.class);
// Set the enumerated if one is present.
Annotation enumerated = getAnnotation(Enumerated.class);
if (enumerated != null) {
m_enumerated = (Enum) MetadataHelper.invokeMethod("value", enumerated);
}
// Set the temporal type if one is present.
Annotation temporal = getAnnotation(Temporal.class);
if (temporal != null) {
m_temporal = (Enum) MetadataHelper.invokeMethod("value", temporal);
}
// Set the convert value if one is present.
Annotation convert = getAnnotation(Convert.class);
if (convert != null) {
m_convert = (String) MetadataHelper.invokeMethod("value", convert);
}
}
/**
* INTERNAL:
* This is used to return the column for a BasicAccessor. In the case
* of a BasicCollectionAccessor or BasicMapAccessor, this method should
* return the value column. NOTE: In the case of a BasicMapAccessor, the
* key column could be returned instead.
*/
protected abstract ColumnMetadata getColumn(String loggingCtx);
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getConvert() {
return m_convert;
}
/**
* INTERNAL:
* Process column metadata details into a database field. This will set
* correct metadata and log defaulting messages to the user. It also looks
* for an attribute override.
*
* This method will call getColumn() which assumes the subclasses will
* return the appropriate ColumnMetadata to process based on the context
* provided.
*
* @See BasicCollectionAccessor and BasicMapAccessor.
*/
protected DatabaseField getDatabaseField(DatabaseTable defaultTable, String loggingCtx) {
// Check if we have an attribute override first, otherwise process for
// a column (ignoring if for a key column on a basic map)
ColumnMetadata column;
if (getDescriptor().hasAttributeOverrideFor(getAttributeName()) && ! loggingCtx.equals(MetadataLogger.MAP_KEY_COLUMN)) {
column = getDescriptor().getAttributeOverrideFor(getAttributeName()).getColumn();
} else {
column = getColumn(loggingCtx);
}
// Get the actual database field and apply any defaults.
DatabaseField field = column.getDatabaseField();
// Make sure there is a table name on the field.
if (field.getTableName().equals("")) {
field.setTable(defaultTable);
}
// Set the correct field name, defaulting and logging when necessary.
String defaultName = column.getUpperCaseAttributeName();
// If this is for a map key column, append a suffix.
if (loggingCtx.equals(MetadataLogger.MAP_KEY_COLUMN)) {
defaultName += DEFAULT_MAP_KEY_COLUMN_SUFFIX;
}
field.setName(getName(field.getName(), defaultName, loggingCtx));
return field;
}
/**
* INTERNAL:
*/
public abstract FetchType getDefaultFetchType();
/**
* INTERNAL:
* Used for OX mapping.
*/
public Enum getEnumerated() {
return m_enumerated;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public Enum getFetch() {
return m_fetch;
}
/**
* INTERNAL:
* Return the field classification for the given temporal type.
*/
protected Class getFieldClassification(Enum type) {
if (type.name().equals(TemporalType.DATE.name())){
return java.sql.Date.class;
} else if(type.name().equals(TemporalType.TIME.name())){
return java.sql.Time.class;
} else if(type.name().equals(TemporalType.TIMESTAMP.name())){
return java.sql.Timestamp.class;
} else {
return null;
}
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getLob() {
return null;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public Boolean getOptional() {
return m_optional;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public Enum getTemporal() {
return m_temporal;
}
/**
* INTERNAL:
*/
@Override
protected boolean hasConvert() {
return m_convert != null;
}
/**
* INTERNAL:
* Return true if this represents an enum type mapping. Will return true
* if the accessor's reference class is an enum or if a @Enumerated exists.
*/
public boolean isEnumerated() {
if (hasConvert()) {
// If we have an @Enumerated with a @Convert, the @Convert takes
// precedence and we will ignore the @Enumerated and log a message.
if (m_enumerated != null) {
getLogger().logWarningMessage(MetadataLogger.IGNORE_ENUMERATED, getJavaClass(), getAnnotatedElement());
}
return false;
} else {
return m_enumerated != null || isValidEnumeratedType(getReferenceClass());
}
}
/**
* INTERNAL:
* Return true if this accessor represents a BLOB/CLOB mapping.
*/
public boolean isLob() {
if (hasConvert()) {
// If we have a Lob specified with a Convert, the Convert takes
// precedence and we will ignore the Lob and log a message.
if (m_lob) {
getLogger().logWarningMessage(MetadataLogger.IGNORE_LOB, getJavaClass(), getAnnotatedElement());
}
return false;
} else {
return m_lob;
}
}
/**
* INTERNAL:
*/
public boolean isOptional() {
if (m_optional == null) {
return true;
} else {
return m_optional.booleanValue();
}
}
/**
* INTERNAL:
* Returns true is the given class is primitive wrapper type.
*/
protected boolean isPrimitiveWrapperClass(Class cls) {
return Long.class.isAssignableFrom(cls) ||
Short.class.isAssignableFrom(cls) ||
Float.class.isAssignableFrom(cls) ||
Byte.class.isAssignableFrom(cls) ||
Double.class.isAssignableFrom(cls) ||
Number.class.isAssignableFrom(cls) ||
Boolean.class.isAssignableFrom(cls) ||
Integer.class.isAssignableFrom(cls) ||
Character.class.isAssignableFrom(cls) ||
String.class.isAssignableFrom(cls) ||
java.math.BigInteger.class.isAssignableFrom(cls) ||
java.math.BigDecimal.class.isAssignableFrom(cls) ||
java.util.Date.class.isAssignableFrom(cls) ||
java.util.Calendar.class.isAssignableFrom(cls);
}
/**
* INTERNAL:
* Return true if this accessor represents a serialized mapping.
*/
public boolean isSerialized() {
if (hasConvert()) {
getLogger().logWarningMessage(MetadataLogger.IGNORE_SERIALIZED, getJavaClass(), getAnnotatedElement());
return false;
} else {
return isValidSerializedType(getReferenceClass());
}
}
/**
* INTERNAL:
* Return true if this represents a temporal type mapping. Will return true
* if the accessor's reference class is a temporal type or if a @Temporal
* exists.
*/
public boolean isTemporal() {
if (hasConvert()) {
// If we have a Temporal specification with a Convert specification,
// the Convert takes precedence and we will ignore the Temporal and
// log a message.
if (m_temporal != null) {
getLogger().logWarningMessage(MetadataLogger.IGNORE_TEMPORAL, getJavaClass(), getAnnotatedElement());
}
return false;
} else {
return m_temporal != null || isValidTemporalType(getReferenceClass());
}
}
/**
* INTERNAL:
* Returns true if the given class is a valid blob type.
*/
protected boolean isValidBlobType(Class cls) {
return cls.equals(byte[].class) ||
cls.equals(Byte[].class) ||
cls.equals(java.sql.Blob.class);
}
/**
* INTERNAL:
* Returns true if the given class is a valid clob type.
*/
protected boolean isValidClobType(Class cls) {
return cls.equals(char[].class) ||
cls.equals(String.class) ||
cls.equals(Character[].class) ||
cls.equals(java.sql.Clob.class);
}
/**
* INTERNAL:
* Returns true if the given class is a valid lob type.
*/
protected boolean isValidLobType(Class cls) {
return isValidClobType(cls) || isValidBlobType(cls);
}
/**
* INTERNAL:
* Return true if the given class is a valid enum type.
*/
protected boolean isValidEnumeratedType(Class cls) {
return cls.isEnum();
}
/**
* INTERNAL:
* Returns true if the given class is valid for SerializedObjectMapping.
*/
protected boolean isValidSerializedType(Class cls) {
if (cls.isPrimitive()) {
return false;
}
if (isPrimitiveWrapperClass(cls)) {
return false;
}
if (isValidLobType(cls)) {
return false;
}
if (isValidTemporalType(cls)) {
return false;
}
return true;
}
/**
* INTERNAL:
* Returns true if the given class is a valid temporal type and must be
* marked temporal.
*/
protected boolean isValidTemporalType(Class cls) {
return (cls.equals(java.util.Date.class) ||
cls.equals(java.util.Calendar.class) ||
cls.equals(java.util.GregorianCalendar.class));
}
/**
* INTERNAL:
*
* Process a potential class-instance attribute. If the reference class is a Class,
* set the converter on the mapping
*/
protected void processClassInstance(DatabaseMapping mapping) {
ClassInstanceConverter converter = new ClassInstanceConverter();
setConverter(mapping, converter);
}
/**
* INTERNAL:
* Process a convert value to apply the specified EclipseLink converter
* (Converter, TypeConverter, ObjectTypeConverter) to the given mapping.
*
* This method is called in second stage processing and should only be
* called on accessors that have a convert value specified.
*/
public void processConvert() {
processConvert(getDescriptor().getMappingForAttributeName(getAttributeName()), m_convert);
}
/**
* INTERNAL:
* Process a Convert annotation or convert element to apply to specified
* EclipseLink converter (Converter, TypeConverter, ObjectTypeConverter)
* to the given mapping.
*
* This method is called in second stage processing and should only be
* called on accessors that have a Convert annotation specified.
*/
protected void processConvert(DatabaseMapping mapping, String converterName) {
// There is no work to do if the converter's name is "none".
if (! converterName.equals(CONVERT_NONE)) {
if (converterName.equals(CONVERT_SERIALIZED)) {
processSerialized(mapping);
} else if (converterName.equals(CONVERT_CLASS_INSTANCE)){
processClassInstance(mapping);
} else {
AbstractConverterMetadata converter = getProject().getConverter(converterName);
if (converter == null) {
throw ValidationException.converterNotFound(getJavaClass(), converterName, getAnnotatedElement());
} else {
// Process the converter for this mapping.
converter.process(mapping, this);
}
}
}
}
/**
* INTERNAL:
* Process an Enumerated annotation. The method may still be called if no
* Enumerated annotation has been specified but the accessor's reference
* class is a valid enumerated type.
*/
protected void processEnumerated(DatabaseMapping mapping) {
// If this accessor is tagged as an enumerated type, validate the
// reference class.
if (m_enumerated != null && !isValidEnumeratedType(getReferenceClass())) {
throw ValidationException.invalidTypeForEnumeratedAttribute(mapping.getAttributeName(), getReferenceClass(), getJavaClass());
} else {
// Create an EnumTypeConverter and set it on the mapping.
if (m_enumerated == null) {
// TODO: Log a defaulting message
setConverter(mapping, new EnumTypeConverter(mapping, getReferenceClass(), true));
} else {
setConverter(mapping, new EnumTypeConverter(mapping, getReferenceClass(), m_enumerated.name().equals(EnumType.ORDINAL.name())));
}
}
}
/**
* INTERNAL:
*
* Process an Enumerated, Lob or Temporal specification. Will default
* a serialized converter if necessary.
*/
protected void processJPAConverters(DatabaseMapping mapping) {
// Check for an enum first since it will fall into a serializable
// mapping otherwise (Enums are serialized)
if (isEnumerated()) {
processEnumerated(mapping);
} else if (isLob()) {
processLob(mapping);
} else if (isTemporal()) {
processTemporal(mapping);
} else if (isSerialized()) {
processSerialized(mapping);
}
}
/**
* INTERNAL: (Overridden in BasicAccessor)
*
* Process a lob specification. The lob must be specified to process and
* create a lob type mapping.
*/
protected void processLob(DatabaseMapping mapping) {
// Set the field classification type on the mapping based on the
// referenceClass type.
if (isValidClobType(getReferenceClass())) {
setFieldClassification(mapping, java.sql.Clob.class);
setConverter(mapping, new TypeConversionConverter(mapping));
} else if (isValidBlobType(getReferenceClass())) {
setFieldClassification(mapping, java.sql.Blob.class);
setConverter(mapping, new TypeConversionConverter(mapping));
} else if (Helper.classImplementsInterface(getReferenceClass(), Serializable.class)) {
setFieldClassification(mapping, java.sql.Blob.class);
setConverter(mapping, new SerializedObjectConverter(mapping));
} else {
// The referenceClass is neither a valid BLOB or CLOB attribute.
throw ValidationException.invalidTypeForLOBAttribute(mapping.getAttributeName(), getReferenceClass(), getJavaClass());
}
}
/**
* INTERNAL:
* Process a converter for the given mapping. Will look for a converter
* name from a convert specification.
*/
protected void processMappingConverter(DatabaseMapping mapping) {
if (m_convert == null) {
processJPAConverters(mapping);
} else {
processMappingConverter(mapping, m_convert);
}
}
/**
* INTERNAL:
* Process a convert value which specifies the name of an EclipseLink
* converter to process with this accessor's mapping. EclipseLink converters
* (which are global to the persistent unit) can not be processed till we
* have processed all the classes in the persistence unit. So for now, add
* this accessor to the project list of convert dependant accessors, and
* process it in stage 2, that is, during the project process.
*
* The method will look for an EclipseLink converter first (based on the
* converter name provided) and will override any JPA annotations. Log
* warnings will be issued for any annotations that are being ignore
* because of a Convert override.
*/
protected void processMappingConverter(DatabaseMapping mapping, String convertValue) {
if (convertValue != null && ! convertValue.equals(CONVERT_NONE)) {
// EclipseLink converter specified, defer this accessors converter
// processing to stage 2 project processing.
getProject().addConvertAccessor(this);
}
// Regardless if we found a convert or not, look for JPA converters.
// This ensures two things;
// 1 - if no Convert is specified, then any JPA converter that is
// specified will be applied (see BasicMapAccessor's override of the
// method hasConvert()).
// 2 - if a convert and a JPA converter are specified, then a log
// warning will be issued stating that we are ignoring the JPA
// converter.
processJPAConverters(mapping);
}
/**
* INTERNAL:
*
* Process a potential serializable attribute. If the class implements
* the Serializable interface then set a SerializedObjectConverter on
* the mapping.
*/
protected void processSerialized(DatabaseMapping mapping) {
if (Helper.classImplementsInterface(getReferenceClass(), Serializable.class)) {
SerializedObjectConverter converter = new SerializedObjectConverter(mapping);
setConverter(mapping, converter);
} else {
throw ValidationException.invalidTypeForSerializedAttribute(mapping.getAttributeName(), getReferenceClass(), getJavaClass());
}
}
/**
* INTERNAL:
* Process a temporal type accessor.
*/
protected void processTemporal(DatabaseMapping mapping) {
if (m_temporal == null) {
// We have a temporal basic, but the temporal type was not
// specified. Per the JPA spec we must throw an exception.
throw ValidationException.noTemporalTypeSpecified(getAttributeName(), getJavaClass());
} else {
if (isValidTemporalType(getReferenceClass())) {
// Set a TypeConversionConverter on the mapping.
if (m_temporal.name().equals(TemporalType.DATE.name())) {
setFieldClassification(mapping, java.sql.Date.class);
} else if(m_temporal.name().equals(TemporalType.TIME.name())) {
setFieldClassification(mapping,java.sql.Time.class);
} else {
// Through annotation and XML validation, it must be
// TemporalType.TIMESTAMP and can't be anything else.
setFieldClassification(mapping, java.sql.Timestamp.class);
}
setConverter(mapping, new TypeConversionConverter(mapping));
} else {
throw ValidationException.invalidTypeForTemporalAttribute(getAttributeName(), getReferenceClass(), getJavaClass());
}
}
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setConvert(String convert) {
m_convert = convert;
}
/**
* INTERNAL:
*/
public abstract void setConverter(DatabaseMapping mapping, Converter converter);
/**
* INTERNAL:
*/
public abstract void setConverterClassName(DatabaseMapping mapping, String converterClassName);
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setEnumerated(Enum enumerated) {
m_enumerated = enumerated;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setFetch(Enum fetch) {
m_fetch = fetch;
}
/**
* INTERNAL:
*/
public abstract void setFieldClassification(DatabaseMapping mapping, Class classification);
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setLob(String ignore) {
m_lob = true;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setOptional(Boolean optional) {
m_optional = optional;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setTemporal(Enum temporalType) {
m_temporal = temporalType;
}
/**
* INTERNAL:
*/
public boolean usesIndirection() {
Enum fetchType = getFetch();
if (fetchType == null) {
fetchType = getDefaultFetchType();
}
return fetchType.name().equals(FetchType.LAZY.name());
}
}