Author: aadamchik
Date: Tue Dec 11 18:22:35 2012
New Revision: 1420322
URL: http://svn.apache.org/viewvc?rev=1420322&view=rev
Log:
CAY-1779 Flatten object entities for many to many relationships on reverse
engineering
patch by Ilya Drabenia - combined
Added:
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml
Modified:
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java
Modified:
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java
URL:
http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java?rev=1420322&r1=1420321&r2=1420322&view=diff
==============================================================================
---
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java
(original)
+++
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/DbLoader.java
Tue Dec 11 18:22:35 2012
@@ -36,6 +36,7 @@ import java.util.Set;
import org.apache.cayenne.CayenneException;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.dba.TypesMapping;
+
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
@@ -121,7 +122,7 @@ public class DbLoader {
/**
* Creates new DbLoader with specified naming strategy.
- *
+ *
* @since 3.0
*/
public DbLoader(Connection connection, DbAdapter adapter,
@@ -152,7 +153,7 @@ public class DbLoader {
/**
* Returns true if the generator should map all primary key columns as
* ObjAttributes.
- *
+ *
* @since 3.0
*/
public boolean isCreatingMeaningfulPK() {
@@ -161,7 +162,7 @@ public class DbLoader {
/**
* Returns database connection used by this DbLoader.
- *
+ *
* @since 3.0
*/
public Connection getConnection() {
@@ -174,7 +175,7 @@ public class DbLoader {
* {@link org.apache.cayenne.CayenneDataObject}. If generic class name is
* null (which is the default), DbLoader will assign each entity a unique
* class name derived from the table name.
- *
+ *
* @since 1.2
*/
public String getGenericClassName() {
@@ -187,7 +188,7 @@ public class DbLoader {
* {@link org.apache.cayenne.CayenneDataObject}. If generic class name is
* set to null (which is the default), DbLoader will assign each entity a
* unique class name derived from the table name.
- *
+ *
* @since 1.2
*/
public void setGenericClassName(String genericClassName) {
@@ -196,7 +197,7 @@ public class DbLoader {
/**
* Returns DbAdapter associated with this DbLoader.
- *
+ *
* @since 1.1
*/
public DbAdapter getAdapter() {
@@ -213,7 +214,7 @@ public class DbLoader {
/**
* Retrieves catalogs for the database associated with this DbLoader.
- *
+ *
* @return List with the catalog names, empty Array if none found.
*/
public List<String> getCatalogs() throws SQLException {
@@ -233,7 +234,7 @@ public class DbLoader {
/**
* Retrieves the schemas for the database.
- *
+ *
* @return List with the schema names, empty Array if none found.
*/
public List<String> getSchemas() throws SQLException {
@@ -254,7 +255,7 @@ public class DbLoader {
/**
* Returns all the table types for the given database. Types may be such as
* "TABLE", "VIEW", "SYSTEM TABLE", etc.
- *
+ *
* @return List of Strings, empty array if nothing found.
*/
public List<String> getTableTypes() throws SQLException {
@@ -274,7 +275,7 @@ public class DbLoader {
/**
* Returns all tables for given combination of the criteria. Tables
returned
* as DbEntities without any attributes or relationships.
- *
+ *
* @param catalogPattern
* The name of the catalog, may be null.
* @param schemaPattern
@@ -344,7 +345,7 @@ public class DbLoader {
/**
* Loads dbEntities for the specified tables.
- *
+ *
* @param map
* DataMap to be populated with DbEntities.
* @param tables
@@ -557,17 +558,13 @@ public class DbLoader {
+ objEntity.getName());
map.addObjEntity(objEntity);
loadedEntities.add(objEntity);
- // added entity without attributes or relationships...
- if (delegate != null) {
- delegate.objEntityAdded(objEntity);
- }
}
// update ObjEntity attributes and relationships
EntityMergeSupport objEntityMerger = createEntityMerger(map);
objEntityMerger.synchronizeWithDbEntities(loadedEntities);
}
-
+
/**
* @since 3.2
*/
@@ -753,6 +750,37 @@ public class DbLoader {
}
/**
+ * Method remove temporary entities from dataMap and optimize relationships
+ * @param map
+ */
+ private void optimizeObjRelationships(DataMap map) {
+ List<ObjEntity> entitiesForDelete = new ArrayList<ObjEntity>();
+
+ for (ObjEntity curEntity : map.getObjEntities()) {
+ ManyToManyCandidateEntity entity = new
ManyToManyCandidateEntity(curEntity);
+
+ if (entity.isRepresentManyToManyTable()) {
+ entity.optimizeRelationships();
+ entitiesForDelete.add(curEntity);
+ }
+ }
+
+ // remove needed entities
+ for (ObjEntity curDeleteEntity : entitiesForDelete) {
+ map.removeObjEntity(curDeleteEntity.getName(), true);
+ }
+ }
+
+ private void fireObjEntitiesAddedEvents(DataMap map) {
+ for (ObjEntity curEntity : map.getObjEntities()) {
+ // notify delegate
+ if (delegate != null) {
+ delegate.objEntityAdded(curEntity);
+ }
+ }
+ }
+
+ /**
* @since 3.2
*/
public String[] getDefaultTableTypes() {
@@ -777,7 +805,7 @@ public class DbLoader {
* Performs database reverse engineering and generates DataMap that
contains
* default mapping of the tables and views. By default will include regular
* tables and views.
- *
+ *
* @since 1.0.7
* @deprecated since 3.2 use
* {@link #load(DataMap, String, String, String, String...)}
@@ -799,22 +827,33 @@ public class DbLoader {
* Performs database reverse engineering and generates DataMap object that
* contains default mapping of the tables and views. Allows to limit types
* of tables to read.
- *
+ *
* @deprecated since 3.2 use
* {@link #load(DataMap, String, String, String, String...)}
* method that supports catalogs.
*/
public DataMap loadDataMapFromDB(String schemaPattern, String tablePattern,
String[] tableTypes, DataMap dataMap) throws SQLException {
+ clearDataMap(dataMap);
+
load(dataMap, null, schemaPattern, tablePattern, tableTypes);
return dataMap;
}
+ private void clearDataMap(DataMap dataMap) {
+ dataMap.clearDbEntities();
+ dataMap.clearEmbeddables();
+ dataMap.clearObjEntities();
+ dataMap.clearProcedures();
+ dataMap.clearQueries();
+ dataMap.clearResultSets();
+ }
+
/**
* Performs database reverse engineering to match the specified catalog,
* schema, table name and table type patterns and fills the specified
* DataMap object with DB and object mapping info.
- *
+ *
* @since 3.2
*/
public void load(DataMap dataMap, String catalogPattern,
@@ -830,7 +869,10 @@ public class DbLoader {
if (loadDbEntities(dataMap, tables)) {
loadDbRelationships(dataMap);
+
loadObjEntities(dataMap);
+ optimizeObjRelationships(dataMap);
+ fireObjEntitiesAddedEvents(dataMap);
}
}
@@ -842,7 +884,7 @@ public class DbLoader {
* currently this method is NOT CALLED from "loadDataMapFromDB" and should
* be invoked explicitly by the user. </i>
* </p>
- *
+ *
* @since 1.1
* @deprecated since 3.2 use
* {@link #loadProcedures(DataMap, String, String, String)}
that
@@ -861,7 +903,7 @@ public class DbLoader {
* currently this method is NOT CALLED from "loadDataMapFromDB" and should
* be invoked explicitly by the user. </i>
* </p>
- *
+ *
* @since 3.2
*/
public void loadProcedures(DataMap dataMap, String catalogPattern,
@@ -1010,7 +1052,7 @@ public class DbLoader {
/**
* Sets new naming strategy for reverse engineering
- *
+ *
* @since 3.0
*/
public void setNamingStrategy(NamingStrategy strategy) {
Added:
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java
URL:
http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java?rev=1420322&view=auto
==============================================================================
---
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java
(added)
+++
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/access/ManyToManyCandidateEntity.java
Tue Dec 11 18:22:35 2012
@@ -0,0 +1,113 @@
+package org.apache.cayenne.access;
+
+import org.apache.cayenne.map.DbRelationship;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.ObjRelationship;
+import org.apache.cayenne.map.Relationship;
+import org.apache.cayenne.util.NamedObjectFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class represent ObjEntity that may be optimized using flattened
relationships
+ * as many to many table
+ */
+class ManyToManyCandidateEntity {
+ private ObjEntity entity;
+
+ public ManyToManyCandidateEntity(ObjEntity entityValue) {
+ entity = entityValue;
+ }
+
+ public ObjEntity getEntity() {
+ return entity;
+ }
+
+ private boolean isTargetEntitiesDifferent() {
+ return !getTargetEntity1().equals(getTargetEntity2());
+ }
+
+ private boolean isRelationshipsHasDependentPK() {
+ boolean isRelationship1HasDepPK =
getDbRelationship1().getReverseRelationship().isToDependentPK();
+ boolean isRelationship2HasDepPK =
getDbRelationship2().getReverseRelationship().isToDependentPK();
+
+ return isRelationship1HasDepPK && isRelationship2HasDepPK;
+ }
+
+ private ObjRelationship getRelationship1() {
+ List<Relationship> relationships = new
ArrayList<Relationship>(entity.getRelationships());
+ return (ObjRelationship) relationships.get(0);
+ }
+
+ private ObjRelationship getRelationship2() {
+ List<Relationship> relationships = new
ArrayList<Relationship>(entity.getRelationships());
+ return (ObjRelationship) relationships.get(1);
+ }
+
+ private ObjEntity getTargetEntity1() {
+ return (ObjEntity) getRelationship1().getTargetEntity();
+ }
+
+ private ObjEntity getTargetEntity2() {
+ return (ObjEntity) getRelationship2().getTargetEntity();
+ }
+
+ private DbRelationship getDbRelationship1() {
+ return getRelationship1().getDbRelationships().get(0);
+ }
+
+ private DbRelationship getDbRelationship2() {
+ return getRelationship2().getDbRelationships().get(0);
+ }
+
+ /**
+ * Method check - if current entity represent many to many temporary table
+ * @return true if current entity is represent many to many table;
otherwise returns false
+ */
+ public boolean isRepresentManyToManyTable() {
+ boolean hasTwoRelationships = entity.getRelationships().size() == 2;
+ boolean isNotHaveAttributes = entity.getAttributes().size() == 0;
+
+ return hasTwoRelationships && isNotHaveAttributes &&
isRelationshipsHasDependentPK()
+ && isTargetEntitiesDifferent();
+ }
+
+ private void removeRelationshipsFromTargetEntities() {
+
getTargetEntity1().removeRelationship(getRelationship1().getReverseRelationship().getName());
+
getTargetEntity2().removeRelationship(getRelationship2().getReverseRelationship().getName());
+ }
+
+ private void addFlattenedRelationship(ObjEntity srcEntity, ObjEntity
dstEntity,
+ DbRelationship... relationshipPath) {
+ ObjRelationship newRelationship = (ObjRelationship)
NamedObjectFactory.createRelationship(srcEntity, dstEntity,
+ true);
+
+ newRelationship.setSourceEntity(srcEntity);
+ newRelationship.setTargetEntity(dstEntity);
+
+ for (DbRelationship curRelationship : relationshipPath) {
+ newRelationship.addDbRelationship(curRelationship);
+ }
+
+ srcEntity.addRelationship(newRelationship);
+ }
+
+ /**
+ * Method make direct relationships between 2 entities and remove
relationships to
+ * many to many entity
+ */
+ public void optimizeRelationships() {
+ removeRelationshipsFromTargetEntities();
+
+ DbRelationship dbRelationship1 =
getRelationship1().getDbRelationships().get(0);
+ DbRelationship dbRelationship2 =
getRelationship2().getDbRelationships().get(0);
+
+ addFlattenedRelationship(getTargetEntity1(), getTargetEntity2(),
dbRelationship1.getReverseRelationship(),
+ dbRelationship2);
+
+ addFlattenedRelationship(getTargetEntity2(), getTargetEntity1(),
dbRelationship2.getReverseRelationship(),
+ dbRelationship1);
+ }
+
+}
Added:
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java
URL:
http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java?rev=1420322&view=auto
==============================================================================
---
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java
(added)
+++
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/access/ManyToManyCandidateEntityTest.java
Tue Dec 11 18:22:35 2012
@@ -0,0 +1,84 @@
+package org.apache.cayenne.access;
+
+import junit.framework.TestCase;
+import org.apache.cayenne.configuration.ConfigurationNameMapper;
+import org.apache.cayenne.configuration.ConfigurationTree;
+import org.apache.cayenne.configuration.DataChannelDescriptor;
+import org.apache.cayenne.configuration.DataMapLoader;
+import org.apache.cayenne.configuration.DefaultConfigurationNameMapper;
+import org.apache.cayenne.configuration.XMLDataChannelDescriptorLoader;
+import org.apache.cayenne.configuration.XMLDataMapLoader;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.DIBootstrap;
+import org.apache.cayenne.di.Injector;
+import org.apache.cayenne.di.Module;
+import org.apache.cayenne.map.DataMap;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.map.Relationship;
+import org.apache.cayenne.resource.URLResource;
+
+import java.net.URL;
+import java.util.ArrayList;
+
+/**
+ *
+ */
+public class ManyToManyCandidateEntityTest extends TestCase {
+
+ private DataMap map;
+
+ @Override
+ public void setUp() throws Exception {
+ Module testModule = new Module() {
+
+ public void configure(Binder binder) {
+ binder.bind(DataMapLoader.class).to(XMLDataMapLoader.class);
+ binder.bind(ConfigurationNameMapper.class).to(
+ DefaultConfigurationNameMapper.class);
+ }
+ };
+
+ Injector injector = DIBootstrap.createInjector(testModule);
+
+ // create and initialize loader instance to test
+ XMLDataChannelDescriptorLoader loader = new
XMLDataChannelDescriptorLoader();
+ injector.injectMembers(loader);
+
+ String testConfigName = "relationship-optimisation";
+ URL url = getClass().getResource("cayenne-" + testConfigName + ".xml");
+
+ ConfigurationTree<DataChannelDescriptor> tree = loader.load(new
URLResource(url));
+
+ map = tree.getRootNode().getDataMap(testConfigName);
+ }
+
+ public void testMatchingForManyToManyEntity() throws Exception {
+ ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
+
+ assertEquals(true, new
ManyToManyCandidateEntity(manyToManyEntity).isRepresentManyToManyTable());
+ }
+
+ public void testMatchingForNotManyToManyEntity() throws Exception {
+ ObjEntity entity = map.getObjEntity("Table1");
+
+ assertEquals(false, new
ManyToManyCandidateEntity(entity).isRepresentManyToManyTable());
+ }
+
+ public void testOptimisationForManyToManyEntity() {
+ ObjEntity manyToManyEntity = map.getObjEntity("Table1Table2");
+
+ new
ManyToManyCandidateEntity(manyToManyEntity).optimizeRelationships();
+
+ ObjEntity table1Entity = map.getObjEntity("Table1");
+ ObjEntity table2Entity = map.getObjEntity("Table2");
+
+ assertEquals(1, table1Entity.getRelationships().size());
+ assertEquals(table2Entity, new
ArrayList<Relationship>(table1Entity.getRelationships()).get(0)
+ .getTargetEntity());
+
+ assertEquals(1, table2Entity.getRelationships().size());
+ assertEquals(table1Entity, new
ArrayList<Relationship>(table2Entity.getRelationships()).get(0)
+ .getTargetEntity());
+ }
+
+}
Added:
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml
URL:
http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml?rev=1420322&view=auto
==============================================================================
---
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml
(added)
+++
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/cayenne-relationship-optimisation.xml
Tue Dec 11 18:22:35 2012
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<domain project-version="6">
+ <map name="relationship-optimisation"/>
+</domain>
Added:
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml
URL:
http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml?rev=1420322&view=auto
==============================================================================
---
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml
(added)
+++
cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/resources/org/apache/cayenne/access/relationship-optimisation.map.xml
Tue Dec 11 18:22:35 2012
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<data-map xmlns="http://cayenne.apache.org/schema/3.0/modelMap"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://cayenne.apache.org/schema/3.0/modelMap
http://cayenne.apache.org/schema/3.0/modelMap.xsd"
+ project-version="6">
+ <property name="defaultPackage" value="com.objectstyle"/>
+ <db-entity name="table1" catalog="many_to_many_test">
+ <db-attribute name="id1" type="INTEGER" isPrimaryKey="true"
isMandatory="true" length="10"/>
+ <db-attribute name="table1col" type="VARCHAR" length="45"/>
+ </db-entity>
+ <db-entity name="table1_table2" catalog="many_to_many_test">
+ <db-attribute name="fk1" type="INTEGER" isPrimaryKey="true"
isMandatory="true" length="10"/>
+ <db-attribute name="fk2" type="INTEGER" isPrimaryKey="true"
isMandatory="true" length="10"/>
+ </db-entity>
+ <db-entity name="table2" catalog="many_to_many_test">
+ <db-attribute name="id2" type="INTEGER" isPrimaryKey="true"
isMandatory="true" length="10"/>
+ <db-attribute name="table2col" type="VARCHAR" length="45"/>
+ </db-entity>
+ <obj-entity name="Table1" className="com.objectstyle.Table1"
dbEntityName="table1">
+ <obj-attribute name="table1col" type="java.lang.String"
db-attribute-path="table1col"/>
+ </obj-entity>
+ <obj-entity name="Table1Table2"
className="com.objectstyle.Table1Table2" dbEntityName="table1_table2">
+ </obj-entity>
+ <obj-entity name="Table2" className="com.objectstyle.Table2"
dbEntityName="table2">
+ <obj-attribute name="table2col" type="java.lang.String"
db-attribute-path="table2col"/>
+ </obj-entity>
+ <db-relationship name="table1Table2Array" source="table1"
target="table1_table2" toDependentPK="true" toMany="true">
+ <db-attribute-pair source="id1" target="fk1"/>
+ </db-relationship>
+ <db-relationship name="toTable1" source="table1_table2" target="table1"
toMany="false">
+ <db-attribute-pair source="fk1" target="id1"/>
+ </db-relationship>
+ <db-relationship name="toTable2" source="table1_table2" target="table2"
toMany="false">
+ <db-attribute-pair source="fk2" target="id2"/>
+ </db-relationship>
+ <db-relationship name="table1Table2Array" source="table2"
target="table1_table2" toDependentPK="true" toMany="true">
+ <db-attribute-pair source="id2" target="fk2"/>
+ </db-relationship>
+ <obj-relationship name="table1Table2Array" source="Table1"
target="Table1Table2" deleteRule="Deny"
db-relationship-path="table1Table2Array"/>
+ <obj-relationship name="toTable1" source="Table1Table2" target="Table1"
deleteRule="Nullify" db-relationship-path="toTable1"/>
+ <obj-relationship name="toTable2" source="Table1Table2" target="Table2"
deleteRule="Nullify" db-relationship-path="toTable2"/>
+ <obj-relationship name="table1Table2Array" source="Table2"
target="Table1Table2" deleteRule="Deny"
db-relationship-path="table1Table2Array"/>
+</data-map>