/*****************************************************************
* 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;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.dbsync.naming.NameBuilder;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbJoin;
import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.event.MapEvent;
import org.apache.cayenne.map.event.RelationshipEvent;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.pref.TableColumnPreferences;
import org.apache.cayenne.modeler.undo.RelationshipUndoableEdit;
import org.apache.cayenne.modeler.util.CayenneDialog;
import org.apache.cayenne.modeler.util.CayenneTable;
import org.apache.cayenne.modeler.util.ModelerUtil;
import org.apache.cayenne.modeler.util.PanelFactory;
import org.apache.cayenne.modeler.util.combo.AutoCompletion;
import org.apache.cayenne.util.Util;
import javax.swing.*;
import javax.swing.table.TableColumn;
import java.awt.*;
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;
/**
* Editor of DbRelationship joins.
*/
public class ResolveDbRelationshipDialog extends CayenneDialog {
protected DbRelationship relationship;
protected DbRelationship reverseRelationship;
protected JTextField name;
protected JTextField reverseName;
protected CayenneTable table;
protected TableColumnPreferences tablePreferences;
protected JButton addButton;
protected JButton removeButton;
protected JButton saveButton;
protected JButton cancelButton;
private boolean cancelPressed;
private RelationshipUndoableEdit undo;
private boolean editable = true;
public ResolveDbRelationshipDialog(DbRelationship relationship) {
this(relationship, true);
}
public ResolveDbRelationshipDialog(DbRelationship relationship, boolean editable) {
super(Application.getFrame(), "", true);
this.editable = editable;
initView();
initController();
if(!initWithModel(relationship)){
cancelPressed = true;
return;
}
this.undo = new RelationshipUndoableEdit(relationship);
this.pack();
this.centerWindow();
}
@Override
public void setVisible(boolean b) {
if(b && cancelPressed) {
return;
}
super.setVisible(b);
}
/**
* Creates graphical components.
*/
private void initView() {
// create widgets
name = new JTextField(25);
reverseName = new JTextField(25);
addButton = new JButton("Add");
addButton.setEnabled(this.editable);
removeButton = new JButton("Remove");
removeButton.setEnabled(this.editable);
saveButton = new JButton("Done");
cancelButton = new JButton("Cancel");
cancelButton.setEnabled(this.editable);
table = new AttributeTable();
table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
tablePreferences = new TableColumnPreferences(getClass(), "dbentity/dbjoinTable");
// assemble
getContentPane().setLayout(new BorderLayout());
CellConstraints cc = new CellConstraints();
PanelBuilder builder = new PanelBuilder(
new FormLayout(
"right:max(50dlu;pref), 3dlu, fill:min(150dlu;pref), 3dlu, fill:min(50dlu;pref)",
"p, 3dlu, p, 3dlu, p, 9dlu, p, 3dlu, top:14dlu, 3dlu, top:p:grow"));
builder.setDefaultDialogBorder();
builder.addSeparator("DbRelationship Information", cc.xywh(1, 1, 5, 1));
builder.addLabel("Relationship:", cc.xy(1, 3));
builder.add(name, cc.xywh(3, 3, 1, 1));
builder.addLabel("Reverse Relationship", cc.xy(1, 5));
builder.add(reverseName, cc.xywh(3, 5, 1, 1));
builder.addSeparator("Joins", cc.xywh(1, 7, 5, 1));
builder.add(new JScrollPane(table), cc.xywh(1, 9, 3, 3, "fill, fill"));
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEADING));
buttons.add(addButton);
buttons.add(removeButton);
builder.add(buttons, cc.xywh(5, 9, 1, 3));
getContentPane().add(builder.getPanel(), BorderLayout.CENTER);
getContentPane().add(PanelFactory.createButtonPanel(new JButton[]{
saveButton, cancelButton
}), BorderLayout.SOUTH);
}
private boolean initWithModel(DbRelationship aRelationship) {
// sanity check
if (aRelationship.getSourceEntity() == null) {
throw new CayenneRuntimeException("Null source entity: %s", aRelationship);
}
if (aRelationship.getSourceEntity().getDataMap() == null) {
throw new CayenneRuntimeException("Null DataMap: %s", aRelationship.getSourceEntity());
}
if (aRelationship.getTargetEntity() == null) {
JOptionPane.showMessageDialog(
this,
"Please select target DbEntity first",
"Select target",
JOptionPane.INFORMATION_MESSAGE);
return false;
}
// Once assigned, can reference relationship directly. Would it be
// OK to assign relationship at the very top of this method?
relationship = aRelationship;
reverseRelationship = relationship.getReverseRelationship();
// init UI components
setTitle("DbRelationship Info: "
+ relationship.getSourceEntity().getName()
+ " to "
+ relationship.getTargetEntityName());
table.setModel(new DbJoinTableModel(relationship, getMediator(), this, true));
TableColumn sourceColumn = table.getColumnModel().getColumn(
DbJoinTableModel.SOURCE);
JComboBox comboBox = Application.getWidgetFactory().createComboBox(
ModelerUtil.getDbAttributeNames(getMediator(), relationship.getSourceEntity()), true);
AutoCompletion.enable(comboBox);
sourceColumn.setCellEditor(Application.getWidgetFactory().createCellEditor(
comboBox));
TableColumn targetColumn = table.getColumnModel().getColumn(
DbJoinTableModel.TARGET);
comboBox = Application.getWidgetFactory().createComboBox(
ModelerUtil.getDbAttributeNames(getMediator(), relationship.getTargetEntity()), true);
AutoCompletion.enable(comboBox);
targetColumn.setCellEditor(Application.getWidgetFactory().createCellEditor(
comboBox));
if (reverseRelationship != null) {
reverseName.setText(reverseRelationship.getName());
}
name.setText(relationship.getName());
tablePreferences.bind(table, null, null, null, DbJoinTableModel.SOURCE, true);
return true;
}
private void initController() {
addButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DbJoinTableModel model = (DbJoinTableModel) table.getModel();
DbJoin join = new DbJoin(relationship);
model.addRow(join);
undo.addDbJoinAddUndo(join);
table.select(model.getRowCount() - 1);
}
});
removeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DbJoinTableModel model = (DbJoinTableModel) table.getModel();
stopEditing();
int row = table.getSelectedRow();
DbJoin join = model.getJoin(row);
undo.addDbJoinRemoveUndo(join);
model.removeRow(join);
}
});
saveButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cancelPressed = false;
if (editable) {
save();
}
dispose();
}
});
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cancelPressed = true;
setVisible(false);
}
});
}
public boolean isCancelPressed() {
return cancelPressed;
}
private void stopEditing() {
// Stop whatever editing may be taking place
int col_index = table.getEditingColumn();
if (col_index >= 0) {
TableColumn col = table.getColumnModel().getColumn(col_index);
col.getCellEditor().stopCellEditing();
}
}
private void save() {
stopEditing();
DbJoinTableModel model = (DbJoinTableModel) table.getModel();
boolean updatingReverse = model.getObjectList().size() > 0;
// handle name update
handleNameUpdate(relationship, name.getText().trim());
model.commit();
// check "to dep pk" setting,
// maybe this is no longer valid
if (relationship.isToDependentPK() && !relationship.isValidForDepPk()) {
relationship.setToDependentPK(false);
}
// If new reverse DbRelationship was created, add it to the target
// Don't create reverse with no joins - makes no sense...
if (updatingReverse) {
// If didn't find anything, create reverseDbRel
if (reverseRelationship == null) {
reverseRelationship = new DbRelationship();
reverseRelationship.setName(NameBuilder
.builder(reverseRelationship, relationship.getTargetEntity())
.baseName(reverseName.getText().trim())
.name());
reverseRelationship.setSourceEntity(relationship.getTargetEntity());
reverseRelationship.setTargetEntityName(relationship.getSourceEntity());
reverseRelationship.setToMany(!relationship.isToMany());
relationship.getTargetEntity().addRelationship(reverseRelationship);
// fire only if the relationship is to the same entity...
// this is needed to update entity view...
if (relationship.getSourceEntity() == relationship.getTargetEntity()) {
getMediator().fireDbRelationshipEvent(
new RelationshipEvent(
this,
reverseRelationship,
reverseRelationship.getSourceEntity(),
MapEvent.ADD));
}
} else {
handleNameUpdate(reverseRelationship, reverseName.getText().trim());
}
Collection<DbJoin> reverseJoins = getReverseJoins();
reverseRelationship.setJoins(reverseJoins);
// check if joins map to a primary key of this entity
if (!relationship.isToDependentPK() && reverseRelationship.isValidForDepPk()) {
reverseRelationship.setToDependentPK(true);
}
}
Application.getInstance().getUndoManager().addEdit(undo);
getMediator().fireDbRelationshipEvent(
new RelationshipEvent(this, relationship, relationship.getSourceEntity()));
}
private void handleNameUpdate(DbRelationship relationship, String userInputName) {
if(Util.nullSafeEquals(relationship.getName(), userInputName)) {
return;
}
String sourceEntityName = NameBuilder
.builder(relationship, relationship.getSourceEntity())
.baseName(userInputName)
.name();
if (Util.nullSafeEquals(sourceEntityName, relationship.getName())) {
return;
}
String oldName = relationship.getName();
relationship.setName(sourceEntityName);
undo.addNameUndo(relationship, oldName, sourceEntityName);
getMediator().fireDbRelationshipEvent(
new RelationshipEvent(this, relationship, relationship.getSourceEntity(), oldName));
}
private Collection<DbJoin> getReverseJoins() {
Collection<DbJoin> joins = relationship.getJoins();
if ((joins == null) || (joins.size() == 0)) {
return Collections.emptyList();
}
List<DbJoin> reverseJoins = new ArrayList<>(joins.size());
// Loop through the list of attribute pairs, create reverse pairs
// and put them to the reverse list.
for (DbJoin pair : joins) {
DbJoin reverseJoin = pair.createReverseJoin();
// since reverse relationship is not yet initialized,
// reverse join will not have it set automatically
reverseJoin.setRelationship(reverseRelationship);
reverseJoins.add(reverseJoin);
}
return reverseJoins;
}
final class AttributeTable extends CayenneTable {
final Dimension preferredSize = new Dimension(203, 100);
@Override
public Dimension getPreferredScrollableViewportSize() {
return preferredSize;
}
}
}