/*
* This file is part of the GeoLatte project.
*
* GeoLatte is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* GeoLatte is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with GeoLatte. If not, see <http://www.gnu.org/licenses/>.
*
* Copyright (C) 2010 - 2012 and Ownership of code is shared by:
* Qmino bvba - Romeinsestraat 18 - 3001 Heverlee (http://www.qmino.com)
* Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
*/
package org.geolatte.common.automapper;
import javassist.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.ProtectionDomain;
/**
* Generates POJO Classes from <code>TableMetaData</code>.
* <p/>
* <p>The <code>MappedClassGenerator</code> also loads the generated classes in a <code>ClassLoader</code>, and records
* the details of the mapping between table and POJO Class in a <code>TableMapping</code> instance.</p>
*
* @author Karel Maesen, Geovise BVBA
*/
class MappedClassGenerator {
final private static Logger LOGGER = LoggerFactory.getLogger(MappedClassGenerator.class);
final private static ClassPool pool = ClassPool.getDefault();
static {
pool.insertClassPath(new ClassClassPath(MappedClassGenerator.class));
}
final private String packageName;
final private NamingStrategy naming;
final private TypeMapper typeMapper;
/**
* Constructs an instance
*
* @param packageName the name of the package to use for classes generated by this instance.
* @param naming the <code>NamingStrategy</code> to use
* @param typeMapper the <code>TypeMapper</code> to use
*/
MappedClassGenerator(String packageName, NamingStrategy naming, TypeMapper typeMapper) {
this.packageName = packageName;
this.naming = naming;
this.typeMapper = typeMapper;
}
/**
* Creates a <code>TableMapping</code> for the specified <code>TableMetaData</code>.
*
* @param tableMetaData the <code>TableMetaData</code> for which to construct a <code>TableMapping</code>.
* @param classLoader the <code>ClassLoader</code> into which the POJO class
* @return a <code>TableMapping</code> that contains the generated POJO class
*/
TableMapping generate(TableMetaData tableMetaData, ClassLoader classLoader) {
try {
String className = packageName + "." + naming.createClassName(tableMetaData.getTableRef());
LOGGER.info(String.format("Mapping table %s to %s", tableMetaData.getTableRef(), className));
TableMapping result = new TableMapping(tableMetaData);
CtClass ctClass = pool.makeClass(className);
for (ColumnMetaData ai : tableMetaData.getColumnMetaData()) {
generatePropertyForAttribute(result, ctClass, ai);
}
Class<?> clazz = loadClass(classLoader, ctClass);
result.setGeneratedClass(clazz);
return result;
} catch (CannotCompileException e) {
throw new RuntimeException("Problem generating class for table " + tableMetaData.getTableRef(), e);
}
}
private Class<?> loadClass(ClassLoader classLoader, CtClass ctClass) throws CannotCompileException {
ProtectionDomain pd = ctClass.getClass().getProtectionDomain();
Class<?> clazz = ctClass.toClass(classLoader, pd);
ctClass.detach();
return clazz;
}
private void generatePropertyForAttribute(TableMapping tableMapping, CtClass pojo, ColumnMetaData ai) {
try {
CtClass ctClass = getCtClass(ai);
String propertyName = naming.createPropertyName(ai.getColumnName());
CtField field = new CtField(ctClass, propertyName, pojo);
CtMethod getter = createGetterMethod(field);
CtMethod setter = createSetterMethod(field);
pojo.addField(field);
pojo.addMethod(getter);
pojo.addMethod(setter);
tableMapping.addColumnMapping(ai, propertyName,
typeMapper.getHibernateType(ai.getDbTypeName(), ai.getSqlType()),
typeMapper.getClass(ai.getDbTypeName(), ai.getSqlType()));
return;
} catch (CannotCompileException e) {
LOGGER.warn("Error compiling getter/setter methods for column: " + ai.getColumnName(), e);
} catch (TypeNotFoundException e) {
LOGGER.warn(String.format("Cannot match type for column %s (sql-type %d).", ai.getColumnName(), ai.getSqlType()));
}
LOGGER.warn("No property included in mapped class corresponding to column " + ai.getColumnName());
}
private CtClass getCtClass(ColumnMetaData ai) throws TypeNotFoundException {
Class<?> javaClass = typeMapper.getClass(ai.getDbTypeName(), ai.getSqlType());
if (javaClass == null) {
throw new TypeNotFoundException(String.format("No type found in Typemapper for: %s (java.sql.Types: %s).",
ai.getDbTypeName(), ai.getSqlType()));
}
try {
return pool.get(javaClass.getCanonicalName());
} catch (NotFoundException e) {
throw new TypeNotFoundException(javaClass.getCanonicalName(), e);
}
}
private CtMethod createGetterMethod(CtField field) throws CannotCompileException {
String fn = field.getName();
return CtNewMethod.getter(this.naming.createGetterName(fn), field);
}
private CtMethod createSetterMethod(CtField field) throws CannotCompileException {
String fn = field.getName();
return CtNewMethod.setter(naming.createSetterName(fn), field);
}
}