/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.jpa.bridge;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.sql.Types;
import java.util.Iterator;
import javax.persistence.InheritanceType;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.jpa.JpaProviderException;
import org.apache.cayenne.jpa.conf.EntityMapLoaderContext;
import org.apache.cayenne.jpa.map.AccessType;
import org.apache.cayenne.jpa.map.JpaAttribute;
import org.apache.cayenne.jpa.map.JpaAttributeOverride;
import org.apache.cayenne.jpa.map.JpaAttributes;
import org.apache.cayenne.jpa.map.JpaBasic;
import org.apache.cayenne.jpa.map.JpaColumn;
import org.apache.cayenne.jpa.map.JpaColumnResult;
import org.apache.cayenne.jpa.map.JpaDiscriminatorColumn;
import org.apache.cayenne.jpa.map.JpaEmbeddable;
import org.apache.cayenne.jpa.map.JpaEmbedded;
import org.apache.cayenne.jpa.map.JpaEntity;
import org.apache.cayenne.jpa.map.JpaEntityListener;
import org.apache.cayenne.jpa.map.JpaEntityListeners;
import org.apache.cayenne.jpa.map.JpaEntityMap;
import org.apache.cayenne.jpa.map.JpaEntityResult;
import org.apache.cayenne.jpa.map.JpaFieldResult;
import org.apache.cayenne.jpa.map.JpaId;
import org.apache.cayenne.jpa.map.JpaJoinColumn;
import org.apache.cayenne.jpa.map.JpaManagedClass;
import org.apache.cayenne.jpa.map.JpaManyToMany;
import org.apache.cayenne.jpa.map.JpaManyToOne;
import org.apache.cayenne.jpa.map.JpaNamedQuery;
import org.apache.cayenne.jpa.map.JpaOneToMany;
import org.apache.cayenne.jpa.map.JpaOneToOne;
import org.apache.cayenne.jpa.map.JpaPersistenceUnitDefaults;
import org.apache.cayenne.jpa.map.JpaPersistenceUnitMetadata;
import org.apache.cayenne.jpa.map.JpaPrimaryKeyJoinColumn;
import org.apache.cayenne.jpa.map.JpaQueryHint;
import org.apache.cayenne.jpa.map.JpaRelationship;
import org.apache.cayenne.jpa.map.JpaSecondaryTable;
import org.apache.cayenne.jpa.map.JpaSqlResultSetMapping;
import org.apache.cayenne.jpa.map.JpaTable;
import org.apache.cayenne.jpa.map.JpaVersion;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbJoin;
import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.Embeddable;
import org.apache.cayenne.map.EmbeddableAttribute;
import org.apache.cayenne.map.EmbeddedAttribute;
import org.apache.cayenne.map.Entity;
import org.apache.cayenne.map.EntityListener;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.project.ProjectPath;
import org.apache.cayenne.query.EntityResult;
import org.apache.cayenne.query.SQLResultSetMapping;
import org.apache.cayenne.util.BaseTreeVisitor;
import org.apache.cayenne.util.HierarchicalTreeVisitor;
import org.apache.cayenne.util.TraversalUtil;
import org.apache.cayenne.util.Util;
import org.apache.cayenne.validation.SimpleValidationFailure;
/**
* A converter between {@link org.apache.cayenne.jpa.map.JpaEntityMap} and Cayenne
* {@link org.apache.cayenne.map.DataMap}.
*
*/
public class DataMapConverter {
protected EntityMapLoaderContext context;
protected ProjectPath targetPath;
protected HierarchicalTreeVisitor visitor;
public synchronized DataMap toDataMap(String name, EntityMapLoaderContext context) {
this.context = context;
// reset
DataMap dataMap = new DataMap(name);
dataMap.setDefaultPackage(context.getEntityMap().getPackageName());
dataMap.setDefaultSchema(context.getEntityMap().getSchema());
this.targetPath = new ProjectPath(dataMap);
if (visitor == null) {
visitor = createVisitor();
}
TraversalUtil.traverse(context.getEntityMap(), visitor);
postprocess(dataMap);
return dataMap;
}
/**
* Connects missing reverse relationships.
*/
protected void postprocess(DataMap dataMap) {
// connect relationships paired via "mappedBy"; if any other reverse relationships
// are missing, Cayenne runtime downstream should take care of it
for (DbEntity entity : dataMap.getDbEntities()) {
Iterator<?> it = entity.getRelationships().iterator();
while (it.hasNext()) {
JpaDbRelationship relationship = (JpaDbRelationship) it.next();
if (relationship.getMappedBy() != null) {
DbRelationship owner = (DbRelationship) relationship
.getTargetEntity()
.getRelationship(relationship.getMappedBy());
if (owner != null) {
for (DbJoin join : owner.getJoins()) {
DbJoin reverse = join.createReverseJoin();
reverse.setRelationship(relationship);
relationship.addJoin(reverse);
}
}
}
}
}
}
protected void recordConflict(ProjectPath path, String message) {
context.recordConflict(new SimpleValidationFailure(path.getObject(), message));
}
/**
* Creates a stateless instance of the JpaEntityMap traversal visitor. This method is
* lazily invoked and cached by this object.
*/
protected HierarchicalTreeVisitor createVisitor() {
BaseTreeVisitor listenersVisitor = new BaseTreeVisitor();
listenersVisitor.addChildVisitor(
JpaEntityListener.class,
new JpaDefaultEntityListenerVisitor());
BaseTreeVisitor defaultsVisitor = new BaseTreeVisitor();
defaultsVisitor.addChildVisitor(JpaEntityListeners.class, listenersVisitor);
BaseTreeVisitor metadataVisitor = new BaseTreeVisitor();
metadataVisitor
.addChildVisitor(JpaPersistenceUnitDefaults.class, defaultsVisitor);
BaseTreeVisitor visitor = new BaseTreeVisitor();
visitor.addChildVisitor(JpaEntity.class, new JpaEntityVisitor());
visitor.addChildVisitor(JpaEmbeddable.class, new JpaEmbeddableVisitor());
visitor.addChildVisitor(JpaNamedQuery.class, new JpaNamedQueryVisitor());
visitor.addChildVisitor(JpaPersistenceUnitMetadata.class, metadataVisitor);
visitor.addChildVisitor(
JpaSqlResultSetMapping.class,
new JpaSQLResultSetMappingVisitor());
return visitor;
}
private void createDbAttribute(
String tableName,
JpaColumn column,
JpaAttribute attribute) {
DbAttribute dbAttribute = new DbAttribute(column.getName());
if (attribute instanceof JpaBasic) {
JpaBasic basic = (JpaBasic) attribute;
dbAttribute.setType(basic.getDefaultJdbcType());
}
else if (attribute instanceof JpaVersion) {
JpaVersion version = (JpaVersion) attribute;
dbAttribute.setType(version.getDefaultJdbcType());
}
dbAttribute.setMandatory(!column.isNullable());
dbAttribute.setMaxLength(column.getLength());
// DbAttribute "no scale" means -1, not 0 like in JPA.
if (column.getScale() > 0) {
dbAttribute.setScale(column.getScale());
}
// DbAttribute "no precision" means -1, not 0 like in JPA.
if (column.getPrecision() > 0) {
dbAttribute.setAttributePrecision(column.getPrecision());
}
DataMap dataMap = targetPath.firstInstanceOf(DataMap.class);
DbEntity entity = dataMap.getDbEntity(tableName);
if (entity == null) {
// table may be defined in a superclass that is not processed yet... so create
// a barebone version, with all remaining properties to be set later
entity = new DbEntity(tableName);
dataMap.addDbEntity(entity);
}
entity.addAttribute(dbAttribute);
}
private String getSecondaryTableDbRelationshipName(String secondaryTableName) {
return "$cay_secondary_" + secondaryTableName;
}
private EntityListener makeEntityListener(JpaEntityListener jpaListener) {
EntityListener listener = new EntityListener(jpaListener.getClassName());
if (jpaListener.getPostLoad() != null) {
listener.getCallbackMap().getPostLoad().addCallbackMethod(
jpaListener.getPostLoad().getMethodName());
}
if (jpaListener.getPostPersist() != null) {
listener.getCallbackMap().getPostPersist().addCallbackMethod(
jpaListener.getPostPersist().getMethodName());
}
if (jpaListener.getPostRemove() != null) {
listener.getCallbackMap().getPostRemove().addCallbackMethod(
jpaListener.getPostRemove().getMethodName());
}
if (jpaListener.getPostUpdate() != null) {
listener.getCallbackMap().getPostUpdate().addCallbackMethod(
jpaListener.getPostUpdate().getMethodName());
}
if (jpaListener.getPrePersist() != null) {
listener.getCallbackMap().getPrePersist().addCallbackMethod(
jpaListener.getPrePersist().getMethodName());
}
if (jpaListener.getPreRemove() != null) {
listener.getCallbackMap().getPreRemove().addCallbackMethod(
jpaListener.getPreRemove().getMethodName());
}
if (jpaListener.getPreUpdate() != null) {
listener.getCallbackMap().getPreUpdate().addCallbackMethod(
jpaListener.getPreUpdate().getMethodName());
}
return listener;
}
Field lookupFieldInHierarchy(Class<?> beanClass, String fieldName)
throws SecurityException, NoSuchFieldException {
try {
return beanClass.getDeclaredField(fieldName);
}
catch (NoSuchFieldException e) {
Class<?> superClass = beanClass.getSuperclass();
if (superClass == null || superClass.getName().equals(Object.class.getName())) {
throw e;
}
return lookupFieldInHierarchy(superClass, fieldName);
}
}
class JpaDefaultEntityListenerVisitor extends BaseTreeVisitor {
@Override
public boolean onStartNode(ProjectPath path) {
JpaEntityListener jpaListener = (JpaEntityListener) path.getObject();
DataMap map = (DataMap) targetPath.firstInstanceOf(DataMap.class);
EntityListener listener = makeEntityListener(jpaListener);
map.addDefaultEntityListener(listener);
return false;
}
}
class JpaEntityListenerVisitor extends BaseTreeVisitor {
@Override
public boolean onStartNode(ProjectPath path) {
JpaEntityListener jpaListener = (JpaEntityListener) path.getObject();
EntityListener listener = makeEntityListener(jpaListener);
ObjEntity entity = (ObjEntity) targetPath.firstInstanceOf(ObjEntity.class);
entity.addEntityListener(listener);
return false;
}
}
class JpaEmbeddedVisitor extends NestedVisitor {
@Override
Object createObject(ProjectPath path) {
JpaEmbedded jpaEmbedded = (JpaEmbedded) path.getObject();
ObjEntity entity = (ObjEntity) targetPath.getObject();
EmbeddedAttribute embedded = new EmbeddedAttribute(jpaEmbedded.getName());
embedded.setType(jpaEmbedded.getPropertyDescriptor().getType().getName());
for (JpaAttributeOverride override : jpaEmbedded.getAttributeOverrides()) {
embedded.addAttributeOverride(override.getName(), override
.getColumn()
.getName());
}
entity.addAttribute(embedded);
// for each embedded attribute, add all Embeddable attributes to DbEntity,
// honoring @Column settings
JpaEmbeddable jpaEmbeddable = path
.firstInstanceOf(JpaEntityMap.class)
.embeddableForClass(jpaEmbedded.getPropertyDescriptor().getType());
for (JpaBasic jpaBasic : jpaEmbeddable.getAttributes().getBasicAttributes()) {
JpaColumn column = jpaBasic.getColumn();
String tableName = column.getTable() != null ? column.getTable() : entity
.getDbEntityName();
createDbAttribute(tableName, jpaBasic.getColumn(), jpaBasic);
}
return embedded;
}
}
class JpaEmbeddableBasicVisitor extends NestedVisitor {
@Override
Object createObject(ProjectPath path) {
JpaBasic jpaBasic = (JpaBasic) path.getObject();
Embeddable embeddable = (Embeddable) targetPath.getObject();
EmbeddableAttribute attribute = new EmbeddableAttribute(jpaBasic.getName());
attribute.setType(getAttributeType(path, jpaBasic.getName()).getName());
attribute.setDbAttributeName(jpaBasic.getColumn().getName());
embeddable.addAttribute(attribute);
return attribute;
}
Class<?> getAttributeType(ProjectPath path, String name) {
AccessType access = null;
JpaManagedClass entity = path.firstInstanceOf(JpaManagedClass.class);
access = entity.getAccess();
if (access == null) {
JpaEntityMap map = path.firstInstanceOf(JpaEntityMap.class);
access = map.getAccess();
}
Class<?> objectClass = targetPath
.firstInstanceOf(Embeddable.class)
.getJavaClass();
try {
if (access == AccessType.FIELD) {
return lookupFieldInHierarchy(objectClass, name).getType();
}
else {
return new PropertyDescriptor(name, objectClass).getPropertyType();
}
}
catch (Exception e) {
throw new JpaProviderException("Error resolving attribute '"
+ name
+ "', access type:"
+ access
+ ", class: "
+ objectClass.getName(), e);
}
}
}
class JpaBasicVisitor extends NestedVisitor {
@Override
Object createObject(ProjectPath path) {
JpaManagedClass entity = path.firstInstanceOf(JpaManagedClass.class);
JpaBasic jpaBasic = (JpaBasic) path.getObject();
ObjEntity parentCayenneEntity = (ObjEntity) targetPath.getObject();
ObjAttribute cayenneAttribute = new ObjAttribute(jpaBasic.getName());
cayenneAttribute
.setType(getAttributeType(path, jpaBasic.getName()).getName());
cayenneAttribute.setDbAttributePath(getAttributePath(path, entity, jpaBasic
.getColumn()));
parentCayenneEntity.addAttribute(cayenneAttribute);
return cayenneAttribute;
}
Class<?> getAttributeType(ProjectPath path, String name) {
AccessType access = null;
JpaManagedClass entity = path.firstInstanceOf(JpaManagedClass.class);
access = entity.getAccess();
if (access == null) {
JpaEntityMap map = path.firstInstanceOf(JpaEntityMap.class);
access = map.getAccess();
}
Class<?> objectClass = targetPath
.firstInstanceOf(ObjEntity.class)
.getJavaClass();
try {
if (access == AccessType.FIELD) {
return lookupFieldInHierarchy(objectClass, name).getType();
}
else {
return new PropertyDescriptor(name, objectClass).getPropertyType();
}
}
catch (Exception e) {
throw new JpaProviderException("Error resolving attribute '"
+ name
+ "', access type:"
+ access
+ ", class: "
+ objectClass.getName(), e);
}
}
protected String getAttributePath(
ProjectPath path,
JpaManagedClass managedClass,
JpaColumn column) {
if (managedClass instanceof JpaEntity) {
JpaEntity entity = (JpaEntity) managedClass;
if (column.getTable().equals(entity.lookupTable().getName())) {
return column.getName();
}
JpaSecondaryTable table = entity.getSecondaryTable(column.getTable());
if (table == null) {
recordConflict(path, "Unrecognized secondary table: '"
+ column.getTable()
+ "'");
return column.getName();
}
return getSecondaryTableDbRelationshipName(table.getName())
+ '.'
+ column.getName();
}
else {
// TODO: andrus, 12/23/2007 this would miss a case if a user decides to
// specify a secondary table on an abstract superclass that is not linked
// to a table yet ... which would be quite crazy, but still...
return column.getName();
}
}
}
class JpaVersionVisitor extends JpaBasicVisitor {
@Override
Object createObject(ProjectPath path) {
JpaManagedClass entity = path.firstInstanceOf(JpaManagedClass.class);
JpaVersion version = (JpaVersion) path.getObject();
ObjEntity parentCayenneEntity = (ObjEntity) targetPath.getObject();
ObjAttribute cayenneAttribute = new ObjAttribute(version.getName());
cayenneAttribute.setType(getAttributeType(path, version.getName()).getName());
cayenneAttribute.setDbAttributePath(getAttributePath(path, entity, version
.getColumn()));
parentCayenneEntity.addAttribute(cayenneAttribute);
return cayenneAttribute;
}
}
class JpaColumnVisitor extends BaseTreeVisitor {
@Override
public boolean onStartNode(ProjectPath path) {
JpaColumn jpaColumn = (JpaColumn) path.getObject();
if (jpaColumn.getTable() == null) {
throw new JpaProviderException("No default table defined for JpaColumn "
+ jpaColumn.getName());
}
JpaAttribute attribute = (JpaAttribute) path.getObjectParent();
createDbAttribute(jpaColumn.getTable(), jpaColumn, attribute);
return false;
}
}
class JpaIdVisitor extends JpaBasicVisitor {
@Override
Object createObject(ProjectPath path) {
JpaId id = (JpaId) path.getObject();
ObjEntity parentCayenneEntity = (ObjEntity) targetPath.getObject();
ObjAttribute cayenneAttribute = new ObjAttribute(id.getName());
cayenneAttribute.setType(getAttributeType(path, id.getName()).getName());
// assuming id's can not be flattened to another table...
cayenneAttribute.setDbAttributePath(id.getColumn().getName());
parentCayenneEntity.addAttribute(cayenneAttribute);
return cayenneAttribute;
}
}
class JpaIdColumnVisitor extends BaseTreeVisitor {
@Override
public boolean onStartNode(ProjectPath path) {
JpaColumn jpaColumn = (JpaColumn) path.getObject();
DbAttribute dbAttribute = new DbAttribute(jpaColumn.getName());
JpaId jpaId = path.firstInstanceOf(JpaId.class);
dbAttribute.setType(jpaId.getDefaultJdbcType());
dbAttribute.setMaxLength(jpaColumn.getLength());
dbAttribute.setMandatory(true);
dbAttribute.setPrimaryKey(true);
if (jpaColumn.getScale() > 0) {
dbAttribute.setScale(jpaColumn.getScale());
}
if (jpaColumn.getPrecision() > 0) {
dbAttribute.setAttributePrecision(jpaColumn.getPrecision());
}
if (jpaColumn.getTable() == null) {
recordConflict(path, "No table defined for JpaColumn '"
+ jpaColumn.getName()
+ "'");
return false;
}
DbEntity entity = targetPath.firstInstanceOf(DataMap.class).getDbEntity(
jpaColumn.getTable());
if (entity == null) {
recordConflict(path, "Invalid table definition for JpaColumn: "
+ jpaColumn.getTable()
+ "'");
return false;
}
entity.addAttribute(dbAttribute);
return false;
}
}
class JpaJoinColumnVisitor extends BaseTreeVisitor {
@Override
public boolean onStartNode(ProjectPath path) {
JpaJoinColumn jpaJoin = (JpaJoinColumn) path.getObject();
JpaRelationship jpaRelationship = (JpaRelationship) path.getObjectParent();
JpaEntity targetEntity = context.getEntityMap().entityForClass(
jpaRelationship.getTargetEntityName());
JpaId jpaTargetId = targetEntity.getAttributes().getIdForColumnName(
jpaJoin.getReferencedColumnName());
if (jpaTargetId == null) {
throw new IllegalArgumentException("Null id "
+ targetEntity.getName()
+ "."
+ jpaJoin.getReferencedColumnName());
}
DbRelationship dbRelationship = (DbRelationship) targetPath.getObject();
// add FK
DbAttribute src = new DbAttribute(jpaJoin.getName());
// TODO: andrus, 5/2/2006 - infer this from Jpa relationship
src.setMandatory(false);
src.setMaxLength(jpaTargetId.getColumn().getLength());
src.setType(jpaTargetId.getDefaultJdbcType());
Entity srcEntity = dbRelationship.getSourceEntity();
srcEntity.addAttribute(src);
// add join
DbJoin join = new DbJoin(dbRelationship, src.getName(), jpaTargetId
.getColumn()
.getName());
dbRelationship.addJoin(join);
return false;
}
}
class JpaEmbeddableVisitor extends NestedVisitor {
JpaEmbeddableVisitor() {
BaseTreeVisitor attributeVisitor = new BaseTreeVisitor();
JpaEmbeddableBasicVisitor basicVisitor = new JpaEmbeddableBasicVisitor();
attributeVisitor.addChildVisitor(JpaBasic.class, basicVisitor);
addChildVisitor(JpaAttributes.class, attributeVisitor);
}
@Override
Object createObject(ProjectPath path) {
JpaEmbeddable jpaEmbeddable = (JpaEmbeddable) path.getObject();
Embeddable embeddable = new Embeddable(jpaEmbeddable.getClassName());
((DataMap) targetPath.getObject()).addEmbeddable(embeddable);
return embeddable;
}
}
class JpaSQLResultSetMappingVisitor extends BaseTreeVisitor {
@Override
public boolean onStartNode(ProjectPath path) {
JpaSqlResultSetMapping jpaMapping = (JpaSqlResultSetMapping) path.getObject();
SQLResultSetMapping mapping = new SQLResultSetMapping(jpaMapping.getName());
for (JpaColumnResult c : jpaMapping.getColumnResults()) {
mapping.addColumnResult(c.getName());
}
for (JpaEntityResult e : jpaMapping.getEntityResults()) {
EntityResult result = new EntityResult(e.getEntityClassName());
for (JpaFieldResult f : e.getFieldResults()) {
result.addObjectField(f.getName(), f.getColumn());
}
// TODO: andrus 2/23/2008 - discriminator column...
}
// my understanding of JPA SQLResultSetMapping is that regardless of whether
// it belongs to entity or to the entire persistence unit, the scope is still
// persistence unit??
targetPath.firstInstanceOf(DataMap.class).addResultSetMapping(mapping);
return false;
}
}
class JpaEntityVisitor extends NestedVisitor {
JpaEntityVisitor() {
BaseTreeVisitor listenersVisitor = new BaseTreeVisitor();
listenersVisitor.addChildVisitor(
JpaEntityListener.class,
new JpaEntityListenerVisitor());
BaseTreeVisitor attributeVisitor = new BaseTreeVisitor();
attributeVisitor.addChildVisitor(
JpaManyToOne.class,
new JpaManyToOneVisitor());
attributeVisitor.addChildVisitor(JpaOneToOne.class, new JpaOneToOneVisitor());
attributeVisitor.addChildVisitor(
JpaOneToMany.class,
new JpaOneToManyVisitor());
attributeVisitor.addChildVisitor(
JpaManyToMany.class,
new JpaManyToManyVisitor());
JpaBasicVisitor basicVisitor = new JpaBasicVisitor();
basicVisitor.addChildVisitor(JpaColumn.class, new JpaColumnVisitor());
attributeVisitor.addChildVisitor(JpaBasic.class, basicVisitor);
JpaEmbeddedVisitor embeddedVisitor = new JpaEmbeddedVisitor();
attributeVisitor.addChildVisitor(JpaEmbedded.class, embeddedVisitor);
JpaVersionVisitor versionVisitor = new JpaVersionVisitor();
versionVisitor.addChildVisitor(JpaColumn.class, new JpaColumnVisitor());
attributeVisitor.addChildVisitor(JpaVersion.class, versionVisitor);
JpaIdVisitor idVisitor = new JpaIdVisitor();
idVisitor.addChildVisitor(JpaColumn.class, new JpaIdColumnVisitor());
attributeVisitor.addChildVisitor(JpaId.class, idVisitor);
// TODO: andrus 8/6/2006 - handle EmbeddedId, AttributeOverride
addChildVisitor(JpaAttributes.class, attributeVisitor);
addChildVisitor(JpaTable.class, new JpaTableVisitor());
addChildVisitor(JpaSecondaryTable.class, new JpaSecondaryTableVisitor());
addChildVisitor(JpaNamedQuery.class, new JpaNamedQueryVisitor());
addChildVisitor(JpaEntityListeners.class, listenersVisitor);
addChildVisitor(
JpaSqlResultSetMapping.class,
new JpaSQLResultSetMappingVisitor());
}
@Override
Object createObject(ProjectPath path) {
JpaEntity jpaEntity = (JpaEntity) path.getObject();
ObjEntity cayenneEntity = new ObjEntity(jpaEntity.getName());
if (jpaEntity.getInheritance() == null
&& jpaEntity.lookupInheritanceStrategy() == InheritanceType.SINGLE_TABLE) {
cayenneEntity.setSuperEntityName(jpaEntity.getSuperEntity().getName());
}
cayenneEntity.setClassName(jpaEntity.getClassName());
initCallbacks(jpaEntity, cayenneEntity);
((DataMap) targetPath.getObject()).addObjEntity(cayenneEntity);
return cayenneEntity;
}
private void initCallbacks(JpaEntity jpaEntity, ObjEntity cayenneEntity) {
if (jpaEntity.getPostLoad() != null) {
cayenneEntity.getCallbackMap().getPostLoad().addCallbackMethod(
jpaEntity.getPostLoad().getMethodName());
}
if (jpaEntity.getPostPersist() != null) {
cayenneEntity.getCallbackMap().getPostPersist().addCallbackMethod(
jpaEntity.getPostPersist().getMethodName());
}
if (jpaEntity.getPostRemove() != null) {
cayenneEntity.getCallbackMap().getPostRemove().addCallbackMethod(
jpaEntity.getPostRemove().getMethodName());
}
if (jpaEntity.getPostUpdate() != null) {
cayenneEntity.getCallbackMap().getPostUpdate().addCallbackMethod(
jpaEntity.getPostUpdate().getMethodName());
}
if (jpaEntity.getPrePersist() != null) {
cayenneEntity.getCallbackMap().getPrePersist().addCallbackMethod(
jpaEntity.getPrePersist().getMethodName());
}
if (jpaEntity.getPreRemove() != null) {
cayenneEntity.getCallbackMap().getPreRemove().addCallbackMethod(
jpaEntity.getPreRemove().getMethodName());
}
if (jpaEntity.getPreUpdate() != null) {
cayenneEntity.getCallbackMap().getPreUpdate().addCallbackMethod(
jpaEntity.getPreUpdate().getMethodName());
}
}
@Override
public void onFinishNode(ProjectPath path) {
JpaEntity entity = path.firstInstanceOf(JpaEntity.class);
DataMap dataMap = targetPath.firstInstanceOf(DataMap.class);
ObjEntity cayenneEntity = targetPath.firstInstanceOf(ObjEntity.class);
// as superentity may not be loaded yet, must lookup DbEntity via JPA
// mapping...
DbEntity cayennePrimaryTable = dataMap.getDbEntity(entity
.lookupTable()
.getName());
for (JpaSecondaryTable secondaryTable : entity.getSecondaryTables()) {
// create a relationship between master DbEntity and a secondary
// DbEntity...
DbEntity cayenneSecondaryTable = dataMap.getDbEntity(secondaryTable
.getName());
JpaDbRelationship dbRelationship = new JpaDbRelationship(
getSecondaryTableDbRelationshipName(secondaryTable.getName()));
dbRelationship.setTargetEntityName(secondaryTable.getName());
cayennePrimaryTable.addRelationship(dbRelationship);
for (JpaPrimaryKeyJoinColumn column : secondaryTable
.getPrimaryKeyJoinColumns()) {
DbAttribute pkAttribute = (DbAttribute) cayennePrimaryTable
.getAttribute(column.getReferencedColumnName());
if (pkAttribute == null) {
recordConflict(path, "Invalid referenced column name: "
+ column.getReferencedColumnName());
continue;
}
DbAttribute attribute = new DbAttribute(column.getName());
attribute.setPrimaryKey(true);
attribute.setMandatory(true);
attribute.setAttributePrecision(pkAttribute.getAttributePrecision());
attribute.setType(pkAttribute.getType());
attribute.setMaxLength(pkAttribute.getMaxLength());
attribute.setAttributePrecision(pkAttribute.getAttributePrecision());
cayenneSecondaryTable.addAttribute(attribute);
DbJoin join = new DbJoin(dbRelationship, column
.getReferencedColumnName(), column.getName());
dbRelationship.addJoin(join);
}
dbRelationship.setToDependentPK(true);
dbRelationship.setToMany(false);
}
// init discriminator column
JpaDiscriminatorColumn discriminator = entity.lookupDiscriminatorColumn();
if (discriminator != null) {
if (cayennePrimaryTable.getAttribute(discriminator.getName()) == null) {
DbAttribute dbAttribute = new DbAttribute(discriminator.getName());
switch (discriminator.getDiscriminatorType()) {
case CHAR:
dbAttribute.setType(Types.CHAR);
dbAttribute.setMaxLength(1);
break;
case STRING:
dbAttribute.setType(Types.VARCHAR);
dbAttribute.setMaxLength(discriminator.getLength());
break;
case INTEGER:
dbAttribute.setType(Types.INTEGER);
break;
}
dbAttribute.setMandatory(false);
cayennePrimaryTable.addAttribute(dbAttribute);
}
String valueString = entity.getDiscriminatorValue();
if (valueString != null && valueString.length() > 0) {
Object value = null;
switch (discriminator.getDiscriminatorType()) {
case CHAR:
value = valueString.charAt(0);
break;
case STRING:
value = valueString;
break;
case INTEGER:
try {
value = Integer.valueOf(valueString);
}
catch (NumberFormatException e) {
recordConflict(
path,
"Invalid integer discriminator value '"
+ valueString);
}
break;
}
if (value != null) {
cayenneEntity.setDeclaredQualifier(ExpressionFactory.matchDbExp(
discriminator.getName(),
value));
}
}
}
super.onFinishNode(path);
}
}
class JpaManyToOneVisitor extends JpaRelationshipVisitor {
@Override
Object createObject(ProjectPath path) {
ObjRelationship objRelationship = (ObjRelationship) super.createObject(path);
return objRelationship.getDbRelationships().get(0);
}
}
class JpaOneToManyVisitor extends JpaRelationshipVisitor {
@Override
Object createObject(ProjectPath path) {
ObjRelationship objRelationship = (ObjRelationship) super.createObject(path);
JpaDbRelationship relationship = (JpaDbRelationship) objRelationship
.getDbRelationships()
.get(0);
if (relationship != null) {
JpaOneToMany jpaRelationship = (JpaOneToMany) path.getObject();
relationship.setMappedBy(jpaRelationship.getMappedBy());
objRelationship.setMapKey(jpaRelationship.getMapKey());
objRelationship.setCollectionType(jpaRelationship
.getPropertyDescriptor()
.getType()
.getName());
}
return relationship;
}
}
class JpaOneToOneVisitor extends JpaRelationshipVisitor {
@Override
Object createObject(ProjectPath path) {
ObjRelationship objRelationship = (ObjRelationship) super.createObject(path);
JpaDbRelationship relationship = (JpaDbRelationship) objRelationship
.getDbRelationships()
.get(0);
if (relationship != null) {
JpaOneToOne jpaRelationship = (JpaOneToOne) path.getObject();
relationship.setMappedBy(jpaRelationship.getMappedBy());
}
return relationship;
}
}
class JpaManyToManyVisitor extends JpaRelationshipVisitor {
@Override
Object createObject(ProjectPath path) {
ObjRelationship objRelationship = (ObjRelationship) super.createObject(path);
JpaDbRelationship relationship = (JpaDbRelationship) objRelationship
.getDbRelationships()
.get(0);
if (relationship != null) {
JpaManyToMany jpaRelationship = (JpaManyToMany) path.getObject();
relationship.setMappedBy(jpaRelationship.getMappedBy());
}
return relationship;
}
}
abstract class JpaRelationshipVisitor extends NestedVisitor {
JpaRelationshipVisitor() {
addChildVisitor(JpaJoinColumn.class, new JpaJoinColumnVisitor());
}
@Override
Object createObject(ProjectPath path) {
JpaRelationship relationship = (JpaRelationship) path.getObject();
ObjEntity cayenneSrcEntity = (ObjEntity) targetPath.getObject();
ObjRelationship cayenneRelationship = new ObjRelationship(relationship
.getName());
cayenneSrcEntity.addRelationship(cayenneRelationship);
JpaEntity jpaTargetEntity = ((JpaEntityMap) path.getRoot())
.entityForClass(relationship.getTargetEntityName());
if (jpaTargetEntity == null) {
recordConflict(path, "Unknown target entity '"
+ relationship.getTargetEntityName());
return null;
}
cayenneRelationship.setTargetEntityName(jpaTargetEntity.getName());
DbEntity cayenneSrcDbEntity = cayenneSrcEntity.getDbEntity();
DbEntity cayenneTargetDbEntity = cayenneSrcEntity.getDataMap().getDbEntity(
jpaTargetEntity.getTable().getName());
if (cayenneTargetDbEntity == null) {
cayenneTargetDbEntity = new DbEntity(jpaTargetEntity.getTable().getName());
cayenneSrcEntity.getDataMap().addDbEntity(cayenneTargetDbEntity);
}
JpaDbRelationship dbRelationship = new JpaDbRelationship(cayenneRelationship
.getName());
dbRelationship.setTargetEntity(cayenneTargetDbEntity);
dbRelationship.setToMany(relationship.isToMany());
cayenneSrcDbEntity.addRelationship(dbRelationship);
cayenneRelationship.addDbRelationship(dbRelationship);
return cayenneRelationship;
}
}
class JpaNamedQueryVisitor extends NestedVisitor {
@Override
Object createObject(ProjectPath path) {
JpaNamedQuery jpaQuery = (JpaNamedQuery) path.getObject();
JpaIndirectQuery cayenneQuery;
JpaQueryHint hint = jpaQuery.getHint(QueryHints.QUERY_TYPE_HINT);
if (hint != null && !Util.isEmptyString(hint.getValue())) {
try {
// query class is not enhanced, so use normal class loader
Class<?> cayenneQueryClass = Class.forName(
hint.getValue(),
true,
Thread.currentThread().getContextClassLoader());
if (!JpaIndirectQuery.class.isAssignableFrom(cayenneQueryClass)) {
recordConflict(path, "Unknown type for Cayenne query '"
+ jpaQuery.getName()
+ "': "
+ cayenneQueryClass.getName());
return null;
}
cayenneQuery = (JpaIndirectQuery) cayenneQueryClass.newInstance();
}
catch (Exception e) {
recordConflict(path, "Problem while creating Cayenne query '"
+ jpaQuery.getName()
+ "', exception"
+ e.getMessage());
return null;
}
}
else {
// by default use EJBQL query...
cayenneQuery = new JpaEjbQLQuery();
}
cayenneQuery.setName(jpaQuery.getName());
cayenneQuery.setJpaQuery(jpaQuery);
DataMap parentMap = (DataMap) targetPath.firstInstanceOf(DataMap.class);
ObjEntity parentEntity = (ObjEntity) targetPath
.firstInstanceOf(ObjEntity.class);
if (parentEntity != null) {
cayenneQuery.setParentEntity(parentEntity);
}
else {
cayenneQuery.setParentMap(parentMap);
}
parentMap.addQuery(cayenneQuery);
return cayenneQuery;
}
}
class JpaTableVisitor extends NestedVisitor {
@Override
Object createObject(ProjectPath path) {
JpaTable jpaTable = (JpaTable) path.getObject();
ObjEntity parentCayenneEntity = (ObjEntity) targetPath.getObject();
DbEntity cayenneEntity = parentCayenneEntity.getDataMap().getDbEntity(
jpaTable.getName());
if (cayenneEntity == null) {
cayenneEntity = new DbEntity(jpaTable.getName());
parentCayenneEntity.getDataMap().addDbEntity(cayenneEntity);
}
// override catalog and schema even if this is an existing entity. See for
// instance JpaColumnVisitor for an example on how an entity without all
// properties is created early.
cayenneEntity.setCatalog(jpaTable.getCatalog());
cayenneEntity.setSchema(jpaTable.getSchema());
parentCayenneEntity.setDbEntity(cayenneEntity);
return cayenneEntity;
}
}
class JpaSecondaryTableVisitor extends NestedVisitor {
@Override
Object createObject(ProjectPath path) {
JpaSecondaryTable jpaTable = (JpaSecondaryTable) path.getObject();
ObjEntity parentCayenneEntity = (ObjEntity) targetPath.getObject();
DbEntity secondaryEntity = parentCayenneEntity.getDataMap().getDbEntity(
jpaTable.getName());
if (secondaryEntity == null) {
secondaryEntity = new DbEntity(jpaTable.getName());
parentCayenneEntity.getDataMap().addDbEntity(secondaryEntity);
}
secondaryEntity.setCatalog(jpaTable.getCatalog());
secondaryEntity.setSchema(jpaTable.getSchema());
// defer primary./secondary relationship creation till after parent entity's
// children are fully parsed...
return secondaryEntity;
}
}
/**
* A superclass of visitors that need to push/pop processed object from the stack.
*/
abstract class NestedVisitor extends BaseTreeVisitor {
abstract Object createObject(ProjectPath path);
@Override
public boolean onStartNode(ProjectPath path) {
Object object = createObject(path);
if (object != null) {
targetPath = targetPath.appendToPath(object);
return true;
}
else {
return false;
}
}
@Override
public void onFinishNode(ProjectPath path) {
targetPath = targetPath.subpathWithSize(targetPath.getPath().length - 1);
}
}
}