IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Linux  >

在 Linux 上构建 LSID 中心

基于 Java 的生命科学标识权威中心整合生物数据资源

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Stefan Atev (satev@us.ibm.com), 程序员, IBM

2003 年 8 月 09 日

我们将向您演示如何从头开始逐步构建一个基于 Java 的生命科学标识(Life Sciences Identifier,LSID)中心。我们将演示如何在 Linux 平台上分别针对一个极小数据集和从蛋白质序列数据库(protein sequence database)Swiss-Prot 下载的数据,构建 LSID 中心。

当今时代正在创建的生物学数据的数量是惊人的。作为一名生物学家或生物信息学家,您可能知道网络上一些能为您手头工作提供非常有用的参考资料的地方 — 但是记住访问这些信息的不同方法常常会降低您的工作效率。或许您可以编写数个 Perl 脚本,或认识某个人,他能给您一些做这做那的代码或过程。在那一刻,您可能会想:“如果想不出一种命名和查找这些数据的公共方法的话,我只好不作生物学家,而改行当程序员了。”当然,找出标识数据的公共方法的好处不仅限于生物信息学,不过本文所研究的是生命科学。

生命科学标识(Life Sciences Identifier,LSID)是一个正在拟订的 I3C 统一资源名(Uniform Resource Name,URN)规范。您可以在 I3C(请参阅 参考资料以获得相关链接)阅读关于这一规范的更多信息。从概念上讲,对于存储在多个分布式数据存储中的数据资源而言,LSID 是一种命名和标识它们的简单方法,能够克服当前所用的各种命名方案的限制。

LSID 解析器是一种软件系统,它实现了一致同意的 LSID 解析协议以允许更高层软件定位和访问由任一 LSID URN 唯一命名的数据。这一解析器解决方案的“服务器”端名为 LSID 中心(LSID authority)。客户机堆栈及示例客户机 LSID LaunchPad 由 LSID 解析协议项目(LSID Resolution Protocol Project)提供。

在本文中,您将看到如何使用用于 Java 的 LSID 解析器堆栈来创建自己的 LSID 中心。

入门

本文假定您对容纳中心的系统有必要的管理特权(在某些步骤中很可能需要 root访问权)。

本文中的所有步骤都在 Red Hat Linux 7.1 和 Red Hat Linux 8 上经过测试。Java JDK V1.3.1 和 V1.4.0 经过了测试。我们使用了 Jakarta Tomcat 4.1.18。样本代码也可用于 IBM WebSphere V5。

先决条件

首先需要的是有权访问这样一个系统:该系统能够运行 Jakarta Tomcat 4 Web 服务器、Java2(推荐使用 JDK 1.3.1 和更高版本)和象 MySQL 3.23.x 这样的数据库引擎。

必需的 Java 程序包

Java LSID 客户机/服务器堆栈要求首先安装几个 Java 包:

将 .jar 文件复制到 Jakarta Tomcat shared/lib 目录,或采用另一种方法:确保 Java 运行时引擎可以通过系统类路径使用这些 .jar 文件。

如果您选择使用 Swiss-Prot 数据集设置样本中心以供自己的测试之用,那么还需要 MySQL Connector/J 分发版(distribution)中的文件 mysql-connector-java-x.x.x-bin.jar,该分发版可从 MySQL AB 获得(请参阅 参考资料以获得链接)。不需要 JDBC 驱动程序的最新版本;使用 LGPL 特许版本 2.0.14 即可。样本中心服务器使用这一模块来访问包含 Swiss-Prot 数据的 MySQL 数据库,也需要将该模块放入 Jakarta Tomcat shared/lib 目录(或使其位于系统类路径中)。





回页首


安装 LSID 软件包

一旦您下载了这些必须预先具备的软件包,就要获取 LSID Java 客户机/服务器堆栈的最新版本(撰写本文时为 0.3)。请获取 二进制 LSID 服务器分发版二进制 LSID 客户机分发版,然后将文件 lsid-client.jar 和 lsid-server.jar 复制到 Jakarta Tomcat shared/lib 目录中。

Java LSID 服务器软件包提供了一组 servlet 和简化的接口用于快速创建 LSID 中心,以及功能完备的 LSID 解析服务。





回页首


Hello World,您的首个 LSID 中心

在进行下一步之前,让我们先实现一个只知道唯一 LSID 为 urn:lsid:ibm.com:hello:world 的中心。这一特定 LSID 的组成部分是:

  • ibm.com— 发布中心的域
  • hello— 该 LSID 的名称空间
  • world— 该 LSID 的对象标识

实现该中心最简单的方法是继承 com.ibm.lsid.server.impl.SimpleAuthority 类,这个类将由标准中心 servlet(由 com.ibm.lsid.server.AuthorityServlet 实现)使用。需要提供/覆盖的方法是:

  • initAuthority
  • getKnownURIs
  • getDataLocations
  • getMetaDataLocations

这一中心将不提供数据或元数据服务,但会简单描述可在哪里检索有关 urn:lsid:ibm.com:hello:world 的数据。

下载 java-samples.tar.gz,然后解压缩 HelloWorldAuthority.java 或 WAR 文件 helloworld.war,即可获得该中心的代码。


>清单 1. Hello, world 代码
01  package lsidsamples;
02  
03  import com.ibm.lsid.LSID;
04  import com.ibm.lsid.MalformedLSIDException;
05  import com.ibm.lsid.ExpiringResponse;
06  import com.ibm.lsid.wsdl.LSIDDataPort;
07  import com.ibm.lsid.wsdl.LSIDMetaDataPort;
08  import com.ibm.lsid.server.LSIDServiceConfig;
09  import com.ibm.lsid.server.LSIDServerException;
10  import com.ibm.lsid.server.impl.SimpleAuthority;
11  import com.ibm.lsid.server.impl.HTTPDataLocation;
12  import com.ibm.lsid.server.impl.FTPDataLocation;
13  
14  public class HelloWorldAuthority extends SimpleAuthority {
15      
16    public void initAuthority(LSIDServiceConfig config) throws LSIDServerException {
17    }
18      
19    public ExpiringResponse getKnownURIs() throws LSIDServerException {
20      try {
21        return new ExpiringResponse(
22          new LSID[] {
23            new LSID("urn:lsid:ibm.com:hello:world")
24          },
25          null
26        );
27      } catch (MalformedLSIDException ex) {
28        throw new LSIDServerException(
39          ex, 500, "This error should have never occurred"
30        );
31      }
32    }
33      
34    public LSIDMetaDataPort[] getMetaDataLocations(LSID lsid, String url) {
35      return new LSIDMetaDataPort[0];
36    }
37  
38    public LSIDDataPort[] getDataLocations(LSID lsid, String url) {
39      return new LSIDDataPort[] {
40        new HTTPDataLocation(
41          "www.ibm.com", 80, "/lsid/hello_world.txt"
42        ),
43        new FTPDataLocation(
44          "ftp.ibm.com", "/lsid/hello_world.txt"
45        )
46      };
47    }
48  }

详细分析“Hello World”

01 行指明我们中心的实现将是 lsidsamples 包的一部分。 03 - 12 行导入实现该中心所需的类和接口。我们将使用类 com.ibm.lsid.server.impl.SimpleAuthority 作为 lsidsamples.HelloWorldAuthority 实现的基础(第 14 行)。

16 - 17 行实现 initAuthority 方法,在中心启动时将调用该方法。因为无需保存任何配置选项(可通过 LSIDServiceConfig 访问),所以我们选择什么也不做。接下来实现 getKnownURIs 。必须返回 LSID 对象数组,因为只支持一个 LSID,所以返回的数组只有一个元素。 MalformedLSIDException 可以在 LSID 的构造函数中出现。 27 - 31 行演示了通常是如何处理 LSID 服务器代码中的异常:如果异常不是 LSIDServerException ,则将其打包并再次抛出。这样做的原因是因为 Java LSID 服务器堆栈知道如何正确地将 LSIDServerException 转换为 SOAP 故障消息。堆栈跟踪信息不会在该过程中丢失。

函数 getMetaDataLocations34 - 36 行)获取作为参数的 LSID 对象,并返回可获得该 LSID 元数据服务的位置的数组。由于我们在这一示例中没有实现任何元数据服务,所以该方法返回一个长度为 0 的数组(返回 null 将表明有错误)。

函数 getDataLocationsgetMetaDataLocations 非常相似,但这次我们返回的数组提供数据的两个可能位置:假想的 URL http://www.ibm.com:80/lsid/hello_world.txt 和 ftp://ftp.ibm.com/lsid/hello_world.txt。

运行并测试中心

要测试中心,必须先部署它。将文件 helloworld.war 复制到 Jakarta Tomcat webapps 目录。该文件将在 Tomcat 启动时被解压缩到 webapps/helloworld,Hello World 中心将在 http://localhost:8080/helloworld/ 可用。

可以在文件 webapps/helloworld/WEB-INF/web.xml 中找到对该中心 servlet 的描述:


清单 2. 查询中心
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems,
Inc.//DTD Web Application 2.3//EN" 
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app id="WebApp">
  <display-name>Hello World LSID Authority</display-name>
  <servlet>
    <servlet-name>AuthorityService</servlet-name>
    <display-name>Hello World Authority Servlet</display-name>
    <servlet-class>com.ibm.lsid.server.AuthorityServlet</servlet-class>
    <init-param>
      <param-name>service1</param-name>
      <param-value>*:* lsidsamples.HelloWorldAuthority</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>AuthorityService</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

我们不必自己编写 Java servlet,因为标准 com.ibm.lsid.server.AuthorityServlet 可以完成我们需要的一切。该中心 servlet 期望使用具有 service1service2 等类似名称的 servlet 参数来配置服务。每个服务负责处理一个特定的中心/名称空间对,星号字符起到通配符的作用。按服务的顺序执行匹配(服务 1 在服务 2 之前,以此类推)。所有的中心服务都必须实现 com.ibm.lsid.server.LSIDAuthorityService 接口。我们的样本中心 lsidsamples.HelloWorldAuthority 通过扩展 com.ibm.lsid.server.impl.SimpleAuthority 来实现该接口。 AuthorityServlet 被装入后,将实例化 HelloWorldAuthority ,然后依次调用 getMetaDataLocationsgetDataLocations ,以获得对标准 LSID 中心方法 getAvailableOperations 构建 WSDL 响应所必需的信息。

要测试该中心,可使用 TestClient.java 样本客户机程序。该程序的已编译类文件位于解压缩的 samples 目录中的 test-client.jar 文件中。请输入以下命令:

java TestClient urn:lsid:ibm.com:hello:world \
   http://localhost:8080/helloworld/

需要位于类路径中 Tomcat shared/lib 目录中的 .jar 文件以及 samples/test-client.jar。 TestClient 的第一个参数是要测试的 LSID,第二个参数临时将 ibm.com 中心服务映射至 http://localhost:8080/helloworld/,Hello World 中心在那里运行。预期的输出是:

Authority version is: 3
The authority knows about 1 LSIDs:
  urn:lsid:ibm.com:hello:world
Data is available at:
(ftp)  ftp://ftp.ibm.com/lsid/hello_world.txt
(http) http://www.ibm.com:80/lsid/hello_world.txt





回页首


使用 Swiss-Prot 数据集

Swiss-Prot 记录包含一个标识和一个 AC 字段,分别对应该记录的人类可读标识及其入藏号(accession number)。我们要实现的样本 Swiss-Prot 中心可以理解以下各形式的 LSID:

表 1. 受支持的 LSID

样本 LSID 描述
urn:lsid:example.org: swiss-id: hv20_mouse 抽象的 LSID,它不包含表示(标识为 HV20_MOUSE的)Swiss-Prot 记录的数据。LSID 与该 Swiss-Prot 记录在各种格式下的具体表示有关。
urn:lsid:example.org: swiss-id: hv20_mouse-sprot 具体的 LSID,用 Swiss-Prot 格式命名标识为 HV20_MOUSE的 Swiss-Prot 记录的数据。
urn:lsid:example.org: swiss-id: hv20_mouse-fasta 具体的 LSID,用 FASTA 格式命名标识为 HV20_MOUSE的 Swiss-Prot 记录的数据。向 FASTA 的实际转换将实时进行,并作为练习留给读者完成。
urn:lsid:example.org: swiss-ac: p01879 抽象的 LSID,不包含表示(主入藏号或辅助入藏号为 P01879)Swiss-Prot 记录的数据。该 LSID 与这一 Swiss-Prot 记录在各种格式下的具体表示有关。
urn:lsid:example.org: swiss-ac: p01879-sprot 具体的 LSID,用 Swiss-Prot 格式命名入藏号为 P01879的 Swiss-Prot 记录的数据。
urn:lsid:example.org: swiss-ac: p01879-fasta 具体的 LSID,用 FASTA 格式命名入藏号为 P01879的 Swiss-Prot 记录的数据。向 FASTA 的实际转换将实时进行。

获取数据集

可以通过 ftp 从 expasy.org 下载以压缩文件形式存在的 Swiss-Prot 数据库(请参阅 参考资料以获得链接)。请注意,这需要传输大约 63 MB 的数据。将 sprot40.dat.gz 文件保存在方便的位置(~/lsid)。然后需要使用 gunzip 程序解压缩它:

cd ~/lsid
gunzip -d sprot40.dat.gz

完成这一操作后,在您的 lsid 目录中应该有一个名为 sprot40.dat 的文件。该数据库的文件格式在 Swiss-Prot 用户手册中有描述(同样,请参阅 参考资料以获得链接)。





回页首


将数据导入 MySQL 数据库

摆在我们面前的第一个任务是创建将由 LSID 中心使用的 MySQL 用户帐户以及必需的数据表。如果 MySQL 守护程序当前没有运行,则启动它(在 Red Hat Linux 7 和 8 上以 root 身份启动 etc/init.d/mysqld),然后通过输入 mysql -u root -p 以 root 用户的身份启动 MySQL 客户机。输入 root MySQL 用户相应的密码,然后输入以下内容:

清单 3. 创建用户帐户
create database sprot4;
grant all on sprot4.*
  to lsiduser@localhost identified by 'none';
grant all on sprot4.*
  to lsiduser@localhost.localdomain identified by 'none';
grant all on sprot4.*
  to lsiduser@'%' identified by 'none';
use sprot4;
create table byid (
  id varchar(40) unique,
  version varchar(40),
  rootac varchar(40) unique,
  index(version)
);
create table byac (
  ac varchar(40) unique,
  rootac varchar(40),
  index(rootac)
);
create table acdata (
  rootac varchar(40) unique,
  data blob
);

如果您希望免去一些输入工作,可从 samples 目录获得 mysql.batch1 文件,然后运行命令 mysql -f -u root -p < mysql.batch1 。这将创建密码为“none”的用户帐户“lsiduser”,该帐户对数据库 sprot4 有访问权。我们将创建的三个表是 byid、byac 和 byacdata:

表 2. 表 byid

字段(列名) 描述
id名称空间为 swiss-id 的 LSID 的唯一标识。最多 40 个字符。例如, id 将包含 LSID urn:lsid:example.com:SWISS-ID:HV20_MOUSE 的字符串 HV20_MOUSE
version可选的(可能为 NULL)版本字符串,最多 40 个字符。对于 LSID urn:lsid:example.com:SWISS-ID:HV20_MOUSE:version2 ,该字段包含值 version2。我们将不在示例中使用该字段。
rootac对应 LSID 的主 Swiss-Prot 入藏号。对于 LSID urn:lsid:example.com:SWISS-ID:HV20_MOUSE ,该字段包含值 P01789。这是主字段,我们将通过它来访问关于该 LSID 的数据。

表 3. 表 byac

字段(列名) 描述
acSwiss-Prot 入藏号(最多 40 个字符)。辅助入藏号(如 P01234)可在此出现,并将对应于 swiss-ac名称空间中的对象标识。
rootac对应入藏号的主 Swiss-Prot 入藏号。对于 LSID urn:lsid:example.com:SWISS-AC:P01234 ,该字段包含值 P08751。这是主字段,我们将通过它来访问关于该 LSID 的数据。

表 4. 表 acdata

字段(列名) 描述
rootac主 Swiss-Prot 入藏号,用于标识 Swiss-Prot 记录。
data二进制数据对象,包含与 rootac 对应、格式为 Swiss-Prot 的 Swiss-Prot 记录。这是对返回有关所查询 LSID 的实际数据。





回页首


将数据装入 MySQL

在将数据导入新创建的表之前,必须将其从平面文件 sprot40.dat 中解压缩出来。可以使用先前下载的 samples 目录中的 Perl 脚本 extract.pl 来执行这一操作。命令为:

cd ~/lsid
perl extract.pl sprot40.dat byid.txt byac.txt acdata.txt

当数据集相当大时,这当然要花一些时间。您还必须有足够的磁盘空间来保存整个导入过程所产生的数据。一旦 extract.pl 完成其作业,就可以通过输入 mysql -u lsiduser -pnone 以用户“lsiduser”的身份启动 MySQL 客户机,然后输入以下命令:

use sprot4;     
load data local
  infile 'byid.txt' into table byid;
load data local
  infile 'byac.txt' into table byac;
load data local
  infile 'acdata.txt' into table acdata;

这一过程也将花去一些时间。一旦完成,就可以删除文件 byid.txt、byac.txt 和 acdata.txt。如果希望省去输入的工作,可从 samples 目录获得 mysql.batch2,然后运行 mysql -f -u lsiduser -pnone < mysql.batch2

Java 代码

现在我们来研究一个比较重要的 LSID 中心。我们将提供用于解析 LSID 的中心服务、用于 Swiss-Prot 和 FASTA 格式的数据记录的数据服务以及支持有限数量元数据的元数据服务。在研究核心中心代码之前,让我们迅速浏览一下 SampleLSIDDataLookup.java(位于 samples 归档)中的支持例程。


清单 4. 用 Java 查找数据
package com.ibm.lsid.samples;
import java.io.InputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.ibm.lsid.LSID;
import com.ibm.lsid.MalformedLSIDException;
import com.ibm.lsid.server.LSIDServerException;
public class SampleLSIDDataLookup {
  ...
  
  public SampleLSIDDataLookup() throws LSIDServerException {
    ...
  }
  public int lsidType(LSID lsid) throws LSIDServerException {
    ...
  }
  public InputStream lsidData(LSID lsid) throws LSIDServerException {
    ...
  }
}

lsidTypelsidData 的实现不太重要;它们的主要工作就是返回 LSID 的类型(UNKNOWN、ABSTRACT 或 CONCRETE),以及与它相关的、作为 InputStream 对象的数据。如果检测到错误,则抛出相应的 LSIDServerException 异常。

核心中心功能由类 SampleLSIDAuthorityMain 实现:


清单 5. SampleLSIDAuthorityMain 类
package com.ibm.lsid.samples;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import com.ibm.lsid.LSID;
import com.ibm.lsid.LSIDException;
import com.ibm.lsid.ExpiringResponse;
import com.ibm.lsid.wsdl.LSIDDataPort;
import com.ibm.lsid.wsdl.LSIDMetaDataPort;
import com.ibm.lsid.wsdl.LSIDWSDLWrapper;
import com.ibm.lsid.server.LSIDServerException;
import com.ibm.lsid.server.LSIDServiceConfig;
import com.ibm.lsid.server.impl.HTTPDataLocation;
import com.ibm.lsid.server.impl.HTTPMetaDataLocation;
import com.ibm.lsid.server.impl.SOAPDataLocation;
import com.ibm.lsid.server.impl.SOAPMetaDataLocation;
import com.ibm.lsid.server.impl.SimpleAuthority;
public class SampleLSIDAuthorityMain extends SimpleAuthority {
  private SampleLSIDDataLookup lookup = null;
  public void initAuthority(LSIDServiceConfig cf) throws LSIDServerException {
    lookup = new SampleLSIDDataLookup();
  }
  public ExpiringResponse getKnownURIs() throws LSIDServerException {
    return null;
  }
  public LSIDMetaDataPort[] getMetaDataLocations(LSID lsid, String url) {
    if (lookup == null)
      return null;
    int lsType;
    try {
      lsType = lookup.lsidType(lsid);
    }
    catch (LSIDServerException ex) {
      ex.printStackTrace();
      lsType = SampleLSIDDataLookup.UNKNOWN;
    }
    if (lsType == SampleLSIDDataLookup.UNKNOWN)
      return null;
    HostDescriptor hd = new HostDescriptor(url);
    return new LSIDMetaDataPort[] {
      new SOAPMetaDataLocation(
        hd.baseURL + "metadata"
      )
    };
  }
  public LSIDDataPort[] getDataLocations(LSID lsid, String url) {
    if (lookup == null)
      return null;
    int lsType;
    try {
      lsType = lookup.lsidType(lsid);
    }
    catch (LSIDServerException ex) {
      ex.printStackTrace();
      lsType = SampleLSIDDataLookup.UNKNOWN;
    }
    if (lsType == SampleLSIDDataLookup.UNKNOWN)
      return null;
    if (lsType == SampleLSIDDataLookup.ABSTRACT)
      return new LSIDDataPort[0];
    
    HostDescriptor hd = new HostDescriptor(url);
    return new LSIDDataPort[] {
      new SOAPDataLocation(
        hd.baseURL + "data"
      ),
      new HTTPDataLocation(
        hd.host, hd.port,
        hd.pathPrefix + "/authority/data?" + lsid
      )
    };
  }
  private static final Pattern HOST_PTN = Pattern.compile(
    "https?://([^/:]+)(?::(\\d+))?(.*)/authority(.*)"
  );
  
  /* Q&D implementation */
  private class HostDescriptor {
    public String host;
    public int port;
    public String pathPrefix;
    public String baseURL;
    
    public HostDescriptor(String url) {
      host = "localhost";
      port = -1;
      pathPrefix = "";
      if (url != null || url.length() > 0) {
        Matcher m = HOST_PTN.matcher(url);
        if (m.lookingAt()) {
          host = m.group(1);
          if (m.group(2).length() > 0)
            port = Integer.parseInt(m.group(2));
          pathPrefix = m.group(3);
        }
      }
      if (port > 0)
        baseURL = "http://" + host + ":" + port +
          pathPrefix + "/authority/";
      else
        baseURL = "http://" + host + pathPrefix + "/authority/";
    }
  }
}

我们在 initAuthority 函数中所做的全部就是准备 SampleLSIDDataLookup 对象,我们将用它验证要解析的 LSID 是否存在。函数 getKnownURIs 返回 null 而不是服务已知的所有 LSID 的列表;返回后者会产生数兆字节长的响应来描述服务所公开的近 480000 个 LSID。

第一个关键的方法是 getMetaDataLocations 。当处理 getAvailableOperations 服务方法的 SOAP 请求时,中心 servlet 将调用该方法。在验证给定的 LSID 是否存在之后,我们返回包含单个位置(元数据服务的端点)的数组。下面的行需要一些详细说明:

new SOAPMetaDataLocation(
  hd.baseURL + "metadata"
)

SOAPMetaDataLocation 类是专用于 SOAP 端点的 LSIDMetaDataPort 接口的具体实现。该构造函数的参数是正在被显示的元数据服务的全限定 URL,我们使用私有类 HostDescriptor 中的方法构造了它。

getDataLocations 方法看起来与之类似。它并不指定元数据的位置,而是指定可以在哪里获得与 LSID 关联的数据。 SOAPDataLocationHTTPDataLocation 都是 LSIDDataPort 接口的具体实现。 SOAPDataLocation 只采用一个全限定 URL 作为其参数,而 HTTPDataLocation 则期望有主机名、数据端口和到数据的路径。

LSID 解析服务器的下一部分是数据服务。我们实现 LSIDDataService 接口,并将其作为参数传递至 LSID 包提供的 DataServlet servlet 类。


清单 6. 数据服务
package com.ibm.lsid.samples;
import java.io.InputStream;
import com.ibm.lsid.LSID;
import com.ibm.lsid.server.LSIDDataService;
import com.ibm.lsid.server.LSIDServerException;
import com.ibm.lsid.server.LSIDServiceConfig;
public class SampleLSIDAuthorityData implements LSIDDataService {
  private SampleLSIDDataLookup lookup = null;
  public InputStream getData(LSID lsid) throws LSIDServerException {
    if (lookup == null)
      throw new LSIDServerException(500, "Cannot query database");
    return lookup.lsidData(lsid);
  }
  public void initDataService(LSIDServiceConfig cf) throws LSIDServerException {
    lookup = new SampleLSIDDataLookup();
  }
}

数据服务实现不太重要,因为主要的工作都在支持类 SampleLSIDDataLookup 中完成了。也许现在是时候指出下面这一点了:正在使用 samples 包中提供的小型实用程序类 SwissToFastaConverter 实时地从 Swiss-Prot 记录生成 FASTA 格式的数据。

要完成这个包,我们必须提供 getMetaDatainitMetaDataService 的实现。初始化期间没有什么特别的事会发生, getMetaData 只须生成已识别的 LSID 的正确 RDF 描述。


清单 7. 获取元数据
package com.ibm.lsid.samples;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import com.ibm.lsid.LSID;
import com.ibm.lsid.ExpiringResponse;
import com.ibm.lsid.MalformedLSIDException;
import com.ibm.lsid.server.LSIDMetaDataService;
import com.ibm.lsid.server.LSIDServerException;
import com.ibm.lsid.server.LSIDServiceConfig;
public class SampleLSIDAuthorityMetaData implements LSIDMetaDataService {
  private SampleLSIDDataLookup lookup = null;
  public void initMetaDataService(LSIDServiceConfig cf) throws LSIDServerException {
    lookup = new SampleLSIDDataLookup();
  }
  
  private static final String RDF_NS=
    "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
  private static final String DC_NS=
    "http://purl.org/dc/elements/1.1/";
  private static final String I3CP_NS=
    "urn:lsid:i3c.org:predicates:";
  private static final String I3C_CONTENT=
    "urn:lsid:i3c.org:types:content";
  private static final String I3C_SPROT=
    "urn:lsid:i3c.org:formats:sprot";
  private static final String I3C_FASTA=
    "urn:lsid:i3c.org:formats:fasta";
  private void appendTripleResource(
    StringBuffer src,
    String subj, String pred, String obj
  ) {
    src.append("<rdf:Description rdf:about=\"");
    src.append(subj);
    src.append("<");
    src.append(pred);
    src.append(" rdf:resource=\")");
    src.append(obj);
    src.append("\"/></rdf:Description>");
  }
  
  public ExpiringResponse getMetaData(LSID lsid) throws LSIDServerException {
    int lsType;
    try {
      lsType = lookup.lsidType(lsid);
    }
    catch (LSIDServerException ex) {
      ex.printStackTrace();
      lsType = SampleLSIDDataLookup.UNKNOWN;
    }
    if (lsType == SampleLSIDDataLookup.UNKNOWN)
      throw new LSIDServerException(201, "Unknown LSID");
    StringBuffer result= new StringBuffer();
    result.append("<?xml version=\"1.0\"?><rdf:RDF");
    result.append(" xmlns:rdf=\"");
    result.append(RDF_NS);
    result.append("\" xmlns:dc=\"");
    result.append(DC_NS);
    result.append("\" xmlns:i3cp=\"");
    result.append(I3CP_NS);
    result.append(">");
    
    String baseLSID= lsid.toString();
    if (baseLSID.endsWith("-fasta") || baseLSID.endsWith("-sprot"))
      baseLSID.substring(0, baseLSID.length() - 6);
    
    if (lsType == SampleLSIDDataLookup.ABSTRACT) {
      appendTripleResource(
        result,
        baseLSID, "i3cp:storedas", baseLSID + "-fasta"
      );
      appendTripleResource(
        result,
        baseLSID, "i3cp:storedas", baseLSID + "-sprot"
      );
      appendTripleResource(
        result,
        baseLSID + "-fasta", "rdf:type", I3C_CONTENT
      );
      appendTripleResource(
        result,
        baseLSID + "-sprot", "rdf:type", I3C_CONTENT
      );
      appendTripleResource(
        result,
        baseLSID + "-fasta", "dc:format", I3C_FASTA
      );
      appendTripleResource(
        result,
        baseLSID + "-sprot", "dc:format", I3C_SPROT
      );
    }
    else {
      String format= I3C_SPROT;
      if (lsid.getObject().endsWith("-fasta")) {
        format= I3C_FASTA;
      }
      appendTripleResource(
        result,
        baseLSID, "i3cp:storedas", baseLSID + "-fasta"
      );
      appendTripleResource(
        result,
        lsid.toString(), "rdf:type", I3C_CONTENT
      );
      appendTripleResource(
        result,
        lsid.toString(), "dc:format", format
      );
    }
    result.append("</rdf:RDF>");
    
    return new ExpiringResponse(
      new ByteArrayInputStream(
        result.toString().getBytes()
      ),
      null
    );
  }
}

运行和测试中心

要测试 Swiss-Prot 中心,可将文件 swiss-prot.war 复制到 Jakarta Tomcat webapps 目录。在 Tomcat 启动时,该文件将解压缩至 webapps/swiss-prot,并且中心服务将在 http://localhost:8080/swiss-prot/authority/ 可用。您可以用测试 Hello World LSID 中心的同一方法运行 TestClient 来测试该中心:

java TestClient urn:lsid:ibm.com:swiss-id:hv20_mouse \
http://localhost:8080/swiss-prot/authority/

期望的输出是:

Authority version is: 3
The authority knows about 0 LSIDs:
Meta is available at:
(soap) http://localhost:8080/swiss-prot/authority/metadata
-- META DATA --
<?xml version="1.0"?><rdf:RDF xmlns:rdf="http://www.w3.org/199....
----

如果决定对具有关联数据的 LSID 进行测试,可以尝试使用命令:

java TestClient urn:lsid:ibm.com:swiss-id:hv20_mouse-fasta \
http://localhost:8080/swiss-prot/authority/

本例中的期望输出是:

Authority version is: 3
The authority knows about 0 LSIDs:
Data is available at:
(soap) http://localhost:8080/swiss-prot/authority/data
(http) http://localhost:8080/swiss-prot/authority/data?
urn:lsid:ibm.com:swiss-id:hv20_mouse-fasta
Meta is available at:
(soap) http://localhost:8080/swiss-prot/authority/metadata
-- DATA --
>HV20_MOUSE (P01789) Ig heavy chain V region M603.
EVKLVESGGGLVQPGGSLRLSCATSGFTFSDFYMEWVRQPPGKRLEWIAASRNKGNKYTTEYSASVKGRFIVSRDTSQ
SILYLQMNALRAEDTAIYYCARNYYGSTWYFDVWGAGTTVTVSS
----
-- META DATA --
<?xml version="1.0"?><rdf:RDF xmlns:rdf="http://www.w3.org/1999/......
----





回页首


元数据

关于 LSID 的元数据可以用 RDF(请参阅 RDF Primer,在 参考资料中列出)来描述。关于 LSID 的最小有用信息包括它是否有关联的数据、数据的格式、或者在 LSID 没有关联数据的情况下,最小有用信息是在哪里可以找到特殊格式的概念的特定表示。





回页首


关于 RDF

RDF 文档由一些简单的语句组成,每个语句包含三个部分:主语、谓语和宾语(值)。我们用英语“Spot barks like a dog”来解释 RDF 三元组,其中“Spot”是主语,“barks like”是谓语,“dog”是宾语。RDF 只是编码这类信息的一种正规方法。关于某一特定 LSID 的元数据由一组 RDF 语句组成。可以将谓语(也称为属性)本身看作特殊种类的主语(资源)。RDF 模式规范(RDF Schema Specification)(请参阅 参考资料)说明了如何使用 RDF 语句描述谓语之间的关系。主语和谓语始终由 URI 命名,由于 LSID 是一个 URN,而 URN 是一种 URI,所以 LSID 既可用作 RDF 语句,也可用作谓语。RDF 中的宾语可以是 URI(在此情况下可使用 LSID),也可以是所谓的“文字”值(也许可以输入,也许不能输入)。





回页首


示例

假定 LSID urn:lsid:pets.org:cats:Tom 给一只猫取名字。这样,该 LSID 表示 Tom 猫的抽象概念,但不描述它的任何特定及不变的位集合。然而,有些与 Tom 相关的东西(如它还是小猫时的照片)有具体的数字表示法。假设 urn:lsid:pets.org:cats:Tom-photos:Nov-22-1998 代表 Tom 的某一张照片。我们可以使用 RDF 属性 urn:lsid:i3c.org:predicates:storedAs 将这张照片“附加”到 urn:lsid:pets.org:cats:Tom ,如下所示:

<rdf:Description rdf:about="urn:lsid:pets.org:cats:tom">
    <i3c:storedas rdf:resource="urn:lsid:pets.org:cats:tom-photos:nov-22-1998"/>
</rdf:Description>

请注意,RDF 中的 URI 是区分大小写的,而 LSID 不区分大小写。为避免任何可能出现的错误,应始终用 LSID 的规范形式(小写)在 RDF 中表示它们。看起来较特殊的 XML 标记 i3c:storedas 是属性的名称。假设名称空间前缀 i3c 代表 urn:lsid:i3c.org:predicates: ,则全限定属性名是 urn:lsid:i3c.org:predicates:storedas (前缀和标记名称的并置)。

因为 Tom 的照片有与其关联的数据,所以我们必须在元数据中描述这一事实。类 urn:lsid:i3c.org:types:content 包含了所有具有关联数据的事物,因此 Tom 的照片属于这一类。我们用一条 RDF 语句描述这一事实:

<rdf:Description rdf:about="urn:lsid:pets.org:cats:tom-photos:nov-22-1998">
    <rdf:type rdf:resource="urn:lsid:i3c.org:types:content"/>
</rdf:Description>

rdf:type 属性用于表示类的成员资格。名称空间前缀 rdf 通常用于表示名称空间 http://www.w3.org/1999/02/22-rdf-syntax-ns#,这在 RDF 规范中定义。

最后,Tom 的照片是以某种格式(如 JPEG)存储的。我们可以使用 Dublin Core RDF 词汇表(请参阅 参考资料)的一部分来表示这一事实。LSID urn:lsid:i3c.org:formats:jpg 表示 JPEG 数据格式的概念。描述所有这一切的 RDF 语句是:

<rdf:Description rdf:about="urn:lsid:pets.org:cats:tom-photos:nov-22-1998">
    <dc:format rdf:resource="urn:lsid:i3c.org:formats:jpg"/>
</rdf:Description>

dc:format 属性用于描述资源的格式。名称空间前缀 dc 通常用于表示名称空间 http://purl.org/dc/elements/1.1/。

通常将 RDF 文档显示为相互链接的图。对于我们的示例,其示意图看起来如下:

图 1. 关系的图形表示法
关系的图形表示法

元数据持久性的说明

和与 LSID 关联的数据不同,元数据会到期。这意味着关于某一 LSID 的元数据是存储所关注对象的瞬态信息的极佳位置。例如,Tom 猫主页的链接可以出现在关于 Tom 的元数据中,并可以随时修改。

给元数据添加值

应将标准属性名尽可能地用于描述资源的特定集合。使用标准词汇可以大大增强元数据的提供者和消费者之间的互操作性。然而,预先存在的属性有时不能充分描述某一给定的关系。在此情况下,应采用能达到目的的新属性。如果提供了该属性的 RDF 模式(RDF Schema)或 Web 本体描述语言(Web Ontology Language)描述,那么支持 RDF 模式的客户机即使不具备您使用的谓语的专门知识,仍然能够理解元数据的意义。

LSID 的位置独立性和不变性的保证使其成为数据库交叉引用的极佳候选。如果您了解两个 LSID 之间有意义的关系,那么不管是谁发出的 LSID,都应该在元数据中描述它。无论源自何处,任何能够解析 LSID 的客户机都能够这样做。





回页首


使中心公开可用

要使一个中心公开可用且符合 LSID 解析建议(LSID Resolution Proposal),您需要提供一种方法使人们无需提前知道您服务的确切位置就可以解析由您的中心处理过的 LSID。也就是说,您中心的客户机不需要编辑它们的 authority ,或做任何类似的事情。

解决这一任务的第一步是建立您中心的 DNS 服务记录。以 pdb.org 中心为例,您应该能够确定 LSID 服务所驻留的主机名和端口号。请输入以下命令:

host -t srv _lsid._tcp.pdb.org

您正在向 DNS 询问使用 TCP 作为网络协议的 pdb.org 的 lsid 服务记录。响应看起来应和下面相似:

_lsid._tcp.pdb.org SRV 1 0 8080 lsidauthority.pdb.org.

这告诉我们,pdb.org 中心的服务正运行在名为 lsidauthority.pdb.org 的主机上,并且在 TCP 端口 8080 上等待连接。遗憾的是,该信息不足以确定 pdb.org 中心服务的端点。这就是为什么 LSID 解析建议要求服务应在主机路径 /authority/ 上可用。在 pdb.org 的例子中,中心服务的全限定 URL 就应该是:http://lsidauthority.pdb.org:8080/authority/。

设置 DNS

您(或您的系统管理员)必须做的全部就是添加一条将运行中心的机器的服务记录。假定该机器是 authority.company.net,并且它将作为名为 company.net 的中心提供服务。进一步假设该服务将位于端口 8080。应该将这条必须添加的记录放入 company.net 的 DNS 服务器的主区域文件(可能是 company.net 上名为 /var/named/company.net.zone 的文件):

_lsid._tcp      IN      SRV     1       0       8080    authority.company.net.

如果该中心名称假设为 authority.company.net 而非 company.net,则 company.net 区域文件中的记录应该类似于:

_lsid._tcp.authority    IN      SRV     1       0       8080    authority.company.net.





回页首


结束语

我们希望,本文这种循序渐进的方法以及 参考资料中以压缩文件形式提供的内容丰富的 samples 目录,将使您迅速上手。同样本着节省您时间和精力的精神,我们还包括了一个备忘副本,您可以用电子邮件将其发送给您的 DNS 管理员以请求实现一条 SRV 记录:

To:<此处填写 DNS 管理员名称>
cc:<此处填写部门经理的名称>
Subject:允许解析至我的 LSID 中心的 DNS 记录

请将下列 SRV 记录添至相应的区域
文件:

_lsid._tcp IN SRV 1 0 <此处填写端口> <此处填写中心主机名>。

如果您正在运行 BIND V4 或更高版本,或对等的域名服务(Domain Name Service),
那么您的系统将支持 SRV 记录(RFC 2782)。

使用这一 SRV 记录的原因是允许运行 LSID
协议(在 http://www.i3c.org/workgroups/techarch/resources/
lsid/docs/LSIDSyntax9-20-02.htm 有说明)的客户机能解析至我运行的 LSID 中心服务器,
我正在<此处填写
主机名>的端口<此处填写端口号>上运行该服务器。

有关 LSID 协议、解析模型和中心服务器的更多信息,
请参阅 http://ibm.com/developerworks/oss/lsid。

请在这一设置开始生效时通知我。

谢谢。

致敬。



参考资料



关于作者

Stefan Atev 是天才孵化计划(Extreme Blue)2001 期学员,曾在过去几个暑期回到 IBM 工作。他已经从事了 SashXB Web 服务客户机的工作,目前正在参与生命科学标识项目,实现生命科学数据存储和应用语义 Web 技术以提高现有生命科学数据库的可用性。可以通过 satev@us.ibm.com与 Stefan 联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款