persistence@glassfish.java.net

RE: Possible to map boolean property to "Y" and "N" values in database?

From: Adam Leftik <adam.leftik_at_oracle.com>
Date: Fri, 23 Feb 2007 11:35:55 -0800

It is possible to use do this with custom TLE converters. For a project I have used the following pattern to make this consistent with annotation based mappings to solve several direct to field mappings that do not line up with the default converters. The package names/comment have been removed to protect the innocent :)

Here is a sample converter that is similar to your problem:

import java.util.Vector;

import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.mappings.DatabaseMapping;
import oracle.toplink.essentials.mappings.converters.Converter;
import oracle.toplink.essentials.sessions.Session;
import oracle.toplink.essentials.tools.sessionconfiguration.DescriptorCustomizer;

public class BooleanConverter implements Converter {


        private static final String FALSE = "f";
        private static final String TRUE = "t";

        public Object convertObjectValueToDataValue(Object objectValue, Session session) {
                if (objectValue != null) {
                        Boolean boolValue = (Boolean) objectValue;
                        if (boolValue) {
                                return TRUE;
                        } else {
                                return FALSE;
                        }
                }
                
                return null;
        }

        public Object convertDataValueToObjectValue(Object dataValue, Session session) {
                if (dataValue != null) {
                        String torf = dataValue.toString();
                        if (TRUE.equalsIgnoreCase(torf)) {
                                return Boolean.TRUE;
                        } else {
                                return Boolean.FALSE;
                        }
                }
                return null;
        }

        public boolean isMutable() {
                return false;
        }

        public void initialize(DatabaseMapping mapping, Session session) {
                //do nothing...
        
        }

}

------
Now I want to be able to use these 'converters' with annotations so the following classes help make this happen. I can extend the converters easily by adding them to the ConverterType Enum and implementing the converter. The Converter annotation then can be easily used to add converters to any entity attribute

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Vector;



import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.mappings.AggregateMapping;
import oracle.toplink.essentials.mappings.AggregateObjectMapping;
import oracle.toplink.essentials.mappings.DatabaseMapping;
import oracle.toplink.essentials.mappings.DirectToFieldMapping;

public class DescriptorCustomizer implements oracle.toplink.essentials.tools.sessionconfiguration.DescriptorCustomizer{
        public DescriptorCustomizer(){}
        
        public void customize(ClassDescriptor desc) throws Exception {
                List<DatabaseMapping> mappings = desc.getMappings();
                for (DatabaseMapping mapping : mappings) {
                        if (mapping instanceof DirectToFieldMapping) {
                                DirectToFieldMapping directMapping = (DirectToFieldMapping)mapping;
                                String attributeName = mapping.getAttributeName();
                                Class mappedClazz = mapping.getDescriptor().getJavaClass();
                                checkFields(directMapping, attributeName, mappedClazz);
                                checkMethods(directMapping, attributeName, mappedClazz);
                        }
                }
                
        }
        

        private void checkMethods(DirectToFieldMapping mapping, String attributeName, Class clazz) throws InstantiationException, IllegalAccessException {
                Method [] methods = clazz.getDeclaredMethods();
                for (Method method: methods) {
                        if (method.getName().equals(mapping.getGetMethodName()) || method.getName().equals(mapping.getSetMethodName())) {
                                Annotation [] annons = method.getAnnotations();
                                checkAnnonations(mapping,annons);
                        }
                }
                
        }

        private void checkFields(DirectToFieldMapping directMapping, String attributeName, Class clazz) throws InstantiationException, IllegalAccessException {
                Field [] fields = clazz.getDeclaredFields();
                for (Field field: fields) {
                        if (field.getName().equals(attributeName)) {
                                Annotation [] annotations = field.getAnnotations();
                                checkAnnonations(directMapping, annotations);
                        }
                }
        }

        private void checkAnnonations(DirectToFieldMapping directMapping, Annotation[] annotations) throws InstantiationException, IllegalAccessException {
                for (Annotation annon: annotations) {
                        if (annon.annotationType().equals(Converter.class)) {
                                 Converter convAnnon = (Converter) annon;
                                ConverterType type = convAnnon.type();
                                Class converterClass = type.getConverter();
                                directMapping.setConverter((oracle.toplink.essentials.mappings.converters.Converter )converterClass.newInstance());
                        }
                }
        }

}



import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;




@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD, METHOD})
public @interface Converter {
    ConverterType type();
}

----
/**
 * Converter Enumeration for converter annotations...
 */
public enum ConverterType {
	BOOLEAN_CONVERTER (BooleanConverter.class),
	TIMEZONE_CONVERTER(TimeZoneConverter.class),
	TIMESTAMPTZ_CONVERTER(TimestampWithTZConverter.class);
	
	private Class clazz;
	
	ConverterType(Class clazz) {
		this.clazz = clazz;
	}
	public Class getConverter() {
		return clazz;
	}
}
Now here is a simple usage of this technique:
  @Entity
  public class Foo {
	@Column(name = "CUSTOMERIND")
	@Converter(type = ConverterType.BOOLEAN_CONVERTER)
	private boolean customerFlag = true;
	
	@Column(name = "TIMEZONE")
	@Converter(type = ConverterType.TIMEZONE_CONVERTER)
	private TimeZone timeZone;
....
}
Now in your persistence.xml all you need to do is add your custom converter! It is the same for all your converter types and you can support many converters per entity and the annotation makes it clear how the attribute is being converted. This code only works for direct to field mappings as it is implemented here, however, it could be extended.
<persistence ...>
	<peristence-unit>
		<class>Foo</class>
		...
		<properties>
		...
			<!-- the pattern for name is toplink.descriptor.customizer.EntityName value is classname that implements the descriptor customizer interface -->
			<property name="toplink.descriptor.customizer.Foo" value="fullyqualnamedtoDecriptorCustomizer"/>
		</properties>
	</peristence-unit>
</persistence>
Hope that helps.
-----Original Message-----
From: Jon Miller [mailto:jemiller_at_uchicago.edu] 
Sent: Friday, Feruary 23, 2007 10:38 AM
To: Glassfish Persistence List
Subject: Possible to map boolean property to "Y" and "N" values in database?
Hi all,
This is similar to a question I had regarding enums. I'm wondering if it's possible to map a boolean value to "Y"s and "N"s in the database? I'm guessing the answer is no, but, I figured I would ask just to make sure.
Jon