/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.translator.jpa;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type.PersistenceType;
import org.teiid.language.SQLConstants.Tokens;
import org.teiid.metadata.Column;
import org.teiid.metadata.ExtensionMetadataProperty;
import org.teiid.metadata.ForeignKey;
import org.teiid.metadata.KeyRecord;
import org.teiid.metadata.MetadataFactory;
import org.teiid.metadata.Table;
import org.teiid.translator.MetadataProcessor;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.TypeFacility;
/**
* TODO:
* - support of abstract entities is an issue, should we represent base and extended types, just extended types?
*
*/
@SuppressWarnings("nls")
public class JPAMetadataProcessor implements MetadataProcessor<EntityManager> {
@ExtensionMetadataProperty(applicable=Column.class, datatype=String.class, display="Foriegn Table Name", description="Applicable on Forign Key columns")
public static final String KEY_ASSOSIATED_WITH_FOREIGN_TABLE = MetadataFactory.JPA_URI+"assosiated_with_table";
@ExtensionMetadataProperty(applicable=Table.class, datatype=String.class, display="Entity Class", description="Java Entity Class that represents this table", required=true)
public static final String ENTITYCLASS= MetadataFactory.JPA_URI+"entity_class";
public void process(MetadataFactory mf, EntityManager entityManager) throws TranslatorException {
Metamodel model = entityManager.getMetamodel();
Set<EntityType<?>> entities = model.getEntities();
for (EntityType<?> entity:entities) {
addEntity(mf, model, entity);
}
// take a second swipe and add Foreign Keys
for (EntityType<?> entity:entities) {
Table t = mf.getSchema().getTable(entity.getName());
addForeignKeys(mf, model, entity, t);
}
}
private Table addEntity(MetadataFactory mf, Metamodel model, EntityType<?> entity) throws TranslatorException {
Table table = mf.getSchema().getTable(entity.getName());
if (table == null) {
table = mf.addTable(entity.getName());
table.setSupportsUpdate(true);
table.setProperty(ENTITYCLASS, entity.getJavaType().getCanonicalName());
addPrimaryKey(mf, model, entity, table);
addSingularAttributes(mf, model, entity, table);
}
return table;
}
private boolean columnExists(String name, Table table) {
return table.getColumnByName(name) != null;
}
private Column addColumn(MetadataFactory mf, String name, String type, Table entityTable) throws TranslatorException {
if (!columnExists(name, entityTable)) {
Column c = mf.addColumn(name, type, entityTable);
c.setUpdatable(true);
return c;
}
return entityTable.getColumnByName(name);
}
private void addForiegnKey(MetadataFactory mf, String name, List<String> columnNames, String referenceTable, Table table) throws TranslatorException {
ForeignKey fk = mf.addForiegnKey("FK_"+name, columnNames, referenceTable, table);
for (String column:columnNames) {
Column c = table.getColumnByName(column);
c.setProperty(KEY_ASSOSIATED_WITH_FOREIGN_TABLE, mf.getName()+Tokens.DOT+referenceTable);
}
fk.setNameInSource(name);
}
private void addSingularAttributes(MetadataFactory mf, Metamodel model, ManagedType<?> entity, Table entityTable) throws TranslatorException {
for (Attribute<?, ?> attr:entity.getAttributes()) {
if (!attr.isCollection()) {
boolean simpleType = isSimpleType(attr.getJavaType());
if (simpleType) {
Column column = addColumn(mf, attr.getName(), TypeFacility.getDataTypeName(getJavaDataType(attr.getJavaType())), entityTable);
if (((SingularAttribute)attr).isOptional()) {
column.setDefaultValue(null);
}
}
else {
boolean classFound = false;
// If this attribute is a @Embeddable then add its columns as
// this tables columns
for (EmbeddableType<?> embeddable:model.getEmbeddables()) {
if (embeddable.getJavaType().equals(attr.getJavaType())) {
addSingularAttributes(mf, model, embeddable, entityTable);
classFound = true;
break;
}
}
if (!classFound) {
// if the attribute is @Entity then add entity's PK as column on this
// table, then add that column as FK
for (EntityType et:model.getEntities()) {
if (et.getJavaType().equals(attr.getJavaType())) {
Table attributeTable = addEntity(mf, model, et);
KeyRecord pk = attributeTable.getPrimaryKey();
if (pk != null) { // TODO: entities must have PK, so this check is not needed.
ArrayList<String> keys = new ArrayList<String>();
for (Column column:pk.getColumns()) {
addColumn(mf, column.getName(), column.getDatatype().getRuntimeTypeName(), entityTable);
keys.add(column.getName());
}
if (!foreignKeyExists(keys, entityTable)) {
addForiegnKey(mf, attr.getName(), keys, attributeTable.getName(), entityTable);
}
}
else {
throw new TranslatorException(JPAPlugin.Util.gs(JPAPlugin.Event.TEIID14001, attributeTable.getName()));
}
classFound = true;
break;
}
}
}
if (!classFound) {
throw new TranslatorException(JPAPlugin.Util.gs(JPAPlugin.Event.TEIID14002, attr.getName()));
}
}
}
}
}
private boolean isSimpleType(Class type) {
return type.isPrimitive() || type.equals(String.class)
|| type.equals(BigDecimal.class) || type.equals(Date.class)
|| type.equals(BigInteger.class)
|| TypeFacility.getRuntimeType(type) != Object.class;
}
private void addForeignKeys(MetadataFactory mf, Metamodel model, ManagedType<?> entity, Table entityTable) throws TranslatorException {
for (Attribute<?, ?> attr:entity.getAttributes()) {
if (attr.isCollection()) {
PluralAttribute pa = (PluralAttribute)attr;
Table forignTable = null;
for (EntityType et:model.getEntities()) {
if (et.getJavaType().equals(pa.getElementType().getJavaType())) {
forignTable = mf.getSchema().getTable(et.getName());
break;
}
}
if (forignTable == null) {
continue;
}
// add foreign keys as columns in table first; check if they exist first
ArrayList<String> keys = new ArrayList<String>();
KeyRecord pk = entityTable.getPrimaryKey();
for (Column entityColumn:pk.getColumns()) {
addColumn(mf, entityColumn.getName(), entityColumn.getDatatype().getRuntimeTypeName(), forignTable);
keys.add(entityColumn.getName());
}
if (!foreignKeyExists(keys, forignTable)) {
addForiegnKey(mf, attr.getName(), keys, entityTable.getName(), forignTable);
}
}
}
}
private boolean foreignKeyExists(List<String> keys, Table forignTable) {
boolean fkExists = false;
for (ForeignKey fk:forignTable.getForeignKeys()) {
boolean allKeysFound = true;
for (String key:keys) {
boolean keyfound = false;
for (Column col:fk.getColumns()) {
if (col.getName().equals(key)) {
keyfound = true;
break;
}
}
if (!keyfound) {
allKeysFound = false;
break;
}
}
if (allKeysFound) {
fkExists = true;
break;
}
}
return fkExists;
}
private void addPrimaryKey(MetadataFactory mf, Metamodel model, EntityType<?> entity, Table entityTable) throws TranslatorException {
// figure out the PK
if (entity.hasSingleIdAttribute()) {
if (entity.getIdType().getPersistenceType().equals(PersistenceType.BASIC)) {
SingularAttribute<?, ?> pkattr = entity.getId(entity.getIdType().getJavaType());
addColumn(mf, pkattr.getName(), TypeFacility.getDataTypeName(getJavaDataType(pkattr.getJavaType())), entityTable);
mf.addPrimaryKey("PK_"+entity.getName(), Arrays.asList(pkattr.getName()), entityTable); //$NON-NLS-1$
}
else if (entity.getIdType().getPersistenceType().equals(PersistenceType.EMBEDDABLE)) {
SingularAttribute<?, ?> pkattr = entity.getId(entity.getIdType().getJavaType());
for (EmbeddableType<?> embeddable:model.getEmbeddables()) {
if (embeddable.getJavaType().equals(pkattr.getJavaType())) {
addSingularAttributes(mf, model, embeddable, entityTable);
ArrayList<String> keys = new ArrayList<String>();
for (Attribute<?, ?> attr:embeddable.getAttributes()) {
if (isSimpleType(attr.getJavaType())) {
keys.add(attr.getName());
}
else {
throw new TranslatorException(JPAPlugin.Util.gs(JPAPlugin.Event.TEIID14003, entityTable.getName()));
}
}
mf.addPrimaryKey("PK_"+pkattr.getName(), keys, entityTable);
break;
}
}
}
}
else {
// Composite PK. If the PK is specified with @IdClass then read its attributes,
// if those attributes are not found, add them as columns then as composite PK
ArrayList<String> keys = new ArrayList<String>();
for (Object obj:entity.getIdClassAttributes()) {
SingularAttribute<?, ?> attr = (SingularAttribute)obj;
addColumn(mf, attr.getName(), TypeFacility.getDataTypeName(getJavaDataType(attr.getJavaType())), entityTable);
keys.add(attr.getName());
}
mf.addPrimaryKey("PK_"+entity.getName(), keys, entityTable);
}
}
private Class getJavaDataType(Class type) {
return TypeFacility.getRuntimeType(type);
}
}