/*****************************************************************
* 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;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.modeler.ClassLoadingService;
import org.apache.cayenne.modeler.dialog.pref.GeneralPreferences;
import org.apache.cayenne.modeler.dialog.pref.PreferenceDialog;
import org.apache.cayenne.modeler.event.DataSourceModificationEvent;
import org.apache.cayenne.modeler.event.DataSourceModificationListener;
import org.apache.cayenne.modeler.pref.DBConnectionInfo;
import org.apache.cayenne.modeler.util.CayenneController;
import org.apache.cayenne.swing.BindingBuilder;
import org.apache.cayenne.swing.ObjectBinding;
import javax.sql.DataSource;
import javax.swing.*;
import java.awt.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
import java.util.prefs.Preferences;
/**
* A subclass of ConnectionWizard that tests configured DataSource, but does not
* keep an open connection.
*
*/
public class DataSourceWizard extends CayenneController {
private DataSourceWizardView view;
private ObjectBinding dataSourceBinding;
private Map<String, DBConnectionInfo> dataSources;
private String dataSourceKey;
// this object is a clone of an object selected from the dropdown, as we
// need to allow
// local temporary modifications
private DBConnectionInfo connectionInfo;
private boolean canceled;
private DataSourceModificationListener dataSourceListener;
private DbAdapter adapter;
private DataSource dataSource;
public DataSourceWizard(CayenneController parent, String title) {
super(parent);
this.view = createView();
this.view.setTitle(title);
this.connectionInfo = new DBConnectionInfo();
initBindings();
initDataSourceListener();
}
/**
* Creates swing dialog for this wizard
*/
private DataSourceWizardView createView() {
return new DataSourceWizardView(this);
}
protected void initBindings() {
BindingBuilder builder = new BindingBuilder(getApplication().getBindingFactory(), this);
dataSourceBinding = builder.bindToComboSelection(view.getDataSources(), "dataSourceKey");
builder.bindToAction(view.getCancelButton(), "cancelAction()");
builder.bindToAction(view.getOkButton(), "okAction()");
builder.bindToAction(view.getConfigButton(), "dataSourceConfigAction()");
}
private void initDataSourceListener() {
dataSourceListener = new DataSourceModificationListener() {
@Override
public void callbackDataSourceRemoved(DataSourceModificationEvent e) {}
@Override
public void callbackDataSourceAdded(DataSourceModificationEvent e) {
setDataSourceKey(e.getDataSourceName());
refreshDataSources();
}
};
getApplication().getFrameController().getProjectController()
.addDataSourceModificationListener(dataSourceListener);
}
private void initFavouriteDataSource() {
Preferences pref = getApplication().getPreferencesNode(GeneralPreferences.class, "");
String favouriteDataSource = pref.get(GeneralPreferences.FAVOURITE_DATA_SOURCE, null);
if(favouriteDataSource != null && dataSources.containsKey(favouriteDataSource)) {
setDataSourceKey(favouriteDataSource);
dataSourceBinding.updateView();
}
}
private void removeDataSourceListener() {
getApplication().getFrameController().getProjectController()
.removeDataSourceModificationListener(dataSourceListener);
}
public String getDataSourceKey() {
return dataSourceKey;
}
public void setDataSourceKey(String dataSourceKey) {
this.dataSourceKey = dataSourceKey;
// update a clone object that will be used to obtain connection...
DBConnectionInfo currentInfo = dataSources.get(dataSourceKey);
if (currentInfo != null) {
currentInfo.copyTo(connectionInfo);
} else {
connectionInfo = new DBConnectionInfo();
}
view.getConnectionInfo().setConnectionInfo(connectionInfo);
}
/**
* Main action method that pops up a dialog asking for user selection.
* Returns true if the selection was confirmed, false - if canceled.
*/
public boolean startupAction() {
this.canceled = true;
refreshDataSources();
initFavouriteDataSource();
view.pack();
view.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
view.setModal(true);
makeCloseableOnEscape();
centerView();
view.setVisible(true);
return !canceled;
}
public DBConnectionInfo getConnectionInfo() {
return connectionInfo;
}
/**
* Tests that the entered information is valid and can be used to open a
* conneciton. Does not store the open connection.
*/
public void okAction() {
DBConnectionInfo info = getConnectionInfo();
ClassLoadingService classLoader = getApplication().getClassLoadingService();
// doing connection testing...
try {
this.adapter = info.makeAdapter(classLoader);
this.dataSource = info.makeDataSource(classLoader);
try (Connection connection = dataSource.getConnection()) {
} catch (SQLException ignore) {
}
} catch (Throwable th) {
reportError("Connection Error", th);
return;
}
onClose(false);
}
public void cancelAction() {
onClose(true);
}
/**
* On close handler. Introduced to remove data source listener.
*/
protected void onClose(boolean canceled) {
// set success flag, and unblock the caller...
this.canceled = canceled;
view.dispose();
removeDataSourceListener();
if(!canceled) {
Preferences pref = getApplication().getPreferencesNode(GeneralPreferences.class, "");
pref.put(GeneralPreferences.FAVOURITE_DATA_SOURCE, getDataSourceKey());
}
}
/**
* Opens preferences panel to allow configuration of DataSource presets.
*/
public void dataSourceConfigAction() {
PreferenceDialog prefs = new PreferenceDialog(this);
prefs.showDataSourceEditorAction(dataSourceKey);
refreshDataSources();
}
public Component getView() {
return view;
}
@SuppressWarnings("unchecked")
private void refreshDataSources() {
this.dataSources = (Map<String, DBConnectionInfo>)getApplication().getCayenneProjectPreferences().getDetailObject(DBConnectionInfo.class)
.getChildrenPreferences();
// 1.2 migration fix - update data source adapter names
final String _12package = "org.objectstyle.cayenne.";
for(DBConnectionInfo info : dataSources.values()) {
if (info.getDbAdapter() != null && info.getDbAdapter().startsWith(_12package)) {
info.setDbAdapter("org.apache.cayenne." + info.getDbAdapter().substring(_12package.length()));
}
}
String[] keys = dataSources.keySet().toArray(new String[0]);
Arrays.sort(keys);
view.getDataSources().setModel(new DefaultComboBoxModel<>(keys));
String key = null;
if (getDataSourceKey() == null || !dataSources.containsKey(getDataSourceKey())) {
if (keys.length > 0) {
key = keys[0];
}
}
setDataSourceKey(key != null ? key : getDataSourceKey());
dataSourceBinding.updateView();
}
public DataSource getDataSource() {
return dataSource;
}
/**
* Returns configured DbAdapter.
*/
public DbAdapter getAdapter() {
return adapter;
}
}