org.apache.cayenne.modeler.dialog.objentity.ObjRelationshipInfo Maven / Gradle / Ivy
The newest version!
/*****************************************************************
* 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.modeler.dialog.objentity;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.map.Attribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.Entity;
import org.apache.cayenne.map.ObjAttribute;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.ObjRelationship;
import org.apache.cayenne.map.Relationship;
import org.apache.cayenne.map.event.RelationshipEvent;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.ProjectController;
import org.apache.cayenne.modeler.dialog.ResolveDbRelationshipDialog;
import org.apache.cayenne.modeler.util.CayenneController;
import org.apache.cayenne.modeler.util.Comparators;
import org.apache.cayenne.modeler.util.EntityTreeFilter;
import org.apache.cayenne.modeler.util.EntityTreeModel;
import org.apache.cayenne.modeler.util.MultiColumnBrowser;
import org.apache.cayenne.util.DeleteRuleUpdater;
import org.apache.cayenne.util.NamedObjectFactory;
import org.apache.cayenne.util.Util;
public class ObjRelationshipInfo extends CayenneController implements
TreeSelectionListener {
static final String COLLECTION_TYPE_MAP = "java.util.Map";
static final String COLLECTION_TYPE_SET = "java.util.Set";
static final String COLLECTION_TYPE_COLLECTION = "java.util.Collection";
static final String DEFAULT_MAP_KEY = "ID (default)";
protected ObjRelationship relationship;
protected List dbRelationships;
protected List savedDbRelationships;
protected ObjEntity objectTarget;
protected List objectTargets;
protected List targetCollections;
protected List mapKeys;
protected String relationshipName;
protected String targetCollection;
protected String mapKey;
protected ObjRelationshipInfoView view;
protected String currentPath;
protected ProjectController mediator;
/**
* Starts options dialog.
*/
public void startupAction() {
view.pack();
view.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
view.setModal(true);
makeCloseableOnEscape();
centerView();
view.setVisible(true);
}
public ObjRelationshipInfo(ProjectController mediator,ObjRelationship relationship) {
super(mediator);
this.view = new ObjRelationshipInfoView(mediator);
this.mediator=mediator;
ObjEntity target = getObjectTarget();
getPathBrowser().addTreeSelectionListener(this);
setObjectTarget(target);
view.sourceEntityLabel.setText(relationship.getSourceEntity().getName());
this.relationship = relationship;
this.relationshipName = relationship.getName();
view.relationshipName.setText(relationshipName);
this.mapKey = relationship.getMapKey();
this.targetCollection = relationship.getCollectionType();
if (targetCollection == null) {
targetCollection = ObjRelationship.DEFAULT_COLLECTION_TYPE;
}
this.objectTarget = (ObjEntity) relationship.getTargetEntity();
if (objectTarget != null) {
updateTargetCombo(objectTarget.getDbEntity());
}
// validate -
// current limitation is that an ObjRelationship must have source
// and target entities present, with DbEntities chosen.
validateCanMap();
this.targetCollections = new ArrayList(4);
targetCollections.add(COLLECTION_TYPE_COLLECTION);
targetCollections.add(ObjRelationship.DEFAULT_COLLECTION_TYPE);
targetCollections.add(COLLECTION_TYPE_MAP);
targetCollections.add(COLLECTION_TYPE_SET);
for( String s : targetCollections) {
view.collectionTypeCombo.addItem(s);
}
this.mapKeys = new ArrayList();
initMapKeys();
// setup path
dbRelationships = new ArrayList(relationship.getDbRelationships());
selectPath();
updateCollectionChoosers();
// add dummy last relationship if we are not connected
connectEnds();
initFromModel();
initController();
}
private void initController() {
view.getCancelButton().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
view.dispose();
}
});
view.getSaveButton().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
saveMapping();
}
});
view.getNewRelButton().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
createRelationship();
}
});
view.getSelectPathButton().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
selectPath();
}
});
view.getCollectionTypeCombo().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setCollectionType();
}
});
view.getMapKeysCombo().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setMapKey();
}
});
}
void initFromModel() {
if (view.pathBrowser.getModel() == null) {
EntityTreeModel treeModel = new EntityTreeModel(getStartEntity());
treeModel.setFilter(new EntityTreeFilter() {
public boolean attributeMatch(Object node, Attribute attr) {
// attrs not allowed here
return false;
}
public boolean relationshipMatch(Object node, Relationship rel) {
if (!(node instanceof Relationship)) {
return true;
}
/**
* We do not allow A->B->A chains, where relationships are to-one
*/
DbRelationship prev = (DbRelationship) node;
return !(!rel.isToMany() && prev.getReverseRelationship() == rel);
}
});
view.pathBrowser.setModel(treeModel);
setSelectionPath(getSavedDbRelationships());
}
}
/**
* Selects path in browser
*/
void setSelectionPath(List rels) {
Object[] path = new Object[rels.size() + 1];
path[0] = getStartEntity();
System.arraycopy(rels.toArray(), 0, path, 1, rels.size());
view.pathBrowser.setSelectionPath(new TreePath(path));
}
public void setCollectionType() {
setTargetCollection((String)view.collectionTypeCombo.getSelectedItem());
if (COLLECTION_TYPE_MAP.equals(targetCollection)){
view.mapKeysLabel.setEnabled(true);
view.mapKeysCombo.setEnabled(true);
setMapKey();
}
else {
view.mapKeysLabel.setEnabled(false);
view.mapKeysCombo.setEnabled(false);
}
}
public void setMapKey() {
setMapKey((String)view.mapKeysCombo.getSelectedItem());
}
@Override
public Component getView() {
return view;
}
public void setSavedDbRelationships(List rels) {
this.savedDbRelationships = rels;
String currPath = "";
for (DbRelationship rel : rels) {
currPath += "->" + rel.getName();
}
if (rels.size() > 0) {
currPath = currPath.substring(2);
}
currentPath = currPath;
view.currentPathLabel.setText(currPath);
}
public void selectPath() {
setSavedDbRelationships(new ArrayList(dbRelationships));
}
/**
* Reverts current path to saved path
*/
protected void revertPath() {
setSelectionPath(getSavedDbRelationships());
setDbRelationships(getSavedDbRelationships());
}
/**
* Updates 'collection type' and 'map keys' comboboxes
*/
protected void updateCollectionChoosers() {
boolean collectionTypeEnabled = isToMany();
view.collectionTypeCombo.setEnabled(collectionTypeEnabled);
view.collectionTypeLabel.setEnabled(collectionTypeEnabled);
if (collectionTypeEnabled) {
view.collectionTypeCombo.setSelectedItem(targetCollection);
}
boolean mapKeysEnabled = collectionTypeEnabled
&& ObjRelationshipInfo.COLLECTION_TYPE_MAP
.equals(view.collectionTypeCombo.getSelectedItem());
view.mapKeysCombo.setEnabled(mapKeysEnabled);
view.mapKeysLabel.setEnabled(mapKeysEnabled);
if (mapKeysEnabled) {
view.mapKeysCombo.setSelectedItem(mapKey);
}
}
/**
* Clears paths and selections in browser
*/
protected void clearPath() {
getPathBrowser().clearSelection();
setDbRelationships(new ArrayList());
}
protected void saveMapping() {
if (!getDbRelationships().equals(getSavedDbRelationships())) {
if (JOptionPane.showConfirmDialog(
(Component) getView(),
"You have changed Db Relationship path. Do you want it to be saved?",
"Save ObjRelationship",
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
selectPath();
}
}
if (savePath()) {
mediator.fireObjRelationshipEvent(new RelationshipEvent(Application
.getFrame(), getRelationship(), getRelationship()
.getSourceEntity()));
}
view.sourceEntityLabel.setText(relationship.getSourceEntity().getName());
view.dispose();
}
/**
* @return relationship path browser
*/
public MultiColumnBrowser getPathBrowser() {
return view.pathBrowser;
}
/**
* Creates a new relationship connecting currently selected source entity with
* ObjRelationship target entity. User is allowed to edit the relationship, change its
* name, and create joins.
*/
protected void createRelationship() {
DbRelationship dbRel = getLastRelationship();
DbEntity source = dbRel != null ? (DbEntity) dbRel.getTargetEntity() : null;
DbRelationshipTarget targetModel = new DbRelationshipTarget(mediator,
getStartEntity(),
source);
targetModel.startupAction();
if (!targetModel.isSavePressed()) {
return;
}
DbRelationship dbRelationship = (DbRelationship) NamedObjectFactory
.createRelationship(
targetModel.getSource(),
targetModel.getTarget(),
targetModel.isToMany());
// note: NamedObjectFactory doesn't set source or target, just the name
dbRelationship.setSourceEntity(targetModel.getSource());
dbRelationship.setTargetEntity(targetModel.getTarget());
dbRelationship.setToMany(targetModel.isToMany());
targetModel.getSource().addRelationship(dbRelationship);
// TODO: creating relationship outside of ResolveDbRelationshipDialog confuses it
// to send incorrect event - CHANGE instead of ADD
ResolveDbRelationshipDialog dialog = new ResolveDbRelationshipDialog(
dbRelationship);
dialog.setVisible(true);
if (dialog.isCancelPressed()) {
targetModel.getSource().removeRelationship(dbRelationship.getName());
}
else {
MultiColumnBrowser pathBrowser = getPathBrowser();
Object[] oldPath = targetModel.isSource1Selected() ? new Object[] {
getStartEntity()
} : pathBrowser.getSelectionPath().getPath();
/**
* Update the view
*/
EntityTreeModel treeModel = (EntityTreeModel) pathBrowser.getModel();
treeModel.invalidate();
pathBrowser.setSelectionPath(new TreePath(new Object[] {
getStartEntity()
}));
pathBrowser.repaint();
Object[] path = new Object[oldPath.length + 1];
System.arraycopy(oldPath, 0, path, 0, path.length - 1);
path[path.length - 1] = dbRelationship;
pathBrowser.setSelectionPath(new TreePath(path));
}
dialog.dispose();
}
/**
* Sets list of DB Relationships current ObjRelationship is mapped to
*/
public void valueChanged(TreeSelectionEvent e) {
TreePath selectedPath = e.getPath();
// first item in the path is Entity, so we must have
// at least two elements to constitute a valid ordering path
if (selectedPath == null || selectedPath.getPathCount() < 2) {
return;
}
Relationship rel = (Relationship) selectedPath.getLastPathComponent();
DbEntity target = (DbEntity) rel.getTargetEntity();
/**
* Initialize root with one of mapped ObjEntities.
*/
Collection objEntities = target.getDataMap().getMappedEntities(target);
List relPath = new Vector(selectedPath
.getPathCount() - 1);
for (int i = 1; i < selectedPath.getPathCount(); i++) {
relPath.add((DbRelationship) selectedPath.getPathComponent(i));
}
setDbRelationships(relPath);
setObjectTarget(objEntities.size() == 0 ? null : objEntities
.iterator()
.next());
updateCollectionChoosers();
}
public void setObjectTarget(ObjEntity objectTarget) {
if (this.objectTarget != objectTarget) {
this.objectTarget = objectTarget;
view.targetCombo.setSelectedItem(objectTarget);
// init available map keys
initMapKeys();
}
}
private void initMapKeys() {
this.mapKeys.clear();
mapKeys.add(DEFAULT_MAP_KEY);
/**
* Object target can be null when selected target DbEntity has no ObjEntities
*/
if (objectTarget == null) {
return;
}
for (ObjAttribute attribute : this.objectTarget.getAttributes()) {
mapKeys.add(attribute.getName());
}
view.mapKeysCombo.removeAllItems();
for(String s :mapKeys)
view.mapKeysCombo.addItem(s);
if (mapKey != null && !mapKeys.contains(mapKey)) {
mapKey = DEFAULT_MAP_KEY;
view.mapKeysCombo.setSelectedItem(mapKey);
}
}
/**
* Places in objectTargets list all ObjEntities for specified DbEntity
*/
@SuppressWarnings("unchecked")
protected void updateTargetCombo(DbEntity dbTarget) {
// copy those that have DbEntities mapped to dbTarget, and then sort
this.objectTargets = new ArrayList();
if (dbTarget != null) {
objectTargets.addAll(dbTarget.getDataMap().getMappedEntities(dbTarget));
Collections.sort(objectTargets, Comparators.getNamedObjectComparator());
}
view.targetCombo.removeAllItems();
for( ObjEntity s : objectTargets) {
view.targetCombo.addItem(s.getName());
}
}
public ObjRelationship getRelationship() {
return relationship;
}
/**
* @return list of DB Relationships current ObjRelationship is mapped to
*/
public List getDbRelationships() {
return dbRelationships;
}
/**
* @return list of saved DB Relationships
*/
public List getSavedDbRelationships() {
return savedDbRelationships;
}
/**
* @return last relationship in the path, or null
if path is empty
*/
public DbRelationship getLastRelationship() {
return dbRelationships.size() == 0 ? null : dbRelationships.get(dbRelationships
.size() - 1);
}
/**
* Sets list of DB Relationships current ObjRelationship is mapped to
*/
public void setDbRelationships(List rels) {
this.dbRelationships = rels;
updateTargetCombo(rels.size() > 0 ? (DbEntity) rels
.get(rels.size() - 1)
.getTargetEntity() : null);
updateCollectionChoosers();
}
/**
* Returns currently selected target of the ObjRelationship.
*/
public ObjEntity getObjectTarget() {
return objectTarget;
}
/**
* Returns a list of ObjEntities available for target mapping.
*/
public List getObjectTargets() {
return objectTargets;
}
public String getRelationshipName() {
return relationshipName;
}
public void setRelationshipName(String relationshipName) {
view.relationshipName.setText(relationshipName);
this.relationshipName = relationshipName;
}
/**
* Processes relationship path when path component at index was changed.
*/
public synchronized void relationshipChanged(int index) {
// strip everything starting from the index
breakChain(index);
// connect the ends
connectEnds();
}
public boolean isToMany() {
// copied algorithm from ObjRelationship.calculateToMany(), only iterating through
// the unsaved dbrels selection.
for (DbRelationship relationship : dbRelationships) {
if (relationship != null && relationship.isToMany()) {
return true;
}
}
return false;
}
/**
* Stores current state of the model in the internal ObjRelationship.
*/
public synchronized boolean savePath() {
boolean hasChanges = false;
boolean oldToMany = relationship.isToMany();
if (!Util.nullSafeEquals(relationship.getName(), relationshipName)) {
hasChanges = true;
relationship.setName(relationshipName);
}
if (savedDbRelationships.size() > 0) {
DbEntity lastEntity = (DbEntity) savedDbRelationships.get(
savedDbRelationships.size() - 1).getTargetEntity();
if (objectTarget == null || objectTarget.getDbEntity() != lastEntity) {
/**
* Entities in combobox and path browser do not match. In this case, we
* rely on the browser and automatically select one of lastEntity's
* ObjEntities
*/
Collection objEntities = lastEntity
.getDataMap()
.getMappedEntities(lastEntity);
objectTarget = objEntities.size() == 0 ? null : objEntities
.iterator()
.next();
}
}
if (objectTarget == null
|| !Util.nullSafeEquals(objectTarget.getName(), relationship
.getTargetEntityName())) {
hasChanges = true;
// note on events notification - this needs to be propagated
// via old modeler events, but we leave this to the controller
// since model knows nothing about Modeler mediator.
relationship.setTargetEntity(objectTarget);
}
// check for path modifications
List oldPath = relationship.getDbRelationships();
if (oldPath.size() != savedDbRelationships.size()) {
hasChanges = true;
updatePath();
}
else {
for (int i = 0; i < oldPath.size(); i++) {
DbRelationship next = savedDbRelationships.get(i);
if (oldPath.get(i) != next) {
hasChanges = true;
updatePath();
break;
}
}
}
String collectionType = ObjRelationship.DEFAULT_COLLECTION_TYPE
.equals(targetCollection)
|| !relationship.isToMany() ? null : targetCollection;
if (!Util.nullSafeEquals(collectionType, relationship.getCollectionType())) {
hasChanges = true;
relationship.setCollectionType(collectionType);
}
// map key only makes sense for Map relationships
String mapKey = COLLECTION_TYPE_MAP.equals(collectionType)
&& !DEFAULT_MAP_KEY.equals(this.mapKey) ? this.mapKey : null;
if (!Util.nullSafeEquals(mapKey, relationship.getMapKey())) {
hasChanges = true;
relationship.setMapKey(mapKey);
}
/**
* As of CAY-436 here we check if to-many property has changed during the editing,
* and if so, delete rule must be reset to default value
*/
if (hasChanges && relationship.isToMany() != oldToMany) {
DeleteRuleUpdater.updateObjRelationship(relationship);
}
return hasChanges;
}
private void updatePath() {
relationship.clearDbRelationships();
for (DbRelationship nextPathComponent : dbRelationships) {
if (nextPathComponent == null) {
break;
}
relationship.addDbRelationship(nextPathComponent);
}
}
private void breakChain(int index) {
// strip everything starting from the index
while (dbRelationships.size() > (index + 1)) {
// remove last
dbRelationships.remove(dbRelationships.size() - 1);
}
}
// Connects last selected DbRelationship in the path to the
// last DbEntity, creating a dummy relationship if needed.
private void connectEnds() {
Relationship last = null;
int size = dbRelationships.size();
if (size > 0) {
last = dbRelationships.get(size - 1);
}
Entity target = getEndEntity();
if (target != null && (last == null || last.getTargetEntity() != target)) {
// try to connect automatically, if we can't use dummy connector
Entity source = (last == null) ? getStartEntity() : last.getTargetEntity();
if (source != null) {
Relationship anyConnector = source != null ? source
.getAnyRelationship(target) : null;
if (anyConnector != null) {
dbRelationships.add((DbRelationship) anyConnector);
}
}
}
}
/**
* Checks if the entity can be edited with this inspector. NOTE: As of CAY-1077,
* relationship inspector can be opened even if no target entity was set.
*/
private void validateCanMap() {
if (relationship.getSourceEntity() == null) {
throw new CayenneRuntimeException(
"Can't map relationship without source entity.");
}
if (getStartEntity() == null) {
throw new CayenneRuntimeException(
"Can't map relationship without source DbEntity.");
}
}
public DbEntity getStartEntity() {
return ((ObjEntity) relationship.getSourceEntity()).getDbEntity();
}
public DbEntity getEndEntity() {
/**
* Object target can be null when selected target DbEntity has no ObjEntities
*/
if (objectTarget == null) {
return null;
}
return objectTarget.getDbEntity();
}
public String getMapKey() {
return mapKey;
}
public void setMapKey(String mapKey) {
this.mapKey = mapKey;
}
public String getCurrentPath() {
return currentPath;
}
public String getTargetCollection() {
return targetCollection;
}
public void setTargetCollection(String targetCollection) {
this.targetCollection = targetCollection;
}
public List getMapKeys() {
return mapKeys;
}
public List getTargetCollections() {
return targetCollections;
}
}