/*******************************************************************************
* Copyright (c) 2013, 2014 Pivotal Software, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of 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.
*
* Contributors:
* Pivotal Software, Inc. - initial API and implementation
********************************************************************************/
package org.cloudfoundry.ide.eclipse.server.ui.internal.tunnel;
import java.util.Properties;
import org.cloudfoundry.client.lib.domain.CloudService;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryPlugin;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudFoundryServer;
import org.cloudfoundry.ide.eclipse.server.core.internal.CloudUtil;
import org.cloudfoundry.ide.eclipse.server.core.internal.client.ICloudFoundryOperation;
import org.cloudfoundry.ide.eclipse.server.core.internal.client.TunnelBehaviour;
import org.cloudfoundry.ide.eclipse.server.core.internal.tunnel.CaldecottTunnelDescriptor;
import org.cloudfoundry.ide.eclipse.server.ui.internal.CloudFoundryImages;
import org.cloudfoundry.ide.eclipse.server.ui.internal.Messages;
import org.cloudfoundry.ide.eclipse.server.ui.internal.actions.CloudFoundryEditorAction;
import org.cloudfoundry.ide.eclipse.server.ui.internal.editor.CloudFoundryApplicationsEditorPage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.datatools.connectivity.ConnectionProfileException;
import org.eclipse.datatools.connectivity.IConnectionProfile;
import org.eclipse.datatools.connectivity.ProfileManager;
import org.eclipse.datatools.connectivity.drivers.DriverInstance;
import org.eclipse.datatools.connectivity.drivers.DriverManager;
import org.eclipse.datatools.connectivity.drivers.IDriverMgmtConstants;
import org.eclipse.datatools.connectivity.drivers.IPropertySet;
import org.eclipse.datatools.connectivity.drivers.PropertySetImpl;
import org.eclipse.datatools.connectivity.drivers.jdbc.IJDBCConnectionProfileConstants;
import org.eclipse.datatools.connectivity.drivers.models.TemplateDescriptor;
import org.eclipse.datatools.connectivity.internal.ui.dialogs.DriverDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.progress.UIJob;
/**
* Creates a connection to a Data Tools profile based on service tunnel
* credentials. If a tunnel doesn't already exist, a new tunnel will be created
* automatically.
* <p/>
* If a Data Tools driver instance does not yet exist, this will prompt the user
* for a driver information (e.g., the JDBC jar file for the driver in the local
* file system).
* <p/>
* Data tools connection profiles and driver instance names are generated to
* reflect the CF service and server where the service resides. Generally, they
* should be unique enough such that they will not clash with other existing
* profiles and driver instances.
* <p/>
* Existing connection profiles will be reused, if and only if the profile
* information matches the tunnel information (i.e. username, URL,
* databasename). Otherwise, the profile may be deleted if it contains old
* information and replaced with a new one.
*/
public abstract class DataToolsTunnelAction extends CloudFoundryEditorAction {
private static final String CLOUD_FOUNDRY_JDBC_PROFILE_DESCRIPTION = Messages.DataToolsTunnelAction_TEXT_PROF_DESCRIP;
private final static String DATA_TOOLS_DRIVER_DEFINITION_ID = "org.eclipse.datatools.connectivity.driverDefinitionID"; //$NON-NLS-1$
private final static String ACTION_NAME = Messages.DataToolsTunnelAction_TEXT_CONN_DT;
private DataSourceDescriptor descriptor;
private final CloudFoundryServer cloudServer;
private final String JOBNAME = Messages.DataToolsTunnelAction_TEXT_CREATE_JOB;
private CaldecottTunnelDescriptor tunnelDescriptor;
protected DataToolsTunnelAction(CloudFoundryApplicationsEditorPage editorPage, DataSourceDescriptor descriptor,
CloudFoundryServer cloudServer) {
super(editorPage, RefreshArea.ALL);
this.descriptor = descriptor;
setText(ACTION_NAME);
setImageDescriptor(CloudFoundryImages.JDBC_DATA_TOOLS);
this.cloudServer = cloudServer;
/**
* FIXNS: Disabled for CF 1.5.0 until tunnel support at client level are
* updated.
*/
setEnabled(false);
setToolTipText(TunnelActionProvider.DISABLED_V2_TOOLTIP_MESSAGE);
}
public static DataToolsTunnelAction getAction(CloudFoundryApplicationsEditorPage editorPage,
CloudService cloudService, CaldecottTunnelDescriptor descriptor, CloudFoundryServer cloudServer) {
String serviceVendor = descriptor != null ? descriptor.getServiceVendor() : null;
if (serviceVendor == null && cloudService != null) {
serviceVendor = CloudUtil.getServiceVendor(cloudService);
}
if ("mysql".equals(serviceVendor)) { //$NON-NLS-1$
return MySQLDataSourceTunnelAction.getTunnelAction(editorPage, descriptor, cloudService, cloudServer);
}
return null;
}
abstract protected void setProperties(Properties properties, CaldecottTunnelDescriptor tunnelDescriptor);
abstract protected boolean matchesProfile(IConnectionProfile connection, CaldecottTunnelDescriptor tunnelDescriptor);
protected DataSourceDescriptor getDescriptor() {
return descriptor;
}
@Override
public String getJobName() {
return JOBNAME;
}
protected Job getJob() {
Job job = super.getJob();
job.setUser(true);
return job;
}
public ICloudFoundryOperation getOperation(IProgressMonitor monitor) throws CoreException {
return new ModifyEditorOperation() {
@Override
protected void performOperation(IProgressMonitor monitor) throws CoreException {
IStatus status = openConnection(monitor);
if (!status.isOK()) {
throw new CoreException(status);
}
}
};
}
protected IStatus openConnection(IProgressMonitor monitor) {
tunnelDescriptor = descriptor.getTunnelDescriptor();
// if there is no tunnel descriptor, create the tunnel first
if (tunnelDescriptor == null && descriptor.getCloudService() != null) {
try {
TunnelBehaviour handler = new TunnelBehaviour(cloudServer);
tunnelDescriptor = handler.startCaldecottTunnel(descriptor.getCloudService().getName(), monitor, false);
}
catch (CoreException e) {
return CloudFoundryPlugin.getErrorStatus(e);
}
}
if (tunnelDescriptor != null) {
// Now check if there are any options that require values,
// and fill in any tunnel
// options
// THis must be wrapped in a UI Job
UIJob uiJob = new UIJob(getJobName()) {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
IConnectionProfile profile = getConnectionProfile();
IStatus status = Status.OK_STATUS;
if (profile != null) {
status = profile.connect();
}
else {
status = CloudFoundryPlugin.getErrorStatus(NLS.bind(Messages.DataToolsTunnelAction_ERROR_CREATE_PROFILE, tunnelDescriptor.getServiceName()));
}
return status;
}
};
uiJob.schedule();
return Status.OK_STATUS;
}
else {
return CloudFoundryPlugin.getErrorStatus(NLS.bind(Messages.DataToolsTunnelAction_ERROR_CREATE_TUNNEL_FOR, tunnelDescriptor.getServiceName()));
}
}
protected IConnectionProfile getConnectionProfile() {
// Check if there is an existing profile
IConnectionProfile profile = null;
IConnectionProfile[] profiles = ProfileManager.getInstance().getProfiles();
if (profiles != null) {
// If a profile exists with the same exact name as given by the
// CF data source integration, then check credentials. If they
// match, use it
// If not, delete and create a new profile. If in the future
// profiles should also be checked against the provider (e.g.
// mysql, psql), and a new
// name given, also check the provider ID
for (IConnectionProfile prf : profiles) {
if (
// prf.getProviderId().equals(descriptor.getDriverProviderID())
// &&
prf.getName().equals(descriptor.getProfileName())) {
profile = prf;
break;
}
}
}
// Otherwise create one, and if necessary also create a new driver
// definition
if (profile == null) {
DriverInstance driverInstance = null;
// See if there is already a driver instance that can be used;
driverInstance = DriverManager.getInstance().getDriverInstanceByName(descriptor.getDriverName());
// Prompt for new driver instance
if (driverInstance == null) {
TemplateDescriptor[] templates = TemplateDescriptor.getDriverTemplateDescriptors();
TemplateDescriptor driverTemplate = null;
if (templates != null) {
for (TemplateDescriptor temp : templates) {
String templateID = temp.getId();
if (templateID.contains(descriptor.getDriverTemplateIdentifier())) {
driverTemplate = temp;
break;
}
}
}
if (driverTemplate != null) {
IPropertySet properties = new PropertySetImpl(descriptor.getDriverName(), driverTemplate.getName());
Properties props = new Properties();
props.setProperty(IDriverMgmtConstants.PROP_DEFN_JARLIST, Messages.DataToolsTunnelAction_PROP_REPLACE);
props.setProperty(IDriverMgmtConstants.PROP_DEFN_TYPE, driverTemplate.getId());
// Must set the properties as the driver dialogue
// throws exceptions if all properties are not set with
// values
// Properties get set again later regardless if a driver
// instance
// was created or not
setProperties(props, tunnelDescriptor);
properties.setBaseProperties(props);
Shell shell = PlatformUI.getWorkbench().getModalDialogShellProvider().getShell();
if (shell != null) {
DriverDialog jdbcDriverDialog = new DriverDialog(shell, driverTemplate.getParentCategory());
jdbcDriverDialog.setPropertySet(properties);
jdbcDriverDialog.setEditMode(true);
jdbcDriverDialog.setIsEditable(true);
if (jdbcDriverDialog.open() == Window.OK) {
properties = jdbcDriverDialog.getPropertySet();
if (properties != null) {
DriverManager.getInstance().addDriverInstance(properties);
driverInstance = DriverManager.getInstance().getDriverInstanceByName(
descriptor.getDriverName());
}
}
}
}
}
if (driverInstance != null) {
Properties props = driverInstance.getPropertySet().getBaseProperties();
props.setProperty(DATA_TOOLS_DRIVER_DEFINITION_ID, driverInstance.getId());
try {
String providerID = descriptor.getDriverProviderID();
profile = ProfileManager.getInstance().createProfile(descriptor.getProfileName(),
CLOUD_FOUNDRY_JDBC_PROFILE_DESCRIPTION, providerID, props);
}
catch (ConnectionProfileException e) {
CloudFoundryPlugin.logError(e);
}
}
else {
CloudFoundryPlugin.logError("Failed to create driver instance for: " //$NON-NLS-1$
+ CloudUtil.getServiceVendor(descriptor.getCloudService()) + " - " //$NON-NLS-1$
+ tunnelDescriptor.getServiceName());
}
}
// Check if the profile credentials have changed, which may happen if a
// service
// in CF server is deleted and then recreated again with the same name
if (profile != null && !matchesProfile(profile, tunnelDescriptor)) {
// Different credentials for the same profile, most likely
// meaning that a new tunnel was created, and the old profile
// has obsolete credentials.
if (profile.getConnectionState() == IConnectionProfile.CONNECTED_STATE) {
IStatus status = profile.disconnect();
if (!status.isOK()) {
CloudFoundryPlugin.log(status);
}
}
// Change the properties to reflect changes
// Set the properties for the tunnel values
Properties props = profile.getBaseProperties();
setProperties(props, tunnelDescriptor);
profile.setBaseProperties(props);
}
return profile;
}
/**
*
* Descriptor that contains information necessary to create a connection to
* a data tools profile
*
*/
static class DataSourceDescriptor {
protected final CaldecottTunnelDescriptor tunnelDescriptor;
protected final CloudService cloudService;
protected final String profileName;
protected final String driverProviderID;
protected final String driverName;
protected final String driverTemplateIdentifier;
public DataSourceDescriptor(CaldecottTunnelDescriptor tunnelDescriptor, CloudService cloudService,
String profileName, String driverProviderID, String driverName, String driverTemplateIdentifier) {
this.tunnelDescriptor = tunnelDescriptor;
this.cloudService = cloudService;
this.profileName = profileName;
this.driverProviderID = driverProviderID;
this.driverName = driverName;
this.driverTemplateIdentifier = driverTemplateIdentifier;
}
public CaldecottTunnelDescriptor getTunnelDescriptor() {
return tunnelDescriptor;
}
public CloudService getCloudService() {
return cloudService;
}
/**
*
* @return A unique name given to the data tools connection profile for
* the given CF service.
*/
public String getProfileName() {
return profileName;
}
/**
* E.g. org.eclipse.datatools.enablement.mysql.connectionProfile for
* MySQL Data Tools provider.
* @return the data tools provider ID for the given data service vendor.
*/
public String getDriverProviderID() {
return driverProviderID;
}
/**
* A unique name given to the driver instance that should be used by the
* profile. This driver instance points to the JDBC jar file in the
* local file system and may have additional configuration.
* @return driver name to be used by the connection profile
*/
public String getDriverName() {
return driverName;
}
/**
* A template identifier is a portion of a driver template ID that
* should be used to match a template that corresponds to a desired
* driver template. For example, "mysql" to find a MySQL Data Tools
* templates. Templates are needed in order to correctly create a Data
* Tools driver instance.
* @return a driver template identifier like "mysql". Used to search for
* registered Data Tools driver templates
*/
public String getDriverTemplateIdentifier() {
return driverTemplateIdentifier;
}
}
static class MySQLDataSourceTunnelAction extends DataToolsTunnelAction {
public MySQLDataSourceTunnelAction(CloudFoundryApplicationsEditorPage editorPage,
DataSourceDescriptor descriptor, CloudFoundryServer cloudServer) {
super(editorPage, descriptor, cloudServer);
}
@Override
protected void setProperties(Properties properties, CaldecottTunnelDescriptor tunnelDescriptor) {
properties.setProperty(IJDBCConnectionProfileConstants.USERNAME_PROP_ID, tunnelDescriptor.getUserName());
properties.setProperty(IJDBCConnectionProfileConstants.PASSWORD_PROP_ID, tunnelDescriptor.getPassword());
properties.setProperty(IJDBCConnectionProfileConstants.URL_PROP_ID, tunnelDescriptor.getURL());
properties.setProperty(IJDBCConnectionProfileConstants.DATABASE_NAME_PROP_ID,
tunnelDescriptor.getDatabaseName());
}
/**
*
* @param editorPage not null
* @param tunnelDescriptor it may be null at this stage if the tunnel
* hasn't yet been created
* @param cloudService not null
* @param cloudServer not null
* @return
*/
static DataToolsTunnelAction getTunnelAction(CloudFoundryApplicationsEditorPage editorPage,
CaldecottTunnelDescriptor tunnelDescriptor, CloudService cloudService, CloudFoundryServer cloudServer) {
String profileName = "Cloud Foundry" + " - " + cloudServer.getServer().getName() + " - " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ CloudUtil.getServiceVendor(cloudService) + " - " + cloudService.getName(); //$NON-NLS-1$
String driverProviderID = "org.eclipse.datatools.enablement.mysql.connectionProfile"; //$NON-NLS-1$
String driverName = "Cloud Foundry Tunnel " + " " + CloudUtil.getServiceVendor(cloudService) + " " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ cloudService.getVersion();
String driverTemplateIdentifier = "mysql"; //$NON-NLS-1$
DataSourceDescriptor dataSourceDescriptor = new DataSourceDescriptor(tunnelDescriptor, cloudService,
profileName, driverProviderID, driverName, driverTemplateIdentifier);
return new MySQLDataSourceTunnelAction(editorPage, dataSourceDescriptor, cloudServer);
}
@Override
protected boolean matchesProfile(IConnectionProfile connection, CaldecottTunnelDescriptor tunnelDescriptor) {
Properties properties = connection.getBaseProperties();
String userName = properties.getProperty(IJDBCConnectionProfileConstants.USERNAME_PROP_ID);
String password = properties.getProperty(IJDBCConnectionProfileConstants.PASSWORD_PROP_ID);
String url = properties.getProperty(IJDBCConnectionProfileConstants.URL_PROP_ID);
String dataBaseName = properties.getProperty(IJDBCConnectionProfileConstants.DATABASE_NAME_PROP_ID);
// For MySQL username, url and database name should not be null in
// the tunnel descriptor.
return tunnelDescriptor.getUserName().equals(userName) && tunnelDescriptor.getURL().equals(url)
&& tunnelDescriptor.getDatabaseName().equals(dataBaseName)
&& tunnelDescriptor.getPassword().equals(password);
}
}
}