Lab5 Lívia

aluno: Lívia Palomo
ano/sem: 2008/2o.
data do laboratório (num. da semana) : 05/12/2008 (10)

Introdução

A proposta deste laboratório foi construir uma pequena aplicação servidor de um software de apostas em jogos de futebol. Um código inicial foi fornecido como base para desenvolvermos o programa.

Desenvolvimento

Todas as modificações realizadas e as funcionalidades inseridas se referem aos itens abaixo:

1) Decorators

Criei as classes MatchListThreadSafeDecorator e MatchListPersistenceDecorator (herdada ListPersistenceDecorator) com o objetivo de decorar corretamente as listas de Matches. A classe MatchListThreadSafeDecorador também é necessária, mas já foi fornecida. Esta necessidade tem origem em diversos motivos:

- As classes ListPersistenceDecorator e ListThreadSafeDecorador, assim como a ListLoggerDecorator, não poderiam ser usadas para decorar uma MatchList, pois os elementos da lista, ou seja, os Matches, devem ser decorados um a um com MatchPersistenceDecorator, MatchThreadSafeDecorator e MatchLoggerDecorator toda vez que a lista for decorada com o Decorator correspondente;
- O método add da MatchList deve ser sobrescrito no Decorator de ThreadSafe e de Logger para que os parâmetros sejam especificados;
- O método add da MatchList deve ser sobrescrito no Decorator de Logger, para que seja emitida uma mensagem apropriada no Logger quando um jogo for adicionado.

Somente desta forma todos os métodos da MatchList e de Match receberiam tratamento adequado (quanto a persistirem novas informações ou lançarem mensagens no log, por exemplo).

Seguem abaixo os Decorators criados por mim (baseados na versão antiga do código fornecido):

MatchListPersistanceDecorator:

class MatchListPersistanceDecorator < ListPersistanceDecorator
 
    def initialize(decorated, filename)     
 
      old_match_list_decorator = decorated.item_decorator     
      decorated.item_decorator = lambda do |match|   
        MatchPersistanceDecorator.new( old_match_list_decorator.call(match), self)     
      end
      super(decorated,filename) 
 
    end            
end

MatchListThreadSafeDecorator:

class MatchListThreadSafeDecorator < Decorator
 
    include MonitorMixin
 
    def initialize(decorated)
 
      old_match_list_decorator = decorated.item_decorator      
      decorated.item_decorator = lambda do |match_list|
        MatchThreadSafeDecorator.new( old_match_list_decorator.call(match_list) )
      end
      super(decorated)
    end
 
    def add(name, end_time)
      synchronize do
        @decorated.add(name,end_time)
      end
    end
 
end

MatchPersistanceDecorator:

class MatchPersistanceDecorator < Decorator
 
    def initialize(obj,decorated_match_list)
      super(obj)
      @decorated_match_list = decorated_match_list
    end
 
    def make_bet(score, user)
      obj = @decorated.make_bet(score,user)
      @decorated_match_list.save      
      obj
    end
 
    def finalize(final_score)
      obj = @decorated.finalize(final_score)
      @decorated_match_list.save      
      obj
    end   
 
end

MatchThreadSafeDecorator:

class MatchThreadSafeDecorator < Decorator
 
    include MonitorMixin
 
    def make_bet(score, user)
      synchronize do
        @decorated.make_bet(score, user);
      end
    end
 
    def finalize(final_score)
      synchronize do
        @decorated.finalize(final_score);          
        end
    end
 
  end

2) Builders

Criei também os construtores das listas, seguindo o padrão Builder. Com isso, as criação das listas deve ser delegada à sua classe Builder correspondente, que possui métodos que decoram a lista com um ou com todos os Decorator.

Abaixo, segue a classe UserBuilder (a MatchBuilder é muito semelhante e não será apresentada):

class UserListBuilder
 
    def user_list
      @user_list.load_list if @added_persistance
      @user_list
    end
 
    def initialize
      @user_list =  UserList.new()
      @added_persistance = false
    end
 
    def add_persistance(filename=$userListFileName)
      @user_list = ListPersistanceDecorator.new(@user_list,filename)
      @added_persistance=true
    end
 
    def add_logging
      @user_list = UserListLoggerDecorator.new(@user_list)
    end
 
    def add_thread_safety
      @user_list = ListThreadSafeDecorator.new(@user_list)
    end
 
    def add_all
      add_persistance
      add_logging
      add_thread_safety 
    end
 
  end

Note que, caso a lista tenha sido decorada com persistência, deve-se chamar o método "load_list" da classe ListPersistenceDecorator para carregar na lista de usuários aqueles já salvos anteriormente no arquivo de usuários.

3) Tratamento de Requisições

Criei um módulo chamado Readers que agrupa todas as classes responsáveis pela execução das requisições enviadas pela aplicação cliente. O padrão utilizado será YAML.
A principal destas classes é a RequestReader, que possui referência para a MatchList, UserList e User correntes. Ela possui o método read, que recebe um arquivo .yml e, a partir dele, cria o HashMap seguindo o padrão YAML. A seguir, este método verifica qual operação está sendo requisitada e chama, para cada caso, a classe do módulo Reader responsável por seu tratamento.
O código de RequestReader se encontra abaixo:

class RequestReader
 
  attr_reader :end
 
  def initialize(current_user,match_list=$match_list,user_list=$user_list)
    @matchList=match_list
    @userList=user_list
    @currentUser = current_user
    @end = false
  end
 
  def read(io_file=$inFile,io_out=$outFile)
      loop do
        string = io_file.gets
        break if string && string.include?("INICIO")
      end
      yaml = io_file.gets("\nFIM") #depois do INICIO, lê tudo até a palavra FIM
      yaml = yaml.chomp("FIM") #tira o FIM do final do string que foi lido do io
      requisicao = YAML.load(yaml) # aqui já temos o hash com a requisicao processada
 
      case requisicao['request']['operation']
        when "make_bet"
          RequestMakeBet.new(requisicao, @currentUser,io_out)
        when "create_user"
          RequestCreateUser.new(requisicao, @currentUser,io_out)
        when "end_connection"
          @end = RequestEndConnection.new(requisicao, @currentUser,io_out)
        when "authenticate_user"
          @currentUser=RequestAuthenticateUser.new(requisicao, @currentUser,io_out).user
        when "get_current_matches"
          RequestCurrentMatches.new(requisicao, @currentUser,io_out)
        when "get_finalized_matches"
          RequestFinalizedMatches.new(requisicao, @currentUser,io_out)
        when "get_match"
          RequestMatch.new(requisicao, @currentUser,io_out)
        when "get_bets"
          RequestBets.new(requisicao, @currentUser,io_out)
        else
          YamlMessagesPrinter.new(io_out_file).print_fail_message("Requisicao de operacao invalida!")
      end
    end
end

Todas as classes de tratamento de requisições herdam da classe Request abaixo, que possui apenas um construtor:

class Request 
    def initialize(hash_request,current_user,io_out,
        match_list = $match_list,user_list = $user_list)
      @match_list=match_list
      @user_list=user_list
      @hash_request=hash_request
      @current_user = current_user
      @io_out = io_out
    end
  end

Para mostrar como cada classe trata sua requisição correspondente, segue como exemplo o código da classe RequestAuthenticateUser, responsável pelo login de usuários

class RequestAuthenticateUser < Request
    attr_accessor :user
 
    def initialize(hash_request, current_user, io_out)
      super
      login=hash_request['request']['parameters']['username']
      password=hash_request['request']['parameters']['password']
      @user=@user_list.authenticate(login, password)
      if (!@user) 
        YamlMessagesPrinter.new(@io_out).print_fail_message("Usuario e Senha invalidos")
      else
        YamlMessagesPrinter.new(@io_out).print_success_message("Usuario e senha validos")
      end
 
    end
  end

Estas classes usam o módulo Printers para responder ao servidor, também seguindo o padrão YAML.
Para ilustrar esta dinâmica de requisições e respostas, realizei um teste abrangente. Diversos arquivos .yml de requisições foram enviadas ao RequestReader, que retorna uma resposta em um arquivod e output. O código deste teste, definido no arquivo operations.rb, segue abaixo:

def teste 
 
  ini() #função de criação de listas e arquivos para persistência definida em initialize.rb
  current_ser = nil
  rr=RequestReader.new(current_ser)
  rr.read(File.new("C:\\in0.yml","r"),$outFile)
  rr.read(File.new("C:\\in1.yml","r"),$outFile)
  rr.read(File.new("C:\\in2.yml","r"),$outFile)
  rr.read(File.new("C:\\in3.yml","r"),$outFile)
  rr.read(File.new("C:\\in4.yml","r"),$outFile)
  $match_list.find_by_name("Sao Paulo x Ipatinga").finalize("0x1")
  rr.read(File.new("C:\\in5.yml","r"),$outFile)
  rr.read(File.new("C:\\in6.yml","r"),$outFile)
  rr.read(File.new("C:\\in7.yml","r"),$outFile) 
 
end

O conteúdo dos arquivos de requisição foi o seguinte:

Arquivo in0.yml:
    INICIO
    ---
    request:  
         operation: get_finalized_matches
         parameters:  
    FIM

Arquivo in1. yml:
    INICIO
    ---
    request:  
         operation: create_user
        parameters:  
               username: lpv
               password: abc123
    FIM

Arquivo in2.yml:
    INICIO
    ---
    request:  
         operation: authenticate_user
         parameters:  
               username: lpv
               password: abc123
    FIM

Arquivo in3.yml:
    INICIO
    ---
    request:  
         operation: make_bet
         parameters:  
               match_name: Palmeiras x Corinthians
               score: 5x1 
    FIM

Arquivo in4.yml:
    INICIO
    ---
    request:  
         operation: make_bet
         parameters:  
               match_name: Sao Paulo x Ipatinga
               score: "0x1" 
    FIM

Arquivo in5.yml:
    INICIO
    ---
    request:  
         operation: get_bets
         parameters:
    FIM

Arquivo in6.yml:
    INICIO
    ---
    request:  
         operation: get_current_matches
         parameters:  
    FIM

Arquivo in7.yml:
    INICIO
    ---
    request:  
         operation: get_finalized_matches
         parameters:  
    FIM

O arquivo contendo os dados de usuários antes e depois de rodar o teste tinha o seguinte conteúdo:

ANTES:

--- 
- !ruby/object:User 
  crypted_pass: 1d51472e804c24bb8362ad1e392c968f21bbd4de
  salt: 0.280781403578436
  username: Joao
- !ruby/object:User 
  crypted_pass: 7f37d257633ddf6e52605b59c1c1ff324f5f4fd1
  salt: 0.363875180253079
  username: Pedro
- !ruby/object:User 
  crypted_pass: 323778e3c022d92c53d1f9ac6046d5d536958092
  salt: 0.827079382784685
  username: Togepi
- !ruby/object:User 
  crypted_pass: fbff1631417cc19e0e8266cbef47f3032ac151f5
  salt: 0.018108451927537
  username: Livia

DEPOIS:

--- 
- !ruby/object:User 
  crypted_pass: 1d51472e804c24bb8362ad1e392c968f21bbd4de
  salt: 0.280781403578436
  username: Joao
- !ruby/object:User 
  crypted_pass: 7f37d257633ddf6e52605b59c1c1ff324f5f4fd1
  salt: 0.363875180253079
  username: Pedro
- !ruby/object:User 
  crypted_pass: 323778e3c022d92c53d1f9ac6046d5d536958092
  salt: 0.827079382784685
  username: Togepi
- !ruby/object:User 
  crypted_pass: fbff1631417cc19e0e8266cbef47f3032ac151f5
  salt: 0.018108451927537
  username: Livia
- !ruby/object:User 
  crypted_pass: 16da99b0c529258b9bbb3982595e220bdbf48ccf
  salt: 0.324341624651437
  username: lpv

O arquivo contendo os dados dos jogos, antes e depois da execução do teste, seguem abaixo:

ANTES:

--- 
- !ruby/object:Match 
  end_time: 1220359800
  final_score: 4 x 1
  name: Vitoria x Serra F.C.
- &id001 !ruby/object:Match 
  end_time: 1228187800
  name: Palmeiras x Corinthians
- &id003 !ruby/object:Match 
  end_time: 1228187800
  name: Sao Paulo x Ipatinga

DEPOIS:

--- 
- !ruby/object:Match 
  bets: []

  end_time: 1220359800
  final_score: 4 x 1
  name: Vitoria x Serra F.C.
- &id001 !ruby/object:Match 
  bets: 
  - !ruby/object:Bet 
    holders: 
    - &id002 !ruby/object:User 
      crypted_pass: 16da99b0c529258b9bbb3982595e220bdbf48ccf
      salt: 0.324341624651437
      username: lpv
    match: *id001
    score: 5x1
  end_time: 1228187800
  name: Palmeiras x Corinthians
- &id003 !ruby/object:Match 
  bets: 
  - !ruby/object:Bet 
    holders: 
    - *id002
    match: *id003
    score: "0x1"
  end_time: 1228187800
  final_score: "0x1"
  name: Sao Paulo x Ipatinga

As respostas do servidor às requisições foram:

INICIO
--- 
:response: 
- :end_time: 02/09/2008 09:50
  :final_score: 4 x 1
  :name: Vitoria x Serra F.C.
  :bet_count: 0 bets
  :winners: no winners
  :finalized: finalized
FIM

INICIO
--- 
:response: 
  :status: success
  :message: Usuario inserido!
FIM
INICIO
--- 
:response: 
  :status: success
  :message: Usuario e senha validos
FIM

INICIO
--- 
:response: 
  :status: success
  :message: Aposta salva!
FIM
INICIO
--- 
:response: 
  :status: success
  :message: Aposta salva!
FIM

INICIO
--- 
:response: 
  :finalized_bets: 
  - :score_bet: "0x1"
    :match: Sao Paulo x Ipatinga
  :winning_bets: 
  - :match: Sao Paulo x Ipatinga
  :current_bets: 
  - :score_bet: 5x1
    :match: Palmeiras x Corinthians
FIM

INICIO
--- 
:response: 
- :end_time: 02/12/2008 01:16
  :name: Palmeiras x Corinthians
  :bet_count: 1 bets
FIM

INICIO
--- 
:response: 
- :end_time: 02/09/2008 09:50
  :final_score: 4 x 1
  :name: Vitoria x Serra F.C.
  :bet_count: 0 bets
  :winners: no winners
  :finalized: finalized
- :end_time: 02/12/2008 01:16
  :final_score: "0x1"
  :name: Sao Paulo x Ipatinga
  :bet_count: 1 bets
  :winners: lpv
  :finalized: finalized
FIM

E, finalmente, no arquivo de log foram inseridas as seguintes mensagens:

I, [2008-12-01T23:33:46.939000 #5380]  INFO -- : User lpv was added successfully.
I, [2008-12-01T23:33:46.939000 #5380]  INFO -- : User lpv logged in.
I, [2008-12-01T23:33:46.954000 #5380]  INFO -- : User 'lpv' made a 5x1 bet at 'Palmeiras x Corinthians'.
I, [2008-12-01T23:33:46.954000 #5380]  INFO -- : User 'lpv' made a 0x1 bet at 'Sao Paulo x Ipatinga'.
I, [2008-12-01T23:33:46.985000 #5380]  INFO -- : Match 'Sao Paulo x Ipatinga' was finalized with score 0x1

O bom funcionamento do programa pôde ser comprovado através destes testes. Os testes unitários fornecidos também comprovaram o sucesso do laboratório, como mostrado abaixo. Porém, não foram incorporados a eles testes dos builders, das classes do módulo Reader nem de algumas classes inseridas e apliadas no módulo Printers. Por isso a necessidade do teste realizado acima.

Teste

4) Servidos Multithread

Por último, implementei o servidor multithread no arquivo operations.tb da seguinte forma:

def operacao(io_in, io_out)
    ini()
    current_user=nil
    rr=RequestReader.new(current_user)
    while(!rr.end)
      rr.read(io_in,io_out)
    end
end
 
def server  
  server = TCPServer.new(4344)
  while ( io_socket = server.accept ) 
    Thread.new do
      io_socket.puts "Conetado..."
      operacao(io_socket,io_socket)
      io_socket.close
      io_socket.puts "Desconectado."
      $server = TCPServer.new(4555)
    end  
  end

Desta forma, cada Thread é responsável por uma conexão, tratada pelo método operação(), para o qual é passado o io_socket como input e output. Este método cria uma request_reader também com o input e o output dados, que, por sua vez, trata as requisições até que seja pedida o término da conexão, através da requisição "end_connection". Como as listas globais $match_list e $user_list criadas no método ini() são decoradas com ThreadSafety, todas as Threads criadas pelo servidor podem alterá-las e ver as alteraçãos das demais Threads de forma segura.

Conclusão

Este laboratório nos deu uma visão geral de uma pequena aplicação cliente criada na linguagem Ruby. Tivemos que compreender muitas linhas de código em Ruby e estudar as classes já prontas para finalizar a aplicação seguindo as especificações.
Nos deparamos com diversos erros e muitas dúvidas com relação ao código passado. A busca por soluções e explicações nos deu experiência de programação, porém consumiu bastante tempo.

O projeto NetBeans da aplicação se encontra AQUI

Add a New Comment
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License