blob: 5faa9c08093fa0186dde3d2c467152675edf0881 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed 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 com.motorolamobility.studio.android.db.core.ui;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.datatools.connectivity.ConnectionProfileException;
import org.eclipse.datatools.modelbase.sql.tables.Table;
import org.eclipse.datatools.sqltools.result.ResultsViewAPI;
import org.eclipse.datatools.sqltools.result.core.IResultManagerListener;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import com.motorola.studio.android.common.log.StudioLogger;
import com.motorola.studio.android.common.utilities.EclipseUtils;
import com.motorolamobility.studio.android.db.core.DbCoreActivator;
import com.motorolamobility.studio.android.db.core.exception.MotodevDbException;
import com.motorolamobility.studio.android.db.core.i18n.DbCoreNLS;
import com.motorolamobility.studio.android.db.core.model.DbModel;
import com.motorolamobility.studio.android.db.core.model.TableModel;
/**
* This class represents a database node on the DB Explorer tree view
*/
public class DbNode extends AbstractTreeNode implements IDbNode
{
/**
* Properties name space.
*/
public static final String PROP_NAMESPACE = "com.motorolamobility.studio.android.db.core"; //$NON-NLS-1$
/**
* Property value used to check if the database is disconnected.
*/
public static final String PROP_VALUE_DB_DISCONNECTED =
"com.motorolamobility.studio.android.db.core.databaseDisconnected"; //$NON-NLS-1$
/**
* Property value used to check if the database is connected.
*/
public static final String PROP_VALUE_DB_CONNECTED =
"com.motorolamobility.studio.android.db.core.databaseConnected"; //$NON-NLS-1$
/**
* Property name used to check database connection status (connected/disconnected).
*/
public static final String PROP_NAME_DB_CONNECTION =
"com.motorolamobility.studio.android.db.core.databaseConnection"; //$NON-NLS-1$
/**
* Property name used to check database connection status (connected/disconnected).
*/
public static final String PROP_NAME_DB_NODE_TYPE =
"com.motorolamobility.studio.android.db.core.IDbNodeType"; //$NON-NLS-1$
/**
* Property value used to check if the database is connected.
*/
public static final String PROP_VALUE_DB_NODE_IS_EXT_STORAGE =
"com.motorolamobility.studio.android.db.core.isExternalStorage"; //$NON-NLS-1$
private class ResultManagerAdapter extends AbstractDbResultManagerAdapter
{
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.AbstractDbResultManagerAdapter#statementExecuted(java.lang.String, java.lang.String)
*/
@Override
public void statementExecuted(String profileName, String sqlStatement)
{
if (model.getProfileName().equals(profileName))
{
//Ignore group execution and read access to table(Select). We'll handle only db changes.
if ((!sqlStatement.equals("Group Execution")) //$NON-NLS-1$
&& (sqlStatement.indexOf("select") != 0) && (!sqlStatement.equals(""))) //$NON-NLS-1$ //$NON-NLS-2$
{
if (sqlStatement.startsWith("drop table") //$NON-NLS-1$
|| sqlStatement.startsWith("create table")) //$NON-NLS-1$
{
//A new table has been created let's refresh the dbNode in order to get a update copy
refreshAsync();
}
else if (sqlStatement.startsWith("alter table")) //$NON-NLS-1$
{
if (!sqlStatement.contains("rename")) //$NON-NLS-1$
{
//Table has been altered but not renamed, let's refresh the table node, loading the possible changes
String tableName = sqlStatement.replace("alter table ", ""); //$NON-NLS-1$ //$NON-NLS-2$
tableName.substring(0, tableName.indexOf(" ")); //$NON-NLS-1$
List<ITreeNode> children = getChildren();
for (ITreeNode child : children)
{
if (child.getName().equalsIgnoreCase(tableName))
{
child.refreshAsync();
break;
}
}
}
else
{
//Since a name has been renamed a refresh on this db node will force to use the latest information.
refreshAsync();
}
}
}
}
}
};
public static final String ICON_PATH = "icons/obj16/dbplate.gif"; //$NON-NLS-1$
protected DbModel model;
private IResultManagerListener resultManagerListener;
protected boolean forceCloseEditors;
@SuppressWarnings("unused")
private DbNode()
{
//Forcing user to use a proper constructor (with a parent)
}
protected DbNode(ITreeNode parent)
{
super(parent);
}
/**
* Creates a new DBNode by using a existent db file.
* @param dbFilePath The SQLite database File
* @param parent The parent of the new node.
* @throws MotodevDbException
*/
public DbNode(IPath dbFilePath, ITreeNode parent) throws MotodevDbException
{
this(parent);
init(dbFilePath);
model = new DbModel(dbFilePath);
}
/**
* Creates a new DBNode by creating a new SQLite3 database file if requested.
* @param dbPath The SQLite database File
* @param parent The parent of the new node.
* @param create set this flag to true if you want to create a new db file, if the flag is false the behavior is the same as the constructor DbNode(Path dbFilePath, AbstractTreeNode parent)
* @throws MotodevDbException
*/
public DbNode(IPath dbPath, ITreeNode parent, boolean create) throws MotodevDbException
{
this(parent);
init(dbPath);
try
{
model = new DbModel(dbPath, create);
}
catch (MotodevDbException e)
{
throw new MotodevDbException("Could not create DBNode", e); //$NON-NLS-1$
}
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#connect()
*/
public IStatus connect()
{
IStatus status = model.connect();
if (status.isOK())
{
if (resultManagerListener == null)
{
resultManagerListener = new ResultManagerAdapter();
ResultsViewAPI.getInstance().getResultManager()
.addResultManagerListener(resultManagerListener);
}
}
setNodeStatus(status);
return status;
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#disconnect()
*/
public IStatus disconnect()
{
IStatus status = closeAssociatedEditors();
if (status.isOK())
{
status = model.disconnect();
if (status.isOK())
{
if (resultManagerListener != null)
{
ResultsViewAPI.getInstance().getResultManager()
.removeResultManagerListener(resultManagerListener);
resultManagerListener = null;
}
clear();
}
if (status.getSeverity() != IStatus.CANCEL)
{
setNodeStatus(status);
}
}
if (status.getSeverity() != IStatus.CANCEL)
{
setNodeStatus(status);
}
return status;
}
public IStatus createTables(List<TableModel> tables)
{
IStatus status = Status.OK_STATUS;
List<ITreeNode> tableNodes = new ArrayList<ITreeNode>(tables.size());
for (TableModel table : tables)
{
status = model.createTable(table);
if (status.isOK())
{
TableNode tableNode = new TableNode(getTable(table.getName()), model, this);
tableNodes.add(tableNode);
}
else
{
break;
}
}
if (status.isOK())
{
putChildren(tableNodes);
}
return status;
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#createTable(java.lang.String, java.lang.String)
*/
public IStatus createTable(TableModel table)
{
IStatus status = model.createTable(table);
if (status.isOK())
{
TableNode tableNode = new TableNode(getTable(table.getName()), model, this);
putChild(tableNode);
}
return status;
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#deleteTable(java.lang.String)
*/
public IStatus deleteTable(ITableNode tableNode)
{
IStatus status = model.deleteTable(tableNode.getName());
if (status.isOK())
{
removeChild(tableNode);
}
return status;
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#getTables()
*/
public List<Table> getTables()
{
return model.getTables();
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#refresh()
*/
@Override
public void refresh()
{
if (!model.isConnected())
{
connect();
}
clear();
List<Table> tables = getTables();
List<ITreeNode> tableNodes = new ArrayList<ITreeNode>(tables.size());
for (Table table : tables)
{
TableNode tableNode = new TableNode(table, model, this);
tableNodes.add(tableNode);
}
putChildren(tableNodes);
}
protected IStatus closeAssociatedEditors(final boolean quiet, final boolean forceClose)
{
Set<IEditorPart> associatedEditors = getAssociatedEditors();
IStatus status = Status.OK_STATUS;
if (!associatedEditors.isEmpty())
{
final boolean[] success = new boolean[]
{
true
};
for (final IEditorPart editor : associatedEditors)
{
final IWorkbenchPage page = EclipseUtils.getPageForEditor(editor);
Display.getDefault().syncExec(new Runnable()
{
public void run()
{
if (!quiet)
{
page.bringToTop(editor);
if (!forceClose)
{
//Use the default Eclipse behavior
if (!page.closeEditor(editor, true))
{
success[0] = false;
}
}
else
{
if (editor.isDirty())
{
//Use our dialog, because the operation can't be cancelled.
boolean shallSave =
EclipseUtils.showQuestionDialog(
DbCoreNLS.DbNode_Close_Editor_Msg_Title,
NLS.bind(DbCoreNLS.DbNode_Close_Editor_Msg,
getName()));
if (shallSave)
{
editor.doSave(new NullProgressMonitor());
}
}
page.closeEditor(editor, false);
}
}
else
{
page.closeEditor(editor, false);
}
}
});
if (!success[0])
{
break;
}
}
if (!success[0])
{
status =
new Status(IStatus.CANCEL, DbCoreActivator.PLUGIN_ID,
DbCoreNLS.DbNode_Canceled_Save_Operation);
}
}
return status;
}
/**
* @param status
* @return
*/
protected IStatus closeAssociatedEditors()
{
return closeAssociatedEditors(false, forceCloseEditors);
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#getTable(java.lang.String)
*/
public Table getTable(String tableName)
{
return model.getTable(tableName);
}
private void init(IPath dbFilePath)
{
String dbFileName = dbFilePath.lastSegment();
String id = dbFilePath.toFile().getParent() + "." + dbFileName; //$NON-NLS-1$
setId(id);
setName(dbFileName);
ImageDescriptor icon =
DbCoreActivator.imageDescriptorFromPlugin(DbCoreActivator.PLUGIN_ID, ICON_PATH);
setIcon(icon);
setToolTip(dbFilePath);
}
/*
* Sets the tool tip for the node given its path.
*/
private void setToolTip(IPath dbPath)
{
//for mapped nodes (i.e., children of IDbMapperNode) the tool tip is its path
if (getParent() instanceof IDbMapperNode)
{
setTooltip(NLS.bind(DbCoreNLS.DbNode_Tooltip_Prefix, dbPath.toString()));
}
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#isLeaf()
*/
@Override
public boolean isLeaf()
{
return false;
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.IDbNode#deleteDb()
*/
public IStatus deleteDb()
{
closeAssociatedEditors(true, forceCloseEditors);
disconnect();
return model.deleteDb();
}
/* (non-Javadoc)
* @see org.eclipse.ui.IActionFilter#testAttribute(java.lang.Object, java.lang.String, java.lang.String)
*/
@Override
public boolean testAttribute(Object target, String name, String value)
{
boolean result = false;
//check if 'name' is a specific DbNode property
if (name.equals(PROP_NAME_DB_CONNECTION)
|| PROP_NAME_DB_CONNECTION.equals(PROP_NAMESPACE + '.' + name))
{
if (value.equals(PROP_VALUE_DB_CONNECTED))
{
result = isConnected();
}
else if (value.equals(PROP_VALUE_DB_DISCONNECTED))
{
result = !isConnected();
}
}
else if (name.equals(PROP_NAME_DB_NODE_TYPE))
{
if (value.equals(PROP_VALUE_DB_NODE_IS_EXT_STORAGE))
{
result = (getParent() instanceof IDbMapperNode);
}
}
else
{
//check if 'name' is a generic ITreeNode property
result = super.testAttribute(target, name, value);
}
return result;
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#getIcon()
*/
@Override
public ImageDescriptor getIcon()
{
return getSpecificIcon("org.eclipse.datatools.connectivity.sqm.core.ui", //$NON-NLS-1$
"icons/database.gif"); //$NON-NLS-1$
}
public boolean isConnected()
{
return model != null ? model.isConnected() : false;
}
/* (non-Javadoc)
* @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#clean()
*/
@Override
public void cleanUp()
{
setForceCloseEditors(true);
disconnect();
if (model != null)
{
try
{
model.cleanModel();
}
catch (ConnectionProfileException e)
{
StudioLogger.debug(this, "Unable to cleanup db model.");
}
}
super.cleanUp();
}
/**
* Retrieves the open editors that is used to edit the given profile, if any
*
* @param profile
* The profile that owns the requested editor
* @return The open dirty editor for the given profile, or <code>null</code>
* if there is no editor in this condition
*/
public Set<IEditorPart> getAssociatedEditors()
{
return model != null ? model.getAssociatedEditors() : new HashSet<IEditorPart>(0);
}
/**
* Checks if the db file exists in filesystem
* @return
*/
public boolean existsDbFile()
{
return model != null ? model.getDbPath().toFile().exists() : false;
}
public IPath getPath()
{
return model != null ? model.getDbPath() : null;
}
/**
* @param forceCloseEditors the forceCloseEditors to set
*/
protected void setForceCloseEditors(boolean forceCloseEditors)
{
this.forceCloseEditors = forceCloseEditors;
}
}