Unknown macro: {center}
Portal OpenBus

Data Service
Skip to end of metadata
Go to start of metadata

Implementando Servidor DataService 1.2

Repositório GIT do DataService

Configuração para projetos Maven

URL do servidor Nexus do Tecgraf: http://maven.tecgraf.puc-rio.br:8081/nexus

Dependências:

<dependency>
  <groupId>br.puc-rio.tecgraf.openbus</groupId>
  <artifactId>data_service-core-v1.2</artifactId>
  <version>1.2.1.0</version>
</dependency>
 
<dependency>
   <groupId>br.puc-rio.tecgraf.openbus</groupId>
   <artifactId>data_service-project-v1.2</artifactId>
   <version>1.2.0.1</version>
</dependency>

Definição da regra de formação do DataKey

O datakey deve ser definido pelo implementador do servidor DataService. No datakey pode-se inserir qualquer informação que o serviço entenda como importante para identificar o dado. Pode-se, por exemplo, usar como parte da chave o caminho do dado no sistema de arquivos ou a chave do dado em um banco de dados. O datakey também pode conter informações suficientes para identificar o dado num barramento OpenBus.

O datakey deve representar um dado de forma unívoca em um servidor de dados e será usado pelos clientes de forma opaca, ou seja, o cliente não precisa entender sua estrutura para ter acesso ao dado. Um datakey é definido em CORBA IDL como um vetor de bytes e deve conter quatro campos separados pelo caracter ; (ponto e vírgula).

  1. versão: string para identificar a versão da API DataService.
  2. entidade: string para identificar o sistema que autentica-se em um barramento OpenBus.
  3. identificação da fonte de dados (dataSourceId): string para auxiliar a identificação de diferentes fontes de dados em um mesmo servidor DataService.
  4. identificação do dado (dataId): string para identificar o dado em uma fonte de dados em um servidor DataService.

Exemplos:

  • Versão 1.2, um servidor que se autentica no OpenBus com a entidade dataserver_treinamento_brasil, organiza suas fontes de dados como diretórios no sistema de arquivos (/tmp/sandbox) e identifica o dado com a localização do arquivo dentro do diretório (primeiro_arquivo.txt).

    v1_02;dataserver_treinamento_brasil;/tmp/sandbox;primeiro_arquivo.txt
  • Versão 1.2, um servidor que se autentica no OpenBus com a entidade openspiritserver_treinamento_brasil, organiza suas fontes de dados como o nome de instâncias de banco em um outro software de acesso a dados (OpenWorks2003) e identifica o dado de acordo com a chave de acesso ao dado nesse outro software (tag <key> em notação xml).

    v1_02;openspiritserver_treinamento_brasil;OpenWorks2003;<key en="EpiSeismic_PostStack2d" mv="2.9" dn="MyDomainExample" tn="OpenWorks" tv="2003" pn="MyProject"><sen="Seismic2D" id="1"/><a n="project" ei="1">osp2d</a><a n="filename" ei="1">line1234798.2v2_glb</a></key>

Codificação e serialização do DataKey

datakey é definido como um vetor de bytes, então é importante se preocupar com a codificação de caracteres. Em nossos exemplos usamos a codificação de caracteres US-ASCII. A depender do ambiente de execução do servidor DataService e da definição do identificador do dado pode ser necessário usar UTF-8, ISO-8859-1 ou outra codificação.

A serialização de um datakey precisa se preocupar com caracteres brancos não imprimíveis. A recomendação é usar a notação Base64 para serialização.

Exemplos:

  • Serialização de um DataKey em Base64:

    import org.apache.commons.codec.binary.Base64;
    ...
    byte[] datakey = "v1_02;dataserver_treinamento_brasil;/tmp/sandbox;primeiro_arquivo.txt".getBytes("ASCII");
    System.out.println(new String(Base64.encodeBase64(datakey)));
    //RESULTADO: djFfMDI7ZGF0YXNlcnZlcl90cmVpbmFtZW50b19icmFzaWw7L3RtcC9zYW5kYm94O3ByaW1laXJvX2FycXVpdm8udHh0
  • Desserialização de uma string Base64 para um DataKey:

    import org.apache.commons.codec.binary.Base64;
    ...
    //ENTRADA: djFfMDI7ZGF0YXNlcnZlcl90cmVpbmFtZW50b19icmFzaWw7L3RtcC9zYW5kYm94O3ByaW1laXJvX2FycXVpdm8udHh0
    byte[] datakey = Base64.decodeBase64(input);
    System.out.println(new String(datakey));
    //RESULTADO: v1_02;dataserver_treinamento_brasil;/tmp/sandbox;primeiro_arquivo.txt

Exemplo de preenchimento da estrutura que descreve um dado

Um dado é descrito através da estrutura do DataDescription. Essa estrutura pode ser obtida tanto através da interface DataService (método getDataDescription) quanto da interface IHierarchicalNavigationDataService (getRoots, getChildren, getParent). Um DataDescription é definido em CORBA IDL pela seguinte estrutura:

typedef sequence<string> StringSeq;
 
struct DefaultView {
  string fInterfaceName; // O nome da interface da visão
  DataView fValue; // A instância da visão
};
 
struct Metadata {
  string fName; // O nome do metadado
  any fValue;  // O valor do metadado
};
typedef sequence<Metadata> MetadataSeq;
 
struct DataDescription {
  DataKey fKey; // A chave unívoca do dado
  string fName; // O nome simbólico do dado
  DefaultView fDefaultView; // A visão padrão do dado (opcional)
  StringSeq fOthersViews; // As outras visões oferecidas pelo dado, ou seja, as visões diferentes da visão padrão
  MetadataSeq fMetadata; // Metadados (opcionais) do dado
}; 
 

É importante ter uma atenção especial com os campos fDefaultView, fOthersViews e fMetadata:

  • fDefaultView : esse campo não pode ser nulo pois é definido como uma outra estrutura. Em CORBA IDL, campos que são estruturas não podem ter assumir valor nulo.
  • fOthersViews : esse campo é uma lista de nome das interfaces das visões adicionais. 
  • fMetadata : esse campo é uma lista de metadados, os quais são definidos por uma estrutura que possui um campo para o nome e o valor é definido como um Any. Em CORBA IDL, o Any é um tipo variável que pode conter qualquer outro tipo previamente definido em IDL (incluindo outras estruturas).

Exemplo:

import tecgraf.openbus.data_service.core.v1_02.DataDescription;
import tecgraf.openbus.data_service.core.v1_02.DefaultView;
import tecgraf.openbus.data_service.core.v1_02.Metadata;
import tecgraf.openbus.data_service.core.v1_02.UnstructuredDataViewHelper;
...
DataDescription desc = new DataDescription();
desc.fKey = "v1_02;dataserver_treinamento_brasil;/tmp/sandbox;primeiro_arquivo.txt".getBytes("ASCII");
desc.fName = "primeiro_arquivo.txt";
desc.fDefaultView = new DefaultView("", null); // DefaultView vazio
desc.fOthersViews = new String[] { UnstructuredDataViewHelper.id() }; // UnstructuredDataView como visão adicional

Any mimetype = orb.create_any();
mimetype.insert_string("text/plain");
Any charset = orb.create_any();
charset.insert_string("US-ASCII");

desc.fMetadata = new Metadata[] {
    new Metadata("mimetype", mimetype),
    new Metadata("charset", charset)
};

A visão para dados não-estruturados (UnstructuredDataView)

Uma forma de usar a API DataService com dados não-estruturados é fornecer uma visão UnstructuredDataView. Através dela é possível indicar o endereço de um máquina servidora (hostname e porta) e uma chave de acesso (AccessKey) que permitirá à máquina servidora reconhecer sobre qual dado se trata.

A chave de acesso (AccessKey) não precisa ter relação com o DataKey do DataService. É deixado a cargo do implementador decidir como gerar as chaves de acesso e sua definição pode variar de acordo com o protocolo usado para fornecer o dado não-estruturado.

Essa visão está definida em CORBA IDL como segue:

/**
 * Visão não-estruturada de um dado para a transferência de seu conteúdo através de um socket.
 */
valuetype UnstructuredDataView : ValueTypeDataView {
  public string fHost; // O nome do host de origem do dado
  public unsigned long fPort; // A porta do host de origem do dado
  public OctetSeq fAccessKey; // A chave de acesso ao dado
  public boolean fWritable; // Flag que indica se é possível alterar no dado
};

Nessa visão não é previsto o preenchimento do conteúdo em si do dado não-estruturado, portanto será preciso escolher um protocolo para transferência desse dado. A escolha do protocolo é uma decisão do implementador e os clientes precisam estar cientes dessa escolha.

Exemplos de como usar essa visão com protocolos conhecidos:

  • HTTP e FTP: o campo fPort pode indicar 80 e 21, respectivamente. O campo fAccessKey pode ser o caminho para o arquivo e pode encapsular algum token de autenticação. O campo fWritable pode se é possível fazer um PUT ou apenas um GET.
  • RTP e RTSP: provavelmente apenas os campos fHost e fPort serão necessários, mas o campo fAccessKey pode ser o caminho para o stream e ser útil para implementar um controle de acesso ao stream.

Implementando Servidor FTC 1.1

O Tecgraf/PUC-Rio define e implementa o protocolo FTC (File Transfer Channels) para acessar remotamente arquivos através da abstração de canais de uma forma multi-linguagem. Essa abstração de canais é semelhante aos canais do Java NIO e permite um controle preciso sobre leituras e escritas favorecendo a escalabilidade. As linguagens suportadas pelo FTC são: C++, Java e Lua. C# passou a ser suportada a partir da versão 1.4 do FTC.

Nesse tutorial será exemplificada a implementação de um servidor FTC 1.1 em Java.

Repositório GIT do FTC

https://git.tecgraf.puc-rio.br/ftc/ftc-core/tree/01_01_07

Configuração para projetos Maven

<dependency>
   <groupId>br.puc-rio.tecgraf.ftc</groupId>
   <artifactId>ftc</artifactId>
   <version>1.1.7</version>
</dependency>

Provendo arquivos através de um Servidor FTC

Deve-se começar implementando a interface tecgraf.ftc.server.FileServerOwner. O método createFileChannel será invocado pela biblioteca do FTC quando for solicitado um canal para o arquivo e possui os seguintes parâmetros:

  • parâmetros de entrada:
    • requester: é um objeto Java qualquer que a API prevê para permitir que o implementador identifique o responsável pela requisição.
    • fileId: é um identificador do arquivo no servidor e não deve ultrapassar 255 bytes. Tipicamente será a localização do arquivo dentro do servidor.
    • readOnly: é uma flag para indicar se o canal deve ser aberto apenas para leitura ou se também para escritas.
  • resultado:
    • deve ser retornado um canal java.nio.channels.FileChannel.

 

Classe que cria os canais (FileServerOwnerImpl.java)
import tecgraf.ftc.common.exception.FailureException;
import tecgraf.ftc.common.exception.PermissionException;
import tecgraf.ftc.server.FileServerConfig;
import tecgraf.ftc.server.FileServerOwner;

import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

public class FileServerOwnerImpl implements FileServerOwner {
	private FileServerConfig config;
	...
	public FileChannel createFileChannel(Object requester, byte[] fileId, boolean readOnly)
            throws PermissionException, FailureException {

        String filename = new String(fileId);

        System.out.println("FTC: requester = " + requester.toString());
        System.out.println("FTC: fileid = " + filename);

        try {
            String mode = (readOnly ? "r" : "rw");
            RandomAccessFile randomAccessFile = new RandomAccessFile(filename, mode);
            return randomAccessFile.getChannel();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            throw new FailureException(e);
        }
    }
	...
	public FileServerConfig getConfig() {
	    return config;
	}

	public void setConfig(FileServerConfig config) {
    	this.config = config;
	} 
}

Após implementar o provedor de canais, basta instanciar um objeto da classe tecgraf.ftc.server.FileServer fornecendo sua implementação do provedor de canais FileServerOwnerImpl (definida no passo anterior). O servidor do FTC é bloqueante e usa seletores do Java NIO.

Classe principal do servidor (Server.java)
import tecgraf.ftc.server.FileServer;
import tecgraf.ftc.server.FileServerConfigImpl;
...
public class Server {
	public static void main(String[] args) {
		...
		FileServerConfigImpl configImpl = new FileServerConfigImpl();
		configImpl.setHostName("localhost");
		configImpl.setPort(9999);

		FileServerOwnerImpl ownerImpl = new FileServerOwnerImpl();
		ownerImpl.setConfig(configImpl);

		final FileServer ftcServer = new FileServer(ownerImpl);

        ftcServer.dispatch(); // inicia loop de eventos bloqueante
	}
}

Para interromper o loop de eventos bloqueante do FTC será preciso executar o método stop do servidor FTC.

Exemplo da captura do sinal de aborto de um processo em Java e parada adequada do servidor FTC:

Runtime.getRuntime().addShutdownHook(
        new Thread() {
            public void run() {
                // parando o atendimento de requisições FTC
                ftcServer.stop();
            }
        });

Caso não se queira bloquear a thread principal será preciso utilizar uma outra thread para executar o método dispatch do servidor FTC.  Exemplo:

// thread para tratar as requisições do protocolo FTC
final Thread ftcServerThread = new Thread() {
    public void run() {
        ftcServer.dispatch();
    }
};
// disparo da thread para o servidor FTC
ftcServerThread.start();
...
// captura do sinal de aborto de um processo em Java e parada adequada do servidor FTC e da thread adicional
Runtime.getRuntime().addShutdownHook(
        new Thread() {
            public void run() {
                // parando o atendimento de requisições FTC
                ftcServer.stop();
				try {
				    // esperando a thread do servidor FTC terminar
				    ftcServerThread.join();
				} catch (InterruptedException e) {
				    e.printStackTrace();
				}
            }
        });

Combinando um Servidor FTC com um Servidor DataService

Provimento da visão UnstructuredDataView a partir de canais do FTC

É possível combinar um servidor FTC com o DataService através do preenchimento da visão UnstructuredDataView. No momento da criação da UnstructuredDataView será preciso criar um canal para o arquivo.

Para isso deve usar o método createFileChannelInfo da interface FileServer do FTC. Esse método espera um objeto requester para representar o reponsável pela requisição e um array de bytes fileId para identificar o arquivo. O tamanho do fileId não deve ultrapassar 255 bytes. O valor de retorno é um tecgraf.ftc.server.FileChannelAccessInfo que permite obter host, port e AccessKey que farão parte da visão UnstructuredDataView.

No exemplo a seguir é apresentada a implementação do método getDataView da interface DataService provendo um UnstructuredDataView. Nesse exemplo, o fileId foi definido pela composição do dataSourceId e dataId já presentes no DataKey e separados pelo caracter / (separador de diretórios no Unix).

import tecgraf.ftc.common.exception.InvalidArraySize;
import tecgraf.ftc.server.FileChannelAccessInfo;
import tecgraf.ftc.server.FileServer;
import tecgraf.openbus.data_service.core.v1_02.DataAccessDenied;
import tecgraf.openbus.data_service.core.v1_02.DataDescription;
import tecgraf.openbus.data_service.core.v1_02.DataKeyWrapper;
import tecgraf.openbus.data_service.core.v1_02.DataNotFound;
import tecgraf.openbus.data_service.core.v1_02.DataView;
import tecgraf.openbus.data_service.core.v1_02.IDataServicePOA;
import tecgraf.openbus.data_service.core.v1_02.InvalidDataKey;
import tecgraf.openbus.data_service.core.v1_02.ServiceFailure;
import tecgraf.openbus.data_service.core.v1_02.UnstructuredDataViewHelper;
import tecgraf.openbus.data_service.core.v1_02.UnstructuredDataViewImpl;
import tecgraf.openbus.data_service.core.v1_02.UnsupportedView;

import java.io.File;

public class DataServiceImpl extends IDataServicePOA {
    private FileServer ftcServer;

    public DataServiceImpl(FileServer ftcServer) {
        this.ftcServer = ftcServer;
    }
	...
    public DataView getDataView(byte[] dataKey, String interfaceName)
            throws UnsupportedView, ServiceFailure, DataAccessDenied, DataNotFound, InvalidDataKey {

		DataKeyWrapper wrapper = new DataKeyWrapper(dataKey);
        // regra de formação de fileIds para identificar arquivos no FTC
        String fileId = wrapper.getDataSourceId() + "/" + wrapper.getDataId();

        File file = new File(wrapper.getDataSourceId(), wrapper.getDataId());
        if (!file.exists())
            throw new DataNotFound("Arquivo " + fileId + " não existe!", new byte[][]{dataKey});

        if (interfaceName.equals(UnstructuredDataViewHelper.id())) {
            try {
                FileChannelAccessInfo info =
                        ftcServer.createFileChannelInfo(this, fileId.getBytes());

                return new UnstructuredDataViewImpl(dataKey, info.getHost(), info.getPort(),
                        info.getAccessKey(), file.canWrite());

            } catch (InvalidArraySize e) {
                e.printStackTrace();
                throw new ServiceFailure("DATASERVICE ERROR: " + e.getMessage());
            }
        }

		throw new UnsupportedView("Não há suporte à visão do tipo " + interfaceName, new byte[][]{dataKey});
    }
}

AccessKey é gerado automaticamente pelo servidor FTC, mas também pode ser definido manualmente. Para isso é possível usar a assinatura alternativa do método createFileChannelInfo:

public FileChannelAccessInfo createFileChannelInfo(Object requester, byte[] fileId, byte[] accessKey) throws InvalidArraySize;

Exceções previstas na API do DataService

É importante atenção ao usar as exceções prevista na API do DataService pois elas possuem campos e por isso devem ser construídas corretamente para evitar erros na serialização em CORBA. Em Java, o compilador de IDL gera construtores com parâmetros adicionais para cada campo presente na exceção.

Exceções em CORBA IDL:

/** Indica que uma visão não suportada foi solicitada. */
exception UnsupportedView {
  string fMessage; /**< Uma mensagem com o detalhamento do erro. */
  DataKeySeq fKeys; /**< As chaves dos dados que não oferecem a visão. */
};

/** Indica que uma visão que deveria ser oferecida por um dado não foi encontrada. */
exception AbsentViews {
  string fMessage; /** Uma mensagem com o detalhamento do erro. */
  DataKeySeq fKey; /** A chave do dado que não oferece as visões. */
  StringSeq fViews; /** A lista das visões não encontradas. */
};

/** Indica que chaves de dados são inválidas. */
exception InvalidDataKey {
  string fMessage; /** Uma mensagem com o detalhamento do erro. */
  DataKeySeq fKeys; /** As chaves dos dados que são inválidas. */
};

/** Indica que o usuário não possui permissão para acessar certos dados. */
exception DataAccessDenied {
  string fMessage; /** Uma mensagem com o detalhamento do erro. */
  DataKeySeq fKeys; /** As chaves dos dados que não podem ser acessados. */
};

/** Indica que dados não foram encontrados. */
exception DataNotFound {
  string fMessage; /** Uma mensagem com o detalhamento do erro. */
  DataKeySeq fKeys; /** As chaves dos dados não encontrados. */
};

/** Indica que houve uma falha no serviço. */
exception ServiceFailure {
  string fMessage; /** Uma mensagem com o detalhamento do erro. */
};

/** Indica que o dado já existe. */
exception DataAlreadyExist {
  string fMessage; /** Uma mensagem com o detalhamento do erro. */
};

/** Indica que o servidor de origem de um dado está inacessível. */
exception UnavailableDataService {
  string fMessage; /** Uma mensagem com o detalhamento do erro. */
};

Exemplos de lançamento das exceções UnsupportedView e DataNotFound:

// exemplos da DataNotFound:
throw new DataNotFound("Arquivo "+ fileId + " não existe!", new byte[][]{dataKey});
// ou ainda:
DataNotFound ex = new DataNotFound();
ex.fMessage = "Arquivo "+ fileId + " não existe!";
ex.fDataKeys = new byte[][] {dataKey};
throw ex;
...
// exemplos da UnsupportedView:
throw new UnsupportedView("não há suporte à visão " + interfaceName, new byte[][]{dataKey});
// ou ainda:
UnsupportedView ex = new UnsupportedView();
ex.fMessage = "Não há suporte à visão do tipo " + interfaceName;
ex.fDataKeys = new byte[][]{dataKey};
throw ex;
  • No labels
Write a comment…