首页 > 代码库 > Eclipse 和 HSQLDB: 将关系数据库服务器嵌入到 Eclipse 中,第 2 部分

Eclipse 和 HSQLDB: 将关系数据库服务器嵌入到 Eclipse 中,第 2 部分

HSQLDB 开发者角色

对 HSQLDB 与 Eclipse 工作台的集成感兴趣的开发者可以很容易地被分为两类:

  1. 客户机开发者,他们只是用 HSQLDB 来存储数据。
  2. 引擎开发者,他们通过添加新的标量函数或者存储过程来扩展 HSQLDB 可以识别的 SQL 语言。

第一类开发者需要方便地访问 HSQLDB JDBC 驱动程序以提交 SQL 语句。不过,最好也可以方便地启动或停止不同的 HSQLDB 实例(比如,两个需要使用不同表的项目)。更高级的开发者可能还喜欢生成 JavaBeans(TM),以将属性映射到表中的列,如果能生成数据访问对象(Data Access Object),从而为查询和更新数据库中的数据提供方法,那也是很方便的。

第二类开发者需要的功能包括,将新的 Java 类添加到数据库引擎中。他们可能还希望 Eclipse 调试器可以在被数据库服务调用的定制方法中设置断点和检查变量。

例如,如果我希望添加一个标量函数,以返回一个字符串列的 CRC32 检验和,我不得不创建一个包含有 public static 方法的类,如清单 1 所示,然后,当清单 2 中的脚本执行以后,它就可以被 SQL 语句所使用了。

清单 1. 扩展 HSQLDB 引擎的 Java 类
package hsqldb.functions;
import java.util.zip.CRC32;
public class CrcUtil {
    public static long crc(String data) {
        CRC32 crc32 = new CRC32();
        crc32.update(data.getBytes());
        return crc32.getValue();
    }
}
清单 2. 添加新标量函数到 HSQLDB 引擎的 SQL 脚本文件
CREATE ALIAS CRC FOR "hsqldb.functions.CrcUtil.crc";
GRANT ALL ON CLASS "hsqldb.functions.CrcUtil" TO PUBLIC;

客户机开发者所期望的大部分特性不在本系列文章的范围之内。有很多 SQL 监控器、OO- 关系映射工具和其他数据库实用工具都可以作为 Eclipse 插件得到,它们独立于数据库,可以与我们的 HSQLDB 插件共同为此类开发者提供一个完整的环境。本系列将着眼于数据库引擎开发者所关心的特性。

 

HSQLDB 插件,第二版

本系列的第 2 部分最初计划要创建一个专门的 HSQLDB 视图,在这里可以编辑很多配置(服务器端口、用户、密码和数据库文件路径),并且它们每一个都可以独立地启动或停止。但我意识到我只需要让 HSQLDB 作为一个标准的 Java 应用程序来运行,Eclipse JDT 本身就已经具备大部分的这些特性,于是我从根本上改变了这个计划。

本文现在的计划是定义一类专用的 Java 项目,在这些项目里 HSQLDB 库将自动可用,它们的行为依赖于服务器和相关工具(数据库管理器和脚本工具)的启动。当然,应该基于每个项目存储 HSQLDB 连接参数,而不是在整个工作台中只保存一次。

 

项目的性质和行为

Eclipse 用 项目性质来定义一类新项目,项目性质是平台中可用的众多扩展点之一。每一个项目可以有多个性质,所以不需要创建全新的项目类,只需复制或者继承 Java 项目类的功能。您可以只告诉工作台,新性质依赖于 JDT 已定义的 Java 项目性质。Java 性质提供了编辑 Java 类、创建单元测试用例和调试 Java 应用程序的所有工具。HSQLDB 引擎性质提供了启动、停止和配置 HSQLDB 的方法。

清单 3 显示了 plugin.xml 文件定义新性质的代码片断。它与一个类相关联,这个类必须实现 IProjectNature ,并可以将生成器和其他元素添加到工作台,但是这个第二版的 HSQLDB 插件不需要去做任何事情。总之,必须给出性质实现类,不然工作台不会将性质添加到项目中。使用新性质很简单,我们只需要将特定的项目设定为 HSQLDB 行为可用(或者不可用)。

清单 3. plugin.xml 清单文件中定义 HSQLDB 引擎项目性质的片断
   <extension
         id="hsqldbEngine"
         name="HSQLDB Engine"
         point="org.eclipse.core.resources.natures">
      <requires-nature
            id="org.eclipse.jdt.core.javanature">
      </requires-nature>
      <runtime>
         <run
               class="hsqldb.nature.DBEngineNature">
         </run>
      </runtime>
   </extension>

每一个 Java 项目将获得一个 HSQLDB 菜单。它被初始化时只包含有一个选项,即“Add HSQLDB Engine nature”,见图 1。如果您曾经尝试过使用 Eclipse Consortium 的 EMF 插件,您将发现两者在概念上是相同的。

图 1. Java 项目上下文菜单中的 HSQLDB 子菜单
技术分享

当用户选择了这一动作,菜单将发生变化并显示出更多选项,包括启动和停止 HSQLDB,以及以独立模式(进行中)或者以客户机/服务器模式启动数据库管理器,见图 2。 我们在第一版插件中定义的,在工作台顶部菜单栏上的“HSQLDB”菜单将消失,由每个 Java 项目上下文菜单中的“HSQLDB”子菜单所取代。

图 2. 添加引擎性质后的 HSQLDB 子菜单
技术分享

操作的可见与启用

在第一版 HSQLDB 插件中,每一步操作都要去检查它是否可以执行(例如,不允许启动数据库服务器的两个实例)。我已经注意到了 Eclipse 没有提供在程序中设置菜单项状态的简单方法,因为工作台试图去自己管理所有事情,只有当真正要调用插件代码时才加载插件。

既然每一步操作都依赖于工作台资源(一个 Java 项目),那么工作台 可以 确定每一个选项什么时候应该可见或者被启用,而不必调用插件。元素 visibility 和 enablement 可以有子元素,比如 objectState ,该子元素查询资源属性以设置每一个操作的状态。清单 4 显示了plugin.xml 中定义 5 个 objectContribution 的片断:

  1. “Run HSQLDB SQL Script”操作,具备此新性质的项目中的任何 *.sql文件都可见。
  2. “HSQLDB”子菜单包含两组选项,对任何 Java 项目都可见。如果两组都有关联到它们的操作,会画出一条分隔线将它们分开。
  3. 只有当 Java 项目具有我们新定义的性质时,所有在本系列第 1 部分最初定义的 HSQLDB 操作才是可见的,例如启动和停止服务器。
  4. 当 Java 项目没有插件自己定义的性质时“Add HSQLDB Engine nature”是可见的。
  5. 最后,当 Java 项目具有我们的新性质时,“Remove HSQLDB Engine nature”可见。
清单 4. plugin.xml 清单文件中定义 HSQLDB 菜单操作的片断
   <extension
         point="org.eclipse.ui.popupMenus">
      <objectContribution
            objectClass="org.eclipse.core.resources.IFile"
            nameFilter="*.sql"
            id="hsqldb.ui.SQLScriptFiles">
         <visibility>
            <objectState
                  name="projectNature"
                  value="http://www.mamicode.com/hsqldb.ui.hsqldbEngine">
            </objectState>
         </visibility>
         <action
               label="Run HSQLDB Script"
               class="hsqldb.ui.popup.actions.HsqldbRunScript"
               menubarPath="additions"
               enablesFor="1"
               id="hsqldb.ui.HsqldbRunScript">
            <enablement>
               <objectState
                     name="projectSessionProperty"
                     value="http://www.mamicode.com/hsqldb.ui.running">
               </objectState>
            </enablement>
         </action>
      </objectContribution>
      <objectContribution
            objectClass="org.eclipse.jdt.core.IJavaProject"
            id="hsqldb.ui.HsqldbEngineMenu">
         <menu
               label="HSQLDB"
               path="additions"
               id="hsqldb.ui.HsqldbProject">
            <separator
                  name="hsqldb.ui.group1">
            </separator>
            <separator
                  name="hsqldb.ui.group2">
            </separator>
         </menu>
      </objectContribution>
      <objectContribution
            objectClass="org.eclipse.jdt.core.IJavaProject"
            id="hsqldb.ui.HsqldbEngineActions">
         <visibility>
            <objectState
                  name="nature"
                  value="http://www.mamicode.com/hsqldb.ui.hsqldbEngine">
            </objectState>
         </visibility>
         <action
               label="Run Database Manager (Standalone)"
               icon="icons/dbmansa.gif"
               class="hsqldb.ui.popup.actions.RunDatabaseManagerStandalone"
               menubarPath="hsqldb.ui.HsqldbProject/group1"
               enablesFor="1"
               id="hsqldb.ui.RunDatabaseManagerStandalone">
            <enablement>
               <not>
                  <objectState
                        name="sessionProperty"
                        value="http://www.mamicode.com/hsqldb.ui.running">
                  </objectState>
               </not>
            </enablement>
         </action>
         <action
               label="Run Database Manager (Client)"
               icon="icons/dbman.gif"
               class="hsqldb.ui.popup.actions.RunDatabaseManager"
               menubarPath="hsqldb.ui.HsqldbProject/group1"
               enablesFor="1"
               id="hsqldb.ui.RunDatabaseManager">
            <enablement>
               <objectState
                     name="sessionProperty"
                     value="http://www.mamicode.com/hsqldb.ui.running">
               </objectState>
            </enablement>
         </action>
         <action
               label="Stop HSQLDB Server"
               icon="icons/stop.gif"
               class="hsqldb.ui.popup.actions.StopHsqldbServer"
               menubarPath="hsqldb.ui.HsqldbProject/group1"
               enablesFor="1"
               id="hsqldb.ui.StopHsqldbServer">
            <enablement>
               <objectState
                     name="sessionProperty"
                     value="http://www.mamicode.com/hsqldb.ui.running">
               </objectState>
            </enablement>
         </action>
         <action
               label="Start HSQLDB Server"
               icon="icons/start.gif"
               class="hsqldb.ui.popup.actions.StartHsqldbServer"
               menubarPath="hsqldb.ui.HsqldbProject/group1"
               enablesFor="1"
               id="hsqldb.ui.StartHsqldbServer">
            <enablement>
               <not>
                  <objectState
                        name="sessionProperty"
                        value="http://www.mamicode.com/hsqldb.ui.running">
                  </objectState>
               </not>
            </enablement>
         </action>
      </objectContribution>
      <objectContribution
            objectClass="org.eclipse.jdt.core.IJavaProject"
            id="hsqldb.ui.JavaProjects">
         <visibility>
            <not>
               <objectState
                     name="nature"
                     value="http://www.mamicode.com/hsqldb.ui.hsqldbEngine">
               </objectState>
            </not>
         </visibility>
         <action
               label="Add Database Engine nature"
               class="hsqldb.ui.popup.actions.AddDBEngineNature"
               menubarPath="hsqldb.ui.HsqldbProject/group2"
               enablesFor="1"
               id="hsqldb.ui.AddDBEngineNature">
         </action>
      </objectContribution>
      <objectContribution
            objectClass="org.eclipse.jdt.core.IJavaProject"
            id="hsqldb.ui.HsqldbEngineProjects">
         <visibility>
            <objectState
                  name="nature"
                  value="http://www.mamicode.com/hsqldb.ui.hsqldbEngine">
            </objectState>
         </visibility>
         <action
               label="Remove Database Engine nature"
               class="hsqldb.ui.popup.actions.RemoveDBEngineNature"
               menubarPath="hsqldb.ui.HsqldbProject/group2"
               enablesFor="1"
               id="hsqldb.ui.AddDBEngineNature">
         </action>
      </objectContribution>
   </extension>

在顶层上下文菜单中包含的第一个 objectContribution 用于 IFile 资源,第二个定义了用于 Java 项目的“HSQLDB”子菜单。其余的那些将显示在子菜单本身上面:第三个在 group1 上,最后两个在 group2 上,但是对于一个给定的项目这两个只有一个是可见的。图 3 和图 4 显示了 PDE 清单编辑器上的这些 objectContributionorg.eclipse.ui.popupMenus 扩展点。

图 3. PDE 清单编辑器上的插件(#1/2)定义的操作
技术分享

图 4.PDE 清单编辑器上的插件(#2/2)定义的操作
技术分享

除了只为正确类型的项目显示操作( objectContribution )之外,每个操作只有在有意义的时候才会被启用:我们只能在所选择的项目已经启动了一个 HSQLDB 服务器实例之后,才能停止它,而且我们不能再启动另外一个实例,因为它会去监听同一个 TCP 端口,而这个端口已经在使用了。同样道理,如果服务器已经在运行,那么数据库管理器只能以客户机/服务器模式启动。但是,如果没有服务器在运行,我们可以以独立模式运行数据库服务器,这种模式下 HSQLDB 引擎正在运行。

这是通过将 会话属性关联到每一个项目资源来完成的。会话属性会在工作台关闭时丢失,这样它们可以帮助插件维持特定资源的不断变化的状态。它们是以程序方式定义的,而不是由插件清单定义的。

还需要有关联到每一个 Java 项目的自定义属性页,以使每一个项目拥有到它们的 HSQLDB 服务器的不同连接参数,例如,不同的用户和密码。清单 5 显示了属性页扩展的片断。对 plugin.xml的讨论到此已经足够,下面将来看一些 Java 代码!

清单 5. plugin.xml 清单文件中针对 HSQLDB 项目属性页的片断
   <extension
         id="hsqldb.ui.property"
         point="org.eclipse.ui.propertyPages">
      <page
            objectClass="org.eclipse.jdt.core.IJavaProject"
            name="HSQLDB Server"
            class="hsqldb.ui.properties.DBEnginePropertyPage"
            id="hsqldb.ui.properties.DBEnginePropertyPage">
         <filter
               name="nature"
               value="http://www.mamicode.com/hsqldb.ui.hsqldbEngine">
         </filter>
      </page>
   </extension>
 

HSQLDB 引擎项目性质

将 hsqldb.ui.hsqldbEngine 性质添加到 Java 项目是很直观的,如清单 6 所示:获得相应的 IProjectDescriptor ,然后向与项目相关联的包含所有性质 id 的字符串数组中添加一个新元素。

这可能反而让读者直觉上认为 IJavaProject并不是IProject 。工作台和 JDT 维持的是平行的资源层次,前者描述项目、文件夹和文件,而后者描述的是 Java 项目、包和类。尽管“Add HSQLDB Engine nature”操作依赖于 Java项目,我们还是必须首先得到相应的 Workbench 项目。然后我们才可以为它赋以新的描述,以及 hsqdbEngine 性质。

(Workbench)项目描述更新完成后,我们定制(Java)项目的 CLASSPATH,并控制 HSQLDB 在其内部创建一个名为“database”的默认数据库。然后刷新项目,以使用户可以看到那些变化(新文件和库)。

如本系列文章的第 1 部分所讲,大部分操作,包括 HSQLDB 数据库类本身,都是来自 HsqldbUtil 类的静态方法,并且在第 2 部分它将拥有一些新的静态方法。

清单 6. 将 DB 引擎性质添加到一个 Java 项目
IProject proj = currentProject.getProject();
IProjectDescription description = proj.getDescription();
// add the hsqldbEngine nature to the project
String[] natures = description.getNatureIds();
String[] newNatures = new String[natures.length + 1];
System.arraycopy(natures, 0, newNatures, 0, natures.length);
// must prefix with plugin id!!!
newNatures[natures.length] = "hsqldb.ui.hsqldbEngine";
description.setNatureIds(newNatures);
proj.setDescription(description, null);
// add the HSQLDB classpath variable
IClasspathEntry[] rawClasspath = currentProject.getRawClasspath();
IClasspathEntry[] newRawClasspath = new IClasspathEntry[
    rawClasspath.length + 1];
System.arraycopy(rawClasspath, 0, newRawClasspath, 0,
    rawClasspath.length);
newRawClasspath[rawClasspath.length] = JavaCore.newVariableEntry(
    new Path("HSQLDB"), null, null);
currentProject.setRawClasspath(newRawClasspath, null);
// create the initial database files
IPath dbPath = proj.getLocation();
String database = dbPath.toString() + "/database";
HsqldbUtil.createEmptyHsqlDatabase(database);
// refresh project so user sees new files, libraries, etc
proj.refreshLocal(IResource.DEPTH_INFINITE, null);

Classpath 变量

除了具有特别的操作之外,DB 引擎项目还需要能访问 HSQLDB 服务器类。从本系列的第 1 部分我们已经知道如何在 hsqldb.core 插件中找到包含有这些类的 hsqldb.jar。但是通过文件系统实现每一个项目对这个库的引用,并不适用于团队开发。设想如果每一个团队成员将 Eclipse 安装到不同的文件系统文件夹,他们每个人将通过不同的路径使用那个库。但是我们并不希望 CVS 用最后一个做出提交的开发者的路径去更新每个开发者的路径。

这就是为什么在第二版 HSQLDB 插件中定义了一个新的 classpath 变量来保存对 hsqldb.jar的引用。当添加 DB 引擎项目性质时,项目的类路径会被更新以包含这个变量,这个变量是由清单 7 中的工作台扩展点定义的。必须有一个类来初始化这个变量;代码见清单 8。

这个新的 classpath 变量对客户机开发者来说也是有用的:他们仅仅需要将这个变量添加到任何一个客户机项目的 Java 编译路径中,或者添加到任何一个客户机应用程序的运行期类路径中。这相对于在文件系统中寻找库 jar 文件简单得多。

清单 7. 声明 HSQLDB classpath 变量的 plugin.xml 片断
   <extension
         point="org.eclipse.jdt.core.classpathVariableInitializer">
      <classpathVariableInitializer
            variable="HSQLDB"
            class="hsqldb.ui.classpath.HsqldbVariable">
      </classpathVariableInitializer>
   </extension>
清单 8. HSQLDB classpath 变量的初始化类
public class HsqldbVariable extends ClasspathVariableInitializer {
    public HsqldbVariable() {
        super();
    }
    public void initialize(String variable) {
        // ignore the "variable" argument, since we define just one
        // classpath variable
        try {
            JavaCore.setClasspathVariable("HSQLDB", new Path(
                HsqldbUtil.getHsqldbJarPath()), null);
        }
        // can‘t create the classpath variable
        catch (JavaModelException e) {
            System.err.println(e);
        }
    }
}
 

移除 HSQLDB 引擎性质

如果我们提供了将 HSQLDB 引擎项目性质添加到 Java 项目的方法,自然,我们也会提供一个方法来移除它。步骤基本上与添加性质相同,但是我们是从字符串数组中删除而不是添加一个性质 id。清单 9 显示了完整的操作类代码,不只是改变项目描述的片断,这样我们可以学习为第二版插件创建的一些实用方法。

清单 9. 移除 HSQLDB 引擎项目性质的操作
public class RemoveDBEngineNature implements IObjectActionDelegate {
    private IJavaProject currentProject;
    
    public RemoveDBEngineNature() {
        super();
    }
    public void setActivePart(IAction action, IWorkbenchPart targetPart) {
    }
    public void run(IAction action) {
        try {
            IProject proj = currentProject.getProject();
            IProjectDescription description = proj.getDescription();
            // remove the hsqldbEngine nature to the project
            String[] natures = description.getNatureIds();
            String[] newNatures = new String[natures.length - 1];
            for(int i = 0; i < natures.length; i++) {
                if (!natures[i].equals("hsqldb.ui.hsqldbEngine"))
                    newNatures[i] = natures[i];
            }
            description.setNatureIds(newNatures);
            proj.setDescription(description, null);
            // refresh project so user sees changes
            proj.refreshLocal(IResource.DEPTH_INFINITE, null);
            // as both decorators share the same visibility rules, this will work
            HsqldbRunningDecorator.updateDecorators();
        }
        catch (Exception e) {
            Shell shell = new Shell();
            MessageDialog.openInformation(
                shell,
                "Hsqldb Ui Plug-in",
                "Cannot remove HSQLDB Database Engine nature:\n" +
                 ActionUtil.getStatusMessages(e));
        }
        Shell shell = new Shell();
        MessageDialog.openInformation(
            shell,
            "Hsqldb Ui Plug-in",
            "Removed HSQLDB Database Engine from this project.\n" +
            "You must manually delete database files and libraries if wanted.");
    }
    public void selectionChanged(IAction action, ISelection selection) {
        currentProject = ActionUtil.findSelectedJavaProject(selection);
    }
}

ActionUtil 类(清单 10)包含了我们的插件操作类要调用的一般任务所需要的方法。它们是:对每个操作接收到的 selectionChanged 方法的参数进行结构化查询( ISelection ),并从中找到 IJavaProject 或者 IFile 资源;显示来自众多 IStatus 实例的详细错误消息,这些消息存储在工作台类抛出的 CoreException 中。不要忘记,如在第 1 部分中看到的,每一步操作必须要记住上一步选择的内容,因为它的 run 方法对此一无所知。

不用考虑在 run 方法的最后一步操作中调用 HsqldbRunningDecorator 类给出的是什么。后面将对其进行简要描述,它可以改变 Package Explorer 视图中项目的图标,这样用户就可以知道哪些项目是 HSQLDB 引擎项目,以及哪些正在运行服务器实例。

清单 10. ActionUtil 类
public class ActionUtil {
   public static IJavaProject findSelectedJavaProject(ISelection selection) {
        IJavaProject currentProject = null;
        if (selection != null) {
            if (selection instanceof IStructuredSelection) {
                IStructuredSelection ss = (IStructuredSelection)selection;
                Object obj = ss.getFirstElement();
                if (obj instanceof IJavaProject) {
                    currentProject = (IJavaProject)obj;
                }
            }
        }
        return currentProject;
    }
    
    public static IFile findSelectedFile(ISelection selection) {
        IFile currentFile = null;
        if (selection != null) {
            if (selection instanceof IStructuredSelection) {
                IStructuredSelection ss = (IStructuredSelection)selection;
                // as this action is enabled only for a single selection,
                // it‘s enough to get the first element
                Object obj = ss.getFirstElement();
                if (obj instanceof IFile) {
                    currentFile = (IFile)obj;
                }
            }
        }
        return currentFile;
    }
    public static String getStatusMessages(Exception e) {
        String msg = e.getMessage();
        if (e instanceof CoreException) {
            CoreException ce = (CoreException)e;    
            IStatus status = ce.getStatus();
            IStatus[] children = status.getChildren();
            for (int i = 0; i < children.length; i++)
                msg += "\n" + children[i].getMessage();
            System.err.println(msg);
            ce.printStackTrace(System.err);
        }
        return msg;
    }
}
 

跟踪正在运行的服务器

既然我们可以让 HSQLDB 项目存在于工作台中,它们定制的操作就需要有实现类。当我们学会了如何启动和停止 HSQLDB 服务器和它的工具时,大部分工作我们在第 1 部分就已经完成。不过,这一次会有很多活动的服务器实例,所以我们创建了一个名为 HsqldbServerTracker 的新类,来完成以下四件事情:

  1. 启动和停止 HSQLDB 服务器实例。
  2. 为每一个运行着的服务器实例保存记录。
  3. 当相应的数据库有运行着的服务器时,记录一个项目会话属性。
  4. 检测服务器实例由于非插件操作引起的终止,比如当用户点击 Console 视图上的停止按钮时,并且更新项目会话属性,以使新的服务器可以启动。

这是一个惟一的类,也就意味着所有项目正在运行的服务器实例只有一个惟一的映射。它需要被初始化,才可以监听调试事件并据此获得进程的终止信息。

再回到启动 HSQLDB

在我们的插件的第一个版本中,服务器作为 Eclipse 工作台内部的一个新的线程启动。但是,在第二版中,我们希望将服务器提交到 JDT 调试器,这样每一个数据库,也就是每一个项目,将有独立的配置。这与在第一版中我们启动 ScriptTool的方法类似,但我们不是为每个独立的配置创建一个全新的 Java Classpath,而是必须使用所选择的项目的类路径,在这个类路径中已经包括了 hsqdb.jar库和实现用户定义 SQL 函数的类。

HsqldbUtil.runScriptTool 的代码被重构,以创建一个名为 launch 的方法(见清单 11),这个方法以项目、主类名和命令行参数作为自己的参数。这个新方法被重写的 runScriptTool 方法和 HsqldbServerTracker 类的 startHsqldbServer 方法所使用。

清单 11. HsqldUtil 类的 launch 方法
protected static ILaunch launch(IJavaProject proj, String name,
                        String mainClass, String args) throws CoreException {
    ILaunchManager manager =
        DebugPlugin.getDefault().getLaunchManager();
    ILaunchConfigurationType type =
        manager.getLaunchConfigurationType(
        IJavaLaunchConfigurationConstants.ID_JAVA_APPLICATION);
    ILaunchConfiguration config = null;
    // if the configuration already exists, use it!
    ILaunchConfiguration[] configurations =
        manager.getLaunchConfigurations(type);
    for (int i = 0; i < configurations.length; i++) {
        if (configurations[i].getName().equals(name))
            config = configurations[i];
    }
    // else create a new one
    if (config == null) {
        ILaunchConfigurationWorkingCopy wc =
            type.newInstance(null, name);
        wc.setAttribute(
            IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME,
            proj.getProject().getName());
        // current directory should be the project root
        wc.setAttribute(
            IJavaLaunchConfigurationConstants.ATTR_WORKING_DIRECTORY,
            proj.getProject().getLocation().toString());
        // use the supplied args
        wc.setAttribute(
            IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME,
            mainClass);
        wc.setAttribute(
            IJavaLaunchConfigurationConstants.ATTR_PROGRAM_ARGUMENTS,
            args);
        // saves the new config
        config = wc.doSave();
    }
    // launches the configuration
    DebugPlugin debug = DebugPlugin.getDefault();
    return config.launch(ILaunchManager.RUN_MODE, null);
}

另一个与第一版插件的不同之处是,每一个操作不会重新创建独立的配置;而是,如果存在带有此项目名的独立配置,那么就使用它。这样开发者就可以自定义 Java VM 的参数和其他的设置,例如,向类路径添加附加的 Java 库。当然,也可以改变主类,或者从类路径中将 HSQLDB 变量删除,使独立配置失效,不过这很容易修改回来,只要删掉它然后使用 HSQLDB 插件操作重新生成一个就可以了。保存独立的配置还可以让用户能在 JDT 调试器内部运行服务器,这样他就可以在他自定义的 SQL 函数上设置断点。

清单 12 显示了 startHsqldbServer 方法。首先,它载入项目持久属性的连接参数,然后,它使用 HsqldbUtil.launch 方法启动服务器实例,并添加一个调试监听器,这样我们可以得到相应的进程终止的消息,然后设置会话属性,以使工作台可以更新插件操作的启用情况。

插件的第二版假定每个项目只有一个数据库,而且数据库的名字是不能更改的,只能是“database”。将此不能修改的名字修改为可配置的名字这一工作将留作读者的练习。

清单 12. HsqldbServerTracker 类中启动 HSQLDB 服务器的方法
public void startHsqldbServer(IJavaProject proj) throws CoreException {
    // build server command-line
    HsqldbParams params = new HsqldbParams(proj);
    String database = proj.getProject().getLocation() + "/database";
    String args = "-database " + database + " " +
        "-port " + params.port;
    // starts the server as a Java app
    ILaunch launch = HsqldbUtil.launch(proj, proj.getElementName(),
        "org.hsqldb.Server", args);
    // saves the mapping between (server) process and project
    servers.put(launch.getProcesses()[0], proj);        
    // register a listener so we know when this process is finished
    DebugPlugin debug = DebugPlugin.getDefault();
    debug.addDebugEventListener(listener);
    // so the UI will reflect there‘s already a server running
    setRunning(proj, Boolean.TRUE);
}

HSQLDB 服务器实例的终止

HsqldbUtil.launch 方法返回一个 ILaunch 实例,从这里我们可以得到通过启动配置创建的 JVM 进程。此进程将作为保存 Java 项目(HSQLDB 引擎)引用的 Map 的主键。

稍后,当服务器追踪器注册的监听器发现一个进程终止时(见清单 13),它会检查那个终止的进程是否在映射中。如果在,相应的会话属性可以从项目中删除,项目条目可以从映射中删除。由于工作台引发的调试事件,所以插件操作的启用情况始终可以反映是否有正在运行的服务器。“running”会话属性也可以由 HsqdbServerTracker 中的 helper 方法处理(见清单 14)。

停止运行着的 HSQLDB 服务器的方法不需要去更新映射或者会话属性,因为这些将由调试事件监听器来完成。仅仅是必须通过 JDBC 连接发送一个 shutdown SQL 语句给服务器。

清单 13. HsqldbServerTracker 的用于终止 HSQLDB 服务器的调试事件监听器
private IDebugEventSetListener listener = new IDebugEventSetListener() {
    public void handleDebugEvents(DebugEvent[] events) {
        // if the event was a terminate...
        if (events[0].getKind() == DebugEvent.TERMINATE) {
            Object source = events[0].getSource();
            if (source instanceof IProcess) {
                // ...and the terminated process is a HSQLDB Server...
                Object proj = servers.get(source);
                if (proj != null) {
                    try {
                        //...remove it from the map and update the ui
                        servers.remove(source);
                        setRunning((IJavaProject)proj, null);
                    }
                    catch (CoreException ce) {
                        System.err.println("HsqldbServerTracker.handleDebugEvents");
                        System.err.println(ActionUtil.getStatusMessages(ce));
                    }
                }
            }
        }
    }
};
清单 14. HsqldbServerTracker 的设置和取得“running”会话属性的 Helper 方法
public boolean getRunning(IJavaProject javaProj) throws CoreException {
    IProject proj = javaProj.getProject();    
    Object value = http://www.mamicode.com/proj.getSessionProperty(new QualifiedName("hsqldb.ui", "running"));
    return value != null;
}
public void setRunning(IJavaProject javaProj, Boolean value)
            throws CoreException {
    if (value != null && value.equals(Boolean.FALSE))
        value = http://www.mamicode.com/null;"hsqldb.ui", "running"),
        value);
    HsqldbRunningDecorator.updateDecorators(javaProj);
}

再回到 HSQLDB 连接属性

在插件的第一版中, HsqldbParam 类只是一个类 C 的结构,用来保存整个工作台的 HSQLDB 连接参数,在第二版中这个类更为智能,它可以载入参数和将参数保存为对应于给定项目工作台资源的持久的会话属性(见清单 15)。

清单 15. 更新后的 HsqldbParams 类
public class HsqldbParams {
    // property names
    public static final String P_PORT = "serverPort";
    public static final String P_USER = "serverUser";
    public static final String P_PASSWD = "serverPasswd";
    
    public int port = 9001;
    public String user = "sa";
    public String passwd = "";
    
    public HsqldbParams() {}
    
    public HsqldbParams(IJavaProject javaProject) throws CoreException {
        load(javaProject);
    }
    
    public void save(IJavaProject javaProject) throws CoreException {
        IProject project = javaProject.getProject();
        project.setPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_PORT), Integer.toString(port));
        project.setPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_USER), user);
        project.setPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_PASSWD), passwd);
    }
    
    public void load(IJavaProject javaProject) throws CoreException {
        IProject project = javaProject.getProject();
        String property = project.getPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_PORT));
        port = (property != null && property.length() > 0) ?
            Integer.parseInt(property) : port;
        property = project.getPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_USER));
        user = (property != null && property.length() > 0) ? property : user;
        property = project.getPersistentProperty(new QualifiedName (
            "hsqldb.ui", P_PASSWD));
        passwd = (property != null && property.length() > 0) ?
            property : passwd;
    }
}

我们需要用一个 HSQLDB 资源属性页来替换 HSQLDB 工作台属性页。图 5 显示了页的外观 (基本上与最初的属性页相同),属性页的代码见清单 16。代码比最初的属性页要稍复杂一些,因为我们不能再使用域编辑器,而且我们要处理“低层次”的 SWT 控制。幸运的是,可以很容易地调整由 PDE 属性页向导生成的属性页,而且读者也不必深入研究 SWT。相应的扩展点已经在清单 5 中列出。

图 5. HSQLDB 引擎项目属性页
技术分享

清单 16. 属性页实现类
public class DBEnginePropertyPage extends PropertyPage {
    private HsqldbParams params;
    
    private Text portText;
    private Text userText;
    private Text passwdText;
    public DBEnginePropertyPage() {
        super();
    }
    private void fillControls() {
        portText.setText(Integer.toString(params.port));
        userText.setText(params.user);
        passwdText.setText(params.passwd);
    }
    
    private void getParams() {
        params = new HsqldbParams();
        try {
            params.port = Integer.parseInt(portText.getText());
        }
        catch (NumberFormatException ne) {
            // do nothing; let the default port number
        }
        params.user = userText.getText();
        params.passwd = passwdText.getText();
    }
    
    private void addControls(Composite parent) {
        Composite composite = createDefaultComposite(parent);
        Label pathLabel = new Label(composite, SWT.NONE);
        pathLabel.setText("&TCP Port:");
        portText = new Text(composite, SWT.SINGLE | SWT.BORDER);
        GridData gd = new GridData();
        gd.widthHint = convertWidthInCharsToPixels(6);
        portText.setLayoutData(gd);
        Label userLabel = new Label(composite, SWT.NONE);
        userLabel.setText("&User:");
        userText = new Text(composite, SWT.SINGLE | SWT.BORDER);
        gd = new GridData();
        gd.widthHint = convertWidthInCharsToPixels(15);
        userText.setLayoutData(gd);
        Label passwdLabel = new Label(composite, SWT.NONE);
        passwdLabel.setText("&Password:");
        passwdText = new Text(composite, SWT.SINGLE | SWT.BORDER);
        gd = new GridData();
        gd.widthHint = convertWidthInCharsToPixels(15);
        passwdText.setLayoutData(gd);
    }
    protected Control createContents(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        composite.setLayout(layout);
        GridData data = http://www.mamicode.com/new GridData(GridData.FILL);>
 

定制

第二版的 HSQLDB 插件大部分可以使用了。为使用重构的 launch 方法,并获得一个 IJavaProject 作为启动所有运行 HSQLDB 工具的方法的参数(这样可以取回特定的连接属性),对 HsqldbUtil 进行了一些小的修改,这样,所有期望的功能都已经到位,开发者可以追踪多个正在运行的服务器实例,这些实例每个都有自己的数据库文件和定制的 SQL 函数。这些变化对读者来说微不足道,他们可以以先前的清单作为出发点,或者可以通过本文结束前的链接下载第二版插件的完整源代码(请参阅 参考资料)。

不过现在还缺少一个重要的特性——用户需要关于每个 HSQLDB 引擎项目的一些可视的反馈信息:首先,指出哪些项目是数据库项目;其次,指出哪些数据库正在运行或者处于停止状态。Eclipse decorator 适于提供这些可视化的线索。图 6 显示了数据库项目图标的外观,图 7 显示项目具有一个正在运行的服务器。请注意,DB 引擎项目的 decorator 隐藏了标准 Java 项目的“J” decorator。这是期望的结果,这样开发者可以快速识别出哪些项目是数据库项目(不过大部分时候插件 decorator 不应该隐藏标准工作台或 JDT decorator)。

图 6. DB 引擎项目的图标
技术分享

图 7. 正在运行的 DB 引擎项目的图标
技术分享

 

DB 引擎 decorator

第一个 decorator 只需要使用 plugin.xml清单元素就可以被定义为可声明的(见清单 17)。这是 Eclipse 2.1 中被引入的 轻量级 decorator 的优点。不过不要忘记添加性质的代码(见清单 6),如果我们不引发 LabelProviderChangedEvent 事件,工作台视图可能不会为变化的资源显示正确的 decorator。实际上,这个事件由 HsqldbRunningDecorator 类的 updateDecorators 引发,那个类描述了插件定义的“其他”decorator。由于两种 decorator 都应用于同一组工作台资源,所以纯 xml 的 decorator 可以从编码的 decorator 那里借用方法,如我们接下来马上要介绍的。

清单 17. DB 引擎项目的纯 xml decorator
   <extension
         point="org.eclipse.ui.decorators">
      <decorator
            lightweight="true"
            quadrant="TOP_RIGHT"
            location="TOP_RIGHT"
            adaptable="true"
            label="HSQLDB Engine Project"
            icon="icons/dec-dbnature.gif"
            state="true"
            id="hsqldb.ui.hsqldbEngineDecorator">
         <description>
            Flags Java Projects that had the HSQLDB Engine project
         	nature added to them.
         </description>
         <enablement>
            <and>
               <objectClass
                     name="org.eclipse.jdt.core.IJavaProject">
               </objectClass>
               <objectState
                     name="nature"
                     value="http://www.mamicode.com/hsqldb.ui.hsqldbEngine">
               </objectState>
            </and>
         </enablement>
      </decorator>
 

正在运行的服务器 decorator

读者可能会试图去用纯粹的说明性(纯 xml)decorator 来标记具有正在运行的 HSQLDB 服务器的项目上,如清单 18 所示。但是这不会很有效,因为当为一个给定的项目第一次启动服务器时,decorator 不会显示出来。稍后,如果您取消选定然后再选定那个项目,图标将会改变,显示真实的状态。

这是因为事实上项目并不在受 decorator 影响的资源列表上,所以没有 LabelProviderChangedEvent 。补救的方法是将 decorator 关联到所有的 DB 引擎项目上,并让 decorator 图标只有当服务器在运行时才可以编程控制。

清单 18. 为正在运行的数据库定义纯 xml decorator 的不成功的尝试
      <decorator
            lightweight="true"
            quadrant="BOTTOM_RIGHT"
            location="BOTTOM_RIGHT"
            adaptable="true"
            label="HSQLDB Running"
            state="true"
            id="hsqldb.ui.hsqldbRunningDecorator">
         <description>
            Flags HSQLDB Engine Projects which have a running
            server instance.
         </description>
         <enablement>
             <and>
                <objectClass
                     name="org.eclipse.jdt.core.IJavaProject">
                </objectClass>
                <and>
                   <objectState
                         name="nature"
                         value="http://www.mamicode.com/hsqldb.ui.hsqldbEngine">
                   </objectState>
                   <objectState
                        name="sessionProperty"
                        value="http://www.mamicode.com/hsqldb.ui.running">
                   </objectState>
                </and>
             </and>
         </enablement>
      </decorator>
   </extension>

清单 19 显示了对第二个 decorator 的正确声明,清单 20 是实现类。注意,图标必须和类位于同一文件夹,不能如第一个 decorator 那样位于icons 文件夹。将 decorator 定义为可编程控制还有一个好处,那就是我们可以发送定向的 LabelProviderChangedEvent 事件,只涉及影响到的项目,而不是更新工作台中所有的 DB 引擎项目。这会减少屏幕的闪烁,对于经常要改变的 decorator 会极大地提高性能。

定向的 updateDecorators 方法只能被 HsqldbServerTracker 的 setRunning 方法调用,不过也可以由添加 DB 引擎操作调用。它使用“broadcast”的方式只是要演示两个可选项。

清单 19. 为正在运行的数据库定义可编程控制的 decorator 的正确方法
      <decorator
            lightweight="true"
            quadrant="BOTTOM_RIGHT"
            location="BOTTOM_RIGHT"
            adaptable="true"
            label="HSQLDB Running"
            state="true"
            class="hsqldb.ui.decorator.HsqldbRunningDecorator"
            id="hsqldb.ui.hsqldbRunningDecorator">
         <description>
            Flags HSQLDB Engine Projects which have a running server instance.
         </description>
         <enablement>
            <and>
               <objectClass
                     name="org.eclipse.jdt.core.IJavaProject">
               </objectClass>
               <objectState
                     name="nature"
                     value="http://www.mamicode.com/hsqldb.ui.hsqldbEngine">
               </objectState>
            </and>
         </enablement>
      </decorator>
   </extension>
清单 20. 正在运行的数据库 decorator 的实现类
public class HsqldbRunningDecorator
    extends LabelProvider
    implements ILightweightLabelDecorator {
    private static final ImageDescriptor runningDescriptor =
        ImageDescriptor.createFromFile (
            HsqldbRunningDecorator.class, "dec-running.gif");
            
    public void decorate(Object element, IDecoration decoration) {
        IJavaProject javaProj = (IJavaProject)element;
        try {
            if (HsqldbServerTracker.getDefault().getRunning(javaProj)) {
                decoration.addOverlay(runningDescriptor);
            }
        }
        catch (CoreException ce) {
            System.err.println(ActionUtil.getStatusMessages(ce));
        }
    }
    public static void updateDecorators(IJavaProject javaProj) {    
        IDecoratorManager dm =
            PluginUi.getDefault().getWorkbench().getDecoratorManager();
        HsqldbRunningDecorator decorator = (HsqldbRunningDecorator)
            dm.getBaseLabelProvider("hsqldb.ui.hsqldbRunningDecorator");
        decorator.fireUpdateDecorators(javaProj);
    }
    
    private void fireUpdateDecorators(IJavaProject proj) {
        // I can generate my own event to update the decorators
        // for a given resource
        final LabelProviderChangedEvent ev =
            new LabelProviderChangedEvent(this, proj); 
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                fireLabelProviderChanged(ev);
            }
        });
    }
    public static void updateDecorators() {
        // I can also let the workbench generate events to update all
        // resources affected by a decorator 
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                PluginUi.getDefault().getWorkbench().getDecoratorManager()
                    .update("hsqldb.ui.hsqldbRunningDecorator");
            }
        });
    }
        
}
 

结束语

本文介绍了如何创建一组插件的第二版,这些插件将 HSQLDB 数据库引入到 Eclipse 工作台中。本文另外除了运行 SQL 语句和脚本以外,还引入了启动和停止数据库服务器的许多实例的简单方法,数据库服务器可以包含用户定义的 SQL 函数(作为 Java 类实现)。服务器可以作为正常的 Java 应用程序来运行,或者在 JDT 调试器内部运行。

我们已经知道,尽管 PDE 可以帮助创建这个插件,为一些任务提供了向导和专门的编辑器,但是我们还是需要更深入地理解平台和 JDT 的核心接口,此外还要明白 XML 扩展点语法。还有很多地方可以改进,因为大部分的工作需要手工编辑清单文件和手工创建 Java 类。

不过我向读者保证,如果没有 Eclipse 平台和 PDE,任务会更加复杂,而且使用任何其他 IDE 的扩展 API 也困难得多。Eclipse 的设计从开始时就是为了支撑如本系列所创建的插件,并且到目前为止结果令人满意:没有人可以看出 HSQLDB 不是在 Eclipse 内部执行的,除非我们启动数据库管理工具,这将是本系列的第 3 部分的主题,将两个开放源代码软件包 HSQLDB 和 Eclipse 完全集成。

当然,有很多方法可以改进插件的设计,欢迎读者将自己的想法通过本文最后的邮件地址发送给作者。

Eclipse 和 HSQLDB: 将关系数据库服务器嵌入到 Eclipse 中,第 2 部分