/***************************************************************** * 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.dbsync.reverse.dbload; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.Map; import java.util.Set; import org.apache.cayenne.dbsync.naming.NameBuilder; import org.apache.cayenne.dbsync.naming.ObjectNameGenerator; 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.util.EqualsBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RelationshipLoader extends AbstractLoader { private static final Logger LOGGER = LoggerFactory.getLogger(DbLoader.class); private final ObjectNameGenerator nameGenerator; RelationshipLoader(DbLoaderConfiguration config, DbLoaderDelegate delegate, ObjectNameGenerator nameGenerator) { super(null, config, delegate); this.nameGenerator = nameGenerator; } @Override public void load(DatabaseMetaData metaData, DbLoadDataStore map) throws SQLException { if (config.isSkipRelationshipsLoading()) { return; } for (Map.Entry<String, Set<ExportedKey>> entry : map.getExportedKeysEntrySet()) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Process keys for: " + entry.getKey()); } Set<ExportedKey> exportedKeys = entry.getValue(); ExportedKey key = exportedKeys.iterator().next(); if (key == null) { throw new IllegalStateException(); } ExportedKey.KeyData PK = key.getPk(); ExportedKey.KeyData FK = key.getFk(); DbEntity pkEntity = map.getDbEntity(PK.getTable()); DbEntity fkEntity = map.getDbEntity(FK.getTable()); if (pkEntity == null || fkEntity == null) { // Check for existence of this entities were made in creation of ExportedKey throw new IllegalStateException(); } if (!new EqualsBuilder() .append(pkEntity.getCatalog(), PK.getCatalog()) .append(pkEntity.getSchema(), PK.getSchema()).append(fkEntity.getCatalog(), FK.getCatalog()) .append(fkEntity.getSchema(), PK.getSchema()).isEquals()) { LOGGER.info("Skip relation: '" + key + "' because it related to objects from other catalog/schema"); LOGGER.info(" relation primary key: '" + PK.getCatalog() + "." + PK.getSchema() + "'"); LOGGER.info(" primary key entity: '" + pkEntity.getCatalog() + "." + pkEntity.getSchema() + "'"); LOGGER.info(" relation foreign key: '" + FK.getCatalog() + "." + FK.getSchema() + "'"); LOGGER.info(" foreign key entity: '" + fkEntity.getCatalog() + "." + fkEntity.getSchema() + "'"); continue; } // forwardRelationship is a reference from table with primary key // it is what exactly we load from db DbRelationship forwardRelationship = new DbRelationship(); forwardRelationship.setSourceEntity(pkEntity); forwardRelationship.setTargetEntityName(fkEntity); // TODO: dirty and non-transparent... using DbRelationshipDetected for the benefit of the merge package. // This info is available from joins.... DbRelationshipDetected reverseRelationship = new DbRelationshipDetected(); reverseRelationship.setFkName(FK.getName()); reverseRelationship.setSourceEntity(fkEntity); reverseRelationship.setTargetEntityName(pkEntity); reverseRelationship.setToMany(false); createAndAppendJoins(exportedKeys, pkEntity, fkEntity, forwardRelationship, reverseRelationship); boolean toDependentPK = isToDependentPK(forwardRelationship); boolean toMany = isToMany(toDependentPK, fkEntity, forwardRelationship); forwardRelationship.setToDependentPK(toDependentPK); forwardRelationship.setToMany(toMany); // set relationship names only after their joins are ready ... // generator logic is based on relationship state... forwardRelationship.setName(NameBuilder .builder(forwardRelationship, pkEntity) .baseName(nameGenerator.relationshipName(forwardRelationship)) .name()); reverseRelationship.setName(NameBuilder .builder(reverseRelationship, fkEntity) .baseName(nameGenerator.relationshipName(reverseRelationship)) .name()); if (delegate.dbRelationshipLoaded(fkEntity, reverseRelationship)) { fkEntity.addRelationship(reverseRelationship); } if (delegate.dbRelationshipLoaded(pkEntity, forwardRelationship)) { pkEntity.addRelationship(forwardRelationship); } } } private boolean isToMany(boolean toDependentPK, DbEntity fkEntity, DbRelationship forwardRelationship) { return !toDependentPK || fkEntity.getPrimaryKeys().size() != forwardRelationship.getJoins().size(); } private boolean isToDependentPK(DbRelationship forwardRelationship) { for (DbJoin dbJoin : forwardRelationship.getJoins()) { if (!dbJoin.getTarget().isPrimaryKey()) { return false; } } return true; } private void createAndAppendJoins(Set<ExportedKey> exportedKeys, DbEntity pkEntity, DbEntity fkEntity, DbRelationship forwardRelationship, DbRelationship reverseRelationship) { for (ExportedKey exportedKey : exportedKeys) { // Create and append joins String pkName = exportedKey.getPk().getColumn(); String fkName = exportedKey.getFk().getColumn(); // skip invalid joins... DbAttribute pkAtt = pkEntity.getAttribute(pkName); if (pkAtt == null) { LOGGER.info("no attribute for declared primary key: " + pkName); continue; } DbAttribute fkAtt = fkEntity.getAttribute(fkName); if (fkAtt == null) { LOGGER.info("no attribute for declared foreign key: " + fkName); continue; } addJoin(forwardRelationship, pkName, fkName); addJoin(reverseRelationship, fkName, pkName); } } private void addJoin(DbRelationship relationship, String sourceName, String targetName){ for (DbJoin join : relationship.getJoins()) { if (join.getSourceName().equals(sourceName) && join.getTargetName().equals(targetName)) { return; } } relationship.addJoin(new DbJoin(relationship, sourceName, targetName)); } }