All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cayenne.modeler.dialog.DbRelationshipDialog Maven / Gradle / Ivy

There is a newer version: 5.0-M1
Show 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
 *
 *    https://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 javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import javax.swing.table.TableColumn;
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.configuration.DataChannelDescriptor;
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.DbRelationshipDialogView;
import org.apache.cayenne.modeler.ProjectController;
import org.apache.cayenne.modeler.event.RelationshipDisplayEvent;
import org.apache.cayenne.modeler.undo.CreateRelationshipUndoableEdit;
import org.apache.cayenne.modeler.undo.RelationshipUndoableEdit;
import org.apache.cayenne.modeler.util.CayenneController;
import org.apache.cayenne.modeler.util.ModelerUtil;
import org.apache.cayenne.modeler.util.combo.AutoCompletion;
import org.apache.cayenne.project.extension.info.ObjectInfo;
import org.apache.cayenne.util.Util;

/**
 * @since 4.2
 */
public class DbRelationshipDialog extends CayenneController {

    private static final Comparator DB_ENTITY_COMPARATOR =
            Comparator.comparing((Function) ent -> ent.getDataMap().getName())
                    .thenComparing(DbEntity::getName);

    private DbRelationship relationship;
    private DbRelationship reverseRelationship;

    private DbRelationshipDialogView view;

    private boolean isCreate = false;

    private ProjectController projectController;

    private RelationshipUndoableEdit undo;

    public DbRelationshipDialog(ProjectController projectController) {
        this.view = new DbRelationshipDialogView();
        this.projectController = projectController;
    }

    @Override
    public Component getView() {
        return view;
    }

    public DbRelationshipDialog createNewRelationship(DbEntity dbEntity) {
        isCreate = true;

        DbRelationship rel = new DbRelationship();
        rel.setName(NameBuilder.builder(rel, dbEntity).name());
        rel.setSourceEntity(dbEntity);

        return modifyRaltionship(rel);
    }

    public DbRelationshipDialog modifyRaltionship(DbRelationship dbRelationship) {
        this.undo = new RelationshipUndoableEdit(dbRelationship);

        this.relationship = dbRelationship;
        this.reverseRelationship = this.relationship.getReverseRelationship();

        if (relationship.getSourceEntity() == null) {
            throw new CayenneRuntimeException("Null source entity: %s", relationship);
        }
        if (relationship.getSourceEntity().getDataMap() == null) {
            throw new CayenneRuntimeException("Null DataMap: %s", relationship.getSourceEntity());
        }

        initController();
        initFromModel();

        return this;
    }

    public void startUp() {
        view.setVisible(true);
        view.dispose();
    }

    private void initFromModel() {
        TargetComboBoxModel targetComboBoxModel =
                new TargetComboBoxModel(projectController.getEntityResolver().getDbEntities());
        view.getTargetEntities().setModel(targetComboBoxModel);

        view.getSourceName().setText(relationship.getSourceEntityName());
        view.getToDepPk().setSelected(relationship.isToDependentPK());
        view.getToMany().setSelected(relationship.isToMany());

        view.getNameField().setText(relationship.getName());
        if(reverseRelationship != null) {
            view.getReverseName().setText(reverseRelationship.getName());
        }

        if(relationship.getTargetEntity() == null) {
            enableOptions(false);
        } else {
            enableInfo();
        }

        view.getComment().setText(ObjectInfo
                .getFromMetaData(projectController.getApplication().getMetaData(),
                        relationship,
                        ObjectInfo.COMMENT));
    }

    private void initController() {
        view.getTargetEntities().addActionListener(action -> {
            DbEntity selectedItem = ((TargetComboBoxModel)view.getTargetEntities().getModel()).selected;
            if(relationship.getTargetEntityName() == null) {
                relationship.setTargetEntityName(selectedItem.getName());
            } else if(!relationship.getTargetEntityName().equals(selectedItem.getName())){
                if (WarningDialogByDbTargetChange.showWarningDialog(projectController, relationship)) {
                    // clear joins...
                    relationship.removeAllJoins();
                    relationship.setTargetEntityName(selectedItem.getName());
                } else {
                    view.getTargetEntities().setSelectedItem(relationship.getTargetEntityName());
                }
                relationship.setToDependentPK(false);
                view.getToDepPk().setSelected(relationship.isValidForDepPk());
                projectController.fireDbRelationshipEvent(new RelationshipEvent(this, relationship, relationship.getSourceEntity()));
            }
            enableInfo();
        });

        view.getAddButton().addActionListener(e -> {
            DbJoinTableModel model = (DbJoinTableModel) view.getTable().getModel();

            DbJoin join = new DbJoin(relationship);
            relationship.addJoin(join);
            model.addRow(join);

            view.getTable().select(model.getRowCount() - 1);
        });

        view.getRemoveButton().addActionListener(e -> {
            DbJoinTableModel model = (DbJoinTableModel) view.getTable().getModel();
            stopEditing();
            int row = view.getTable().getSelectedRow();

            DbJoin join = model.getJoin(row);

            relationship.removeJoin(join);
            if(relationship.isValidForDepPk()) {
                view.getToDepPk().setEnabled(true);
            } else {
                view.getToDepPk().setEnabled(false);
                view.getToDepPk().setSelected(false);
                relationship.setToDependentPK(false);
            }

            model.removeRow(join);
        });

        view.getSaveButton().addActionListener(e -> {
            view.setCancelPressed(false);
            save();
            view.dispose();
            view.setVisible(false);
        });

        view.getCancelButton().addActionListener(e -> {
            view.setCancelPressed(true);
            view.setVisible(false);
        });

        view.addWindowListener(new WindowAdapter()
        {
            @Override
            public void windowClosing(WindowEvent e) {
                view.setCancelPressed(true);
            }
        });

        view.getToDepPk().setEnabled(relationship.isValidForDepPk());
        view.getToDepPk().addActionListener(selected -> {
            boolean isSelected = view.getToDepPk().isSelected();
            DbRelationship reverseRelationship = relationship.getReverseRelationship();
            if(reverseRelationship != null && reverseRelationship.isToDependentPK() && isSelected) {
                boolean setToDepPk = JOptionPane.showConfirmDialog(Application.getFrame(), "Unset reverse relationship's \"To Dep PK\" setting?",
                        "Warning", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE) == JOptionPane.OK_OPTION;
                relationship.setToDependentPK(setToDepPk);
                reverseRelationship.setToDependentPK(!setToDepPk);
            } else {
                relationship.setToDependentPK(view.getToDepPk().isSelected());
            }
        });
    }

    private void enableInfo() {
        enableOptions(true);

        view.getTable().setModel(new DbJoinTableModel(relationship,
                projectController, this, true));

        view.getTable().getModel().addTableModelListener(change -> {
            if(change.getLastRow() != Integer.MAX_VALUE) {
                if(relationship.isValidForDepPk()) {
                    view.getToDepPk().setEnabled(true);
                } else {
                    view.getToDepPk().setEnabled(false);
                }
            }
        });

        TableColumn sourceColumn = view.getTable().getColumnModel().getColumn(DbJoinTableModel.SOURCE);
        JComboBox comboBox = Application.getWidgetFactory().createComboBox(
                ModelerUtil.getDbAttributeNames(relationship.getSourceEntity()), true);

        AutoCompletion.enable(comboBox);
        sourceColumn.setCellEditor(Application.getWidgetFactory().createCellEditor(comboBox));

        TableColumn targetColumn = view.getTable().getColumnModel().getColumn(DbJoinTableModel.TARGET);
        comboBox = Application.getWidgetFactory().createComboBox(
                ModelerUtil.getDbAttributeNames(relationship.getTargetEntity()), true);
        AutoCompletion.enable(comboBox);

        targetColumn.setCellEditor(Application.getWidgetFactory().createCellEditor(comboBox));

        view.getTablePreferences().bind(view.getTable(), null, null, null, DbJoinTableModel.SOURCE, true);
    }

    private void enableOptions(boolean enable) {
        view.enableOptions(enable);
    }

    private void stopEditing() {
        // Stop whatever editing may be taking place
        int col_index = view.getTable().getEditingColumn();
        if (col_index >= 0) {
            TableColumn col = view.getTable().getColumnModel().getColumn(col_index);
            col.getCellEditor().stopCellEditing();
        }
    }

    private void save() {
        stopEditing();

        DbJoinTableModel model = (DbJoinTableModel) view.getTable().getModel();
        boolean updatingReverse = model.getObjectList().size() > 0;

        // handle name update
        handleNameUpdate(relationship, view.getNameField().getText().trim());

        model.commit();

        relationship.setToMany(view.getToMany().isSelected());

        ObjectInfo.putToMetaData(projectController.getApplication().getMetaData(),
                relationship,
                ObjectInfo.COMMENT, view.getComment().getText());
        // 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(view.getReverseName().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()) {
                    projectController
                            .fireDbRelationshipEvent(
                            new RelationshipEvent(
                                    this,
                                    reverseRelationship,
                                    reverseRelationship.getSourceEntity(),
                                    MapEvent.ADD));
                }
            } else {
                handleNameUpdate(reverseRelationship, view.getReverseName().getText().trim());
            }

            Collection reverseJoins = getReverseJoins();
            reverseRelationship.setJoins(reverseJoins);

            // check if joins map to a primary key of this entity
            if (!relationship.isToDependentPK() && reverseRelationship.isValidForDepPk()) {
                reverseRelationship.setToDependentPK(true);
            }
        }

        fireDbRelationshipEvent(isCreate);
    }

    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);

        projectController
                .fireDbRelationshipEvent(
                new RelationshipEvent(this, relationship, relationship.getSourceEntity(), oldName));
    }

    private Collection getReverseJoins() {
        Collection joins = relationship.getJoins();

        if ((joins == null) || (joins.size() == 0)) {
            return Collections.emptyList();
        }

        List 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;
    }

    private void fireDbRelationshipEvent(boolean isCreate) {
        if(!isCreate) {
            projectController
                    .fireDbRelationshipEvent(
                            new RelationshipEvent(this, relationship, relationship.getSourceEntity(), MapEvent.CHANGE));

            Application.getInstance().getUndoManager().addEdit(undo);
        } else {
            DbEntity dbEntity = relationship.getSourceEntity();
            if(dbEntity.getRelationship(relationship.getName()) == null) {
                dbEntity.addRelationship(relationship);
            }

            projectController.fireDbRelationshipEvent(new RelationshipEvent(this, relationship, dbEntity, MapEvent.ADD));

            RelationshipDisplayEvent rde = new RelationshipDisplayEvent(this, relationship, dbEntity, projectController.getCurrentDataMap(),
                    (DataChannelDescriptor) projectController.getProject().getRootNode());

            projectController.fireDbRelationshipDisplayEvent(rde);

            Application.getInstance().getUndoManager().addEdit(
                    new CreateRelationshipUndoableEdit(relationship.getSourceEntity(), new DbRelationship[]{relationship}));
        }
    }

    public Optional getRelationship() {
        return view.isCancelPressed() ? Optional.empty() : Optional.of(relationship);
    }

    final class TargetComboBoxModel extends AbstractListModel implements ComboBoxModel {

        private List entities;
        private DbEntity selected;

        TargetComboBoxModel(Collection dbEntities) {
            super();
            this.entities = new ArrayList<>(dbEntities);
            this.entities.sort(DB_ENTITY_COMPARATOR);
            selected = relationship.getTargetEntity();
        }

        private String getTitle(DbEntity entity) {
            if(entity == null) {
                return "";
            }
            return relationship.getSourceEntity().getDataMap() == entity.getDataMap()
                    ? entity.getName()
                    : entity.getName() + " (" + entity.getDataMap().getName() + ')';
        }

        @Override
        public int getSize() {
            return entities.size();
        }

        @Override
        public String getElementAt(int index) {
            return getTitle(entities.get(index));
        }

        @Override
        public void setSelectedItem(Object anItem) {
            String title = (String)anItem;
            if(title != null) {
                int spacer = title.indexOf(' ');
                if (spacer != -1) {
                    title = title.substring(0, spacer);
                }
            }
            selected = projectController.getEntityResolver().getDbEntity(title);
        }

        @Override
        public Object getSelectedItem() {
            return getTitle(selected);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy