org.apache.cayenne.modeler.dialog.db.merge.MergerOptions.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.cayenne.modeler.dialog.db.merge.MergerOptions.java

Source

/*****************************************************************
 *   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.db.merge;

import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.configuration.event.DataMapEvent;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.dbsync.merge.token.db.AbstractToDbToken;
import org.apache.cayenne.dbsync.merge.DataMapMerger;
import org.apache.cayenne.dbsync.reverse.dbload.DefaultModelMergeDelegate;
import org.apache.cayenne.dbsync.merge.context.MergeDirection;
import org.apache.cayenne.dbsync.merge.context.MergerContext;
import org.apache.cayenne.dbsync.merge.token.MergerToken;
import org.apache.cayenne.dbsync.reverse.dbload.ModelMergeDelegate;
import org.apache.cayenne.dbsync.reverse.dbload.ProxyModelMergeDelegate;
import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactoryProvider;
import org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator;
import org.apache.cayenne.dbsync.naming.NoStemStemmer;
import org.apache.cayenne.dbsync.reverse.dbload.DbLoader;
import org.apache.cayenne.dbsync.reverse.dbload.DbLoaderConfiguration;
import org.apache.cayenne.dbsync.reverse.dbload.LoggingDbLoaderDelegate;
import org.apache.cayenne.dbsync.reverse.filters.FiltersConfig;
import org.apache.cayenne.dbsync.reverse.filters.PatternFilter;
import org.apache.cayenne.dbsync.reverse.filters.TableFilter;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.event.MapEvent;
import org.apache.cayenne.modeler.Application;
import org.apache.cayenne.modeler.ProjectController;
import org.apache.cayenne.modeler.dialog.ValidationResultBrowser;
import org.apache.cayenne.modeler.pref.DBConnectionInfo;
import org.apache.cayenne.modeler.util.CayenneController;
import org.apache.cayenne.project.Project;
import org.apache.cayenne.resource.Resource;
import org.apache.cayenne.swing.BindingBuilder;
import org.apache.cayenne.swing.ObjectBinding;
import org.apache.cayenne.tools.dbimport.DefaultDbImportAction;
import org.apache.cayenne.validation.ValidationResult;
import org.apache.commons.logging.LogFactory;

import javax.sql.DataSource;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.Component;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class MergerOptions extends CayenneController {

    protected MergerOptionsView view;
    protected ObjectBinding sqlBinding;

    protected DBConnectionInfo connectionInfo;
    protected DataMap dataMap;
    protected DbAdapter adapter;
    protected String textForSQL;

    protected MergerTokenSelectorController tokens;
    protected String defaultCatalog;
    protected String defaultSchema;
    private MergerTokenFactoryProvider mergerTokenFactoryProvider;

    public MergerOptions(ProjectController parent, String title, DBConnectionInfo connectionInfo, DataMap dataMap,
            String defaultCatalog, String defaultSchema, MergerTokenFactoryProvider mergerTokenFactoryProvider) {
        super(parent);

        this.mergerTokenFactoryProvider = mergerTokenFactoryProvider;
        this.dataMap = dataMap;
        this.tokens = new MergerTokenSelectorController(parent);
        this.view = new MergerOptionsView(tokens.getView());
        this.connectionInfo = connectionInfo;
        this.defaultCatalog = defaultCatalog;
        this.defaultSchema = defaultSchema;
        this.view.setTitle(title);
        initController();

        prepareMigrator();
        createSQL();
        refreshView();
    }

    public Component getView() {
        return view;
    }

    public String getTextForSQL() {
        return textForSQL;
    }

    protected void initController() {

        BindingBuilder builder = new BindingBuilder(getApplication().getBindingFactory(), this);

        sqlBinding = builder.bindToTextArea(view.getSql(), "textForSQL");

        builder.bindToAction(view.getGenerateButton(), "generateSchemaAction()");
        builder.bindToAction(view.getSaveSqlButton(), "storeSQLAction()");
        builder.bindToAction(view.getCancelButton(), "closeAction()");

        // refresh SQL if different tables were selected
        view.getTabs().addChangeListener(new ChangeListener() {

            public void stateChanged(ChangeEvent e) {
                if (view.getTabs().getSelectedIndex() == 1) {
                    // this assumes that some tables where checked/unchecked... not very
                    // efficient
                    refreshGeneratorAction();
                }
            }
        });
    }

    /**
     * check database and create the {@link List} of {@link MergerToken}s
     */
    protected void prepareMigrator() {
        try {
            adapter = connectionInfo.makeAdapter(getApplication().getClassLoadingService());

            MergerTokenFactory mergerTokenFactory = mergerTokenFactoryProvider.get(adapter);
            tokens.setMergerTokenFactory(mergerTokenFactory);

            FiltersConfig filters = FiltersConfig.create(defaultCatalog, defaultSchema, TableFilter.everything(),
                    PatternFilter.INCLUDE_NOTHING);

            DataMapMerger merger = DataMapMerger.builder(mergerTokenFactory).filters(filters).build();

            DbLoaderConfiguration config = new DbLoaderConfiguration();
            config.setFiltersConfig(filters);

            DataSource dataSource = connectionInfo.makeDataSource(getApplication().getClassLoadingService());

            DataMap dbImport;
            try (Connection conn = dataSource.getConnection();) {
                dbImport = new DbLoader(adapter, conn, config,
                        new LoggingDbLoaderDelegate(LogFactory.getLog(DbLoader.class)),
                        new DefaultObjectNameGenerator(NoStemStemmer.getInstance())).load();
            } catch (SQLException e) {
                throw new CayenneRuntimeException("Can't doLoad dataMap from db.", e);
            }

            tokens.setTokens(merger.createMergeTokens(dataMap, dbImport));
        } catch (Exception ex) {
            reportError("Error loading adapter", ex);
        }
    }

    /**
     * Returns SQL statements generated for selected schema generation options.
     */
    protected void createSQL() {
        // convert them to string representation for display
        StringBuilder buf = new StringBuilder();

        Iterator<MergerToken> it = tokens.getSelectedTokens().iterator();
        String batchTerminator = adapter.getBatchTerminator();

        String lineEnd = batchTerminator != null ? "\n" + batchTerminator + "\n\n" : "\n\n";
        while (it.hasNext()) {
            MergerToken token = it.next();

            if (token instanceof AbstractToDbToken) {
                AbstractToDbToken tdb = (AbstractToDbToken) token;
                for (String sql : tdb.createSql(adapter)) {
                    buf.append(sql);
                    buf.append(lineEnd);
                }
            }
        }

        textForSQL = buf.toString();
    }

    protected void refreshView() {
        sqlBinding.updateView();
    }

    // ===============
    // Actions
    // ===============

    /**
     * Starts options dialog.
     */
    public void startupAction() {
        view.pack();
        view.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        view.setModal(true);
        makeCloseableOnEscape();
        centerView();
        view.setVisible(true);
    }

    public void refreshGeneratorAction() {
        refreshSQLAction();
    }

    /**
     * Updates a text area showing generated SQL.
     */
    public void refreshSQLAction() {
        createSQL();
        sqlBinding.updateView();
    }

    /**
     * Performs configured schema operations via DbGenerator.
     */
    public void generateSchemaAction() {
        refreshGeneratorAction();

        // sanity check...
        List<MergerToken> tokensToMigrate = tokens.getSelectedTokens();
        if (tokensToMigrate.isEmpty()) {
            JOptionPane.showMessageDialog(getView(), "Nothing to migrate.");
            return;
        }

        DataSource dataSource;
        try {
            dataSource = connectionInfo.makeDataSource(getApplication().getClassLoadingService());
        } catch (SQLException ex) {
            reportError("Migration Error", ex);
            return;
        }

        final Collection<ObjEntity> loadedObjEntities = new LinkedList<>();

        MergerContext mergerContext = MergerContext.builder(dataMap).syntheticDataNode(dataSource, adapter)
                .delegate(createDelegate(loadedObjEntities)).build();

        boolean modelChanged = applyTokens(tokensToMigrate, mergerContext);

        DefaultDbImportAction.flattenManyToManyRelationships(dataMap, loadedObjEntities,
                mergerContext.getNameGenerator());

        notifyProjectModified(modelChanged);

        reportFailures(mergerContext);
    }

    private ModelMergeDelegate createDelegate(final Collection<ObjEntity> loadedObjEntities) {
        return new ProxyModelMergeDelegate(new DefaultModelMergeDelegate()) {
            @Override
            public void objEntityAdded(ObjEntity ent) {
                loadedObjEntities.add(ent);
                super.objEntityAdded(ent);
            }
        };
    }

    private boolean applyTokens(List<MergerToken> tokensToMigrate, MergerContext mergerContext) {
        boolean modelChanged = false;

        try {
            for (MergerToken tok : tokensToMigrate) {
                int numOfFailuresBefore = getFailuresCount(mergerContext);

                tok.execute(mergerContext);

                if (!modelChanged && tok.getDirection().equals(MergeDirection.TO_MODEL)) {
                    modelChanged = true;
                }
                if (numOfFailuresBefore == getFailuresCount(mergerContext)) {
                    // looks like the token executed without failures
                    tokens.removeToken(tok);
                }
            }
        } catch (Throwable th) {
            reportError("Migration Error", th);
        }

        return modelChanged;
    }

    private int getFailuresCount(MergerContext mergerContext) {
        return mergerContext.getValidationResult().getFailures().size();
    }

    private void reportFailures(MergerContext mergerContext) {
        ValidationResult failures = mergerContext.getValidationResult();
        if (failures == null || !failures.hasFailures()) {
            JOptionPane.showMessageDialog(getView(), "Migration Complete.");
        } else {
            new ValidationResultBrowser(this).startupAction("Migration Complete",
                    "Migration finished. The following problem(s) were ignored.", failures);
        }
    }

    private void notifyProjectModified(boolean modelChanged) {
        if (!modelChanged) {
            return;
        }

        // mark the model as unsaved
        Project project = getApplication().getProject();
        project.setModified(true);

        ProjectController projectController = getProjectController();
        projectController.setDirty(true);

        projectController.fireDataMapEvent(new DataMapEvent(Application.getFrame(), dataMap, MapEvent.REMOVE));
        projectController.fireDataMapEvent(new DataMapEvent(Application.getFrame(), dataMap, MapEvent.ADD));
    }

    /**
     * Allows user to save generated SQL in a file.
     */
    public void storeSQLAction() {
        JFileChooser fc = new JFileChooser();
        fc.setDialogType(JFileChooser.SAVE_DIALOG);
        fc.setDialogTitle("Save SQL Script");

        Resource projectDir = getApplication().getProject().getConfigurationResource();

        if (projectDir != null) {
            fc.setCurrentDirectory(new File(projectDir.getURL().getPath()));
        }

        if (fc.showSaveDialog(getView()) == JFileChooser.APPROVE_OPTION) {
            refreshGeneratorAction();

            try {
                File file = fc.getSelectedFile();
                FileWriter fw = new FileWriter(file);
                PrintWriter pw = new PrintWriter(fw);
                pw.print(textForSQL);
                pw.flush();
                pw.close();
            } catch (IOException ex) {
                reportError("Error Saving SQL", ex);
            }
        }
    }

    private ProjectController getProjectController() {
        return getApplication().getFrameController().getProjectController();
    }

    public void closeAction() {
        view.dispose();
    }

}