Sunteți pe pagina 1din 10

1

Castor JDO
Persistência Fácil de Objetos em Java
HERVAL FREIRE DE A. JÚNIOR
As camadas de persistência facilitam o desenvolvimento de aplicações com acesso
a banco de dados. Aprenda a usar a API Castor através de um exemplo simples.

A biblioteca Castor é um framework de databinding em Java: em termos simples, ela permite


transformação descomplicada entre objetos Java, XML, diretórios LDAP e bancos de dados
relacionais com expressiva facilidade.
Atualmente na versão 0.9x, a biblioteca Castor vem sendo utilizada em um grande número de
projetos de sistemas J2EE: O servidor de aplicações open-source JBoss, por exemplo, utiliza-se da
API de persistência do Castor na sua implementação de CMP (Container Managed Persistence) de
EJBs.

A API JDO (Java Data Objects)


Um dos pacotes que compõem a biblioteca Castor é o pacote org.exolab.jdo – um conjunto de
classes com a função de gerenciar a persistência de objetos Java em bancos de dados relacionais.
Através das classes deste pacote, o programador pode definir mapeamentos entre suas classes Java e
tabelas em bancos de dados relacionais, de forma bastante semelhante ao mapeamento feito para
EntityBeans, no contexto dos EJBs.
Para realizar a ligação (binding) entre as classes de Java e tabelas do banco de dados, o Castor
utiliza-se de arquivos de configuração no formato XML: estes descritores associam os atributos e
relacionamentos das classes a campos e referências nas tabelas do banco. Esta associação, além de
informar ao Castor como realizar a persistência dos atributos, também servirá para abstrair a
camada de banco de dados na hora de realizar consultas, como veremos no decorrer deste tutorial.
A inserção, atualização e recuperação de objetos persistentes é um processo simples, realizado em
apenas uma linha de código – um grande atrativo para aqueles que se acostumaram a realizar
operações em banco de dados utilizando longas instruções SQL inseridas no meio do código das
classes ou em objetos de acesso a dados (DAOs).
Além de garantir esta facilidade de inclusão e alteração de dados, a API JDO fornece também
uma linguagem de consulta utilizada para manipulação de objetos: trata-se de um sobconjunto da
linguagem de consulta para bancos de dados orientados a objetos OQL (Object Query Language),
de sintaxe muito similar à SQL. Como o Castor trabalha com persistência de dados em bancos de
dados relacionais, a biblioteca trata de mapear consultas OQL em SQL padrão, de forma que o
programador não precisa mais se preocupar em descrever joins complexos entre tabelas, referência
explícita a chaves de tabelas, etc.

Nota: Apesar do nome, o pacote JDO do Castor não implementa a especificação JDO da Sun em
sua totalidade. É importante lembrar que muitos recursos, especialmente no que diz respeito à
linguagem de consulta OQL, ainda estão em desenvolvimento no projeto.

Aplicação Modelo: Sistema de Controle de Funcionários


Cenário da aplicação
Para exemplificar alguns dos recursos do pacote JDO, criaremos uma pequena aplicação. As
classes utilizadas no exemplo seguem a especificação mostrada na figura 1. É válido lembrar que o
objetivo deste esquema de classes não é ser fiel a nenhuma realidade nem representa a melhor
estratégia de modelagem de classes para a situação dada: ele apenas demonstra os tipos de
2

relacionamentos comuns na modelagem Orientada a Objetos - relacionamentos 1-para-muitos, 1-


para-1 e muitos-para-muitos e relacionamentos simples. Além disso, a presença de atributos de
diversos tipos serve para dar uma idéia da capacidade de conversão de tipos da API de persistência
do pacote Castor JDO.

Funcionario
+gerent e
cod igo : int
nome : Stri ng Set or
foto : byte [] 0..* 0..* codigo : int
salario : double nome : String
gratificacao : double telefone : String
nascimento : Da te +operario
tele fone : String
0..* 0..1

Figura 1. Esquema UML da aplicação modelo

O primeiro passo para que possamos trabalhar com a persistência das classes no banco de dados é
criar um esquema físico de armazenamento. Para que a persistência seja possível, o esquema
mostrado na figura 2 foi criado no banco de dados. Para os testes, foi utilizada uma base de dados
MySQL – um dos SGBDs suportados pelo Castor.

tb_func tb_setor

PK codigo PK codigo

foto nome
nasc fone
name
salario
FK1 setor
fone
gratificacao

tb_gerente_setor
PK,FK1 cod_gerente
PK,FK2 cod_setor

Figura 2. Esquema de Banco de Dados da aplicação de exemplo

Classes utilizadas no exemplo


O código-fonte das classes utilizadas neste exemplo é comum a qualquer desenvolvedor Java e
pode ser visto na listagem 1. Para cada atributo privado, supõe-se a existência de métodos públicos
get/set, segundo o padrão JavaBean.

Listagem 1: Código-fonte das classes da aplicação modelo


Package example;

class Setor {
private String nome;
private String telefone;
private Computador computadores;
3

private int codigo;


private java.util.Vector funcionarios;
private java.util.Vector gerentes;
}

class Funcionario {

private String nome;


private java.util.Date nascimento;
private double salario;
private String telefone;
private byte[] foto;
private Setor setor;
private TimeFutebol[] times;
private int codigo;
private double gratificacao;
}

Modelagem do BD
O script da listagem 2 gera a as tabelas necessárias para os testes.

Listagem 2: Script de geração de tabelas no banco de dados


create database empresaBD;
use empresaBD;

create table tb_func (


codigo int not null primary key auto_increment,
foto blob,
nasc date,
name varchar(50),
salario double,
setor int not null references tb_setor,
fone varchar(12),
gratificacao double
);

create table tb_setor (


codigo int not null primary key auto_increment,
nome varchar(50),
fone varchar(12)
);

create table tb_gerente_setor (


cod_gerente int not null references tb_func,
cod_setor int not null references tb_setor,
primary key(cod_gerente, cod_setor)
);

Uma vez que temos toda esta estrutura disponível, podemos partir para o primeiro passo do
processo de configuração do ambiente Castor.

Passo 1: conectando ao banco de dados


Antes que qualquer dado possa ser gravado ou recuperado de uma base de dados, é necessário que
o desenvolvedor descreva a ligação (binding) entre as tabelas e objetos do sistema. Além de
relacionar classes com tabelas, também é necessário especificar o banco de dados utilizado, driver,
senha para conexão e outros detalhes.
Tais configurações são feitas mediante a criação de um descritor XML que deve ser carregado na
aplicação para a localização correta dos dados. A listagem 3 mostra os passos necessários para a
carga de um descritor e a preparação para utilização do banco de dados com o pacote JDO.
Listagem 3: Inicializando uma configuração de banco de dados
Database db;
JDO jdo = new JDO();
jdo.setDatabaseName("empresa_db");
jdo.setConfiguration("database.xml");
4

db = jdo.getDatabase();

db.begin(); // inicializa transação


// faça alguma coisa aqui
db.commit(); // encerra transação
db.close(); // fecha conexão com o banco

Neste código, informamos ao objeto JDO que as configurações de dados encontram-se no arquivo
“database.xml”. Ao executar o método getDatabase(), o objeto JDO procurará a configuração
relativa ao banco de dados de nome “empresa_db”. Após recuperar o objeto Database, o
programador precisa iniciar uma transação para que possa trabalhar os objetos e então executar
quaisquer operações livremente. A persistência dos dados somente é realizada na chamada ao
método commit(). Caso seja necessário, existe também um método “rollback()” na classe Database,
o que permite que a transação seja desfeita em caso de problemas no fluxo de execução.

Conectando a um banco de dados em aplicações J2EE


A API CASTOR dá suporte, também, a conexões com bancos em servidores de aplicação J2EE utilizando
JNDI. Após disponibilizar o objeto JDO com um nome JNDI, o código para recuperar uma conexão de
acesso ao banco em uma aplicação J2EE típica é simples, como mostra o trecho de código abaixo:

InitialContext ctx;
UserTransaction ut;
Database db;

ctx = new InitialContext();


db = (Database) ctx.lookup(“java:comp/env/jdo/empresa_db”);

// faça alguma coisa aqui

db.close();

Como na maioria dos casos a transação é gerenciada pelo container, o programador não precisa especificar
seu início e fim explicitamente. Alguns containers open-source vem utilizando o Castor desta forma, como
framework de persistência de EJBs.

No nosso exemplo específico, para que o banco de dados possa ser aberto e utilizado, o descritor
database.xml tem o conteúdo mostrado na listagem 4:
Listagem 4. Arquivo database.xml
<!DOCTYPE databases PUBLIC "-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN" "jdo-conf.dtd">
<database name="empresa_db" engine="mysql">
<driver class-name="org.gjt.mm.mysql.Driver" url="jdbc:mysql://localhost/empresaBD">
<param name="user" value="herval" />
<param name="password" value="herval" />
</driver>
<mapping href="tabelas.xml" />
</database>

A interpretação deste arquivo é bastante simples e direta. O exemplo demonstra as configurações


para um banco de dados de nome “empresa_db”, explicitando também a base de dados em que
estamos trabalhando – neste caso, um banco de dados MySQL – e determina o driver de acesso e os
parâmetros necessários para a conexão.
Por último, o elemento <mapping>, que pode aparecer várias vezes, define os mapeamentos entre
classes e tabelas no sistema. No nosso exemplo, a única instância da elemento <mapping> faz
referência ao arquivo externo “tabelas.xml”. Este arquivo será explorado mais a diante, no decorrer
do exemplo, e servirá para descrever a ligação (binding) entre tabelas e classes.

Tipos de bancos de dados suportados pelo Castor


No arquivo “database.xml” do nosso exemplo, vemos que o atributo “engine” recebeu o valor “mysql”. Este
valor determina como o Castor gerará código SQL para acesso às tabelas do sistema, assim como
5

determina a existência ou não de alguns recursos, como veremos no decorrer do tutorial. O atributo “engine”
pode receber, atualmente, os valores mostrados na tabela 1, a seguir

Banco de dados Atributo engine


Base de dados JDBC genérica generic
Oracle 7 ou 8 oracle
Sybase 11 sybase
SQL Server sqlserver
DB/2 db2
Informix informix
PostgreSQL 7.1 postgresql
InstantDB instantdb
Interbase interbase
SAP DB sapdb
MySQL mysql
Tabela 1: Bancos de dados suportadas pelo Castor e valores do atributo “engine”

Passo 2: Mapeando objetos em tabelas


Para que a persistência e recuperação de dados sejam possíveis, precisamos estabelecer algum
tipo de relacionamento entre as classes do sistema e as tabelas do banco. A estrutura do descritor de
relacionamentos merece atenção especial, neste nosso pequeno tutorial. Antes de iniciarmos o
exemplo real proposto, tomaremos algum tempo para conhecer a fundo esta estrutura. O arquivo
descritor de mapeamentos segue a estrutura mostrada na listagem 5 a seguir: para cada classe a ser
mapeada, um elemento <class> deve ser escrito e configurado adequadamente.
Listagem 5. Esqueleto de um descritor de mapeamentos
<!DOCTYPE databases PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN" "mapping.dtd">
<mapping>
<class>

</class>

<class>

</class>

</mapping>

O elemento <class> tem sua estrutura mostrada na listagem 6. Para descrever e especificar cada
atributo da classe específica, o elemento <class> comporta um número arbitrário de elementos
<field>, um para cada atributo mapeado para um campo na tabela.

Listagem 6. DTD do elemento XML <class>


<!ELEMENT class ( description?, cache-type?, map-to?, field+ )>
<!ATTLIST class
name ID #REQUIRED
extends IDREF #IMPLIED
depends IDREF #IMPLIED
auto-complete ( true |false ) "false"
identity CDATA #IMPLIED
access ( read-only | shared | exclusive | db-locked ) "shared"
key-generator IDREF #IMPLIED >

Explicando cada um dos atributos do descritor XML de uma classe, temos:


name – o nome completo da classe mapeada, incluindo pacote.
extends – nome da classe-pai, em caso de herança. Apenas aplicado se a classe-pai também tiver
um descritor de mapeamento relacionado com ela.
6

depends – utilizado para descrever classes dependentes, no caso de relacionamentos de


agregação.
auto-complete – em caso de “true”, o Castor localiza atributos na classe que não possuem
mapeamento e tenta persisti-los automaticamente.
identity – define qual campo é chave primária de identificação de objetos da classe. Em caso de
chaves compostas, o nome dos atributos é separado com espaços.
access – determina a forma de acesso a objetos persistentes desta classe.
key-generator – define qual gerador de chave será utilizado, caso um seja adotado. Os geradores
de chave permitem ao Castor gerar o código de identificação no objeto utilizando recursos
proprietários de cada banco, como Stored Procedures, geradores de OIDs/UUIDs, algoritmos MAX
e High/Low, utilizando seqüências (Sequences) ou campos auto-incrementais. Por tratar-se de
configurações avançadas e de certa forma simples de entender em cada caso, omitiremos
explicações mais detalhadas sobre este atributo no decorrer deste tutorial.

Os filhos do elemento <class> são descritos a seguir:


description – uma descrição textual da classe e do mapeamento
cache-type – define se será utilizado ou não o armazenamento em memória dos objetos
armazenados do banco
map-to – elemento que define “para onde” a classe deve ser mapeada. Como estamos estudando
apenas persistência para bancos de dados relacionais, esta tag terá sempre o formato <map-to
table=“nome_da_tabela” />. Apesar de não estudado neste tutorial, é possível definir também
mapeamentos para XML e LDAP neste atributo.
field – os elementos “field” são os descritores dos atributos individuais da classe a ser mapeada. A
estrutura de uma tag <field> é mostrada na listagem 7.

Listagem 7. DTD do elemento XML <field>


<!ELEMENT field ( description?, sql?, xml?, ldap? )>
<!ATTLIST field
name NMTOKEN #REQUIRED
type NMTOKEN #IMPLIED
required ( true | false ) "false"
direct ( true | false ) "false"
lazy ( true | false ) "false"
transient ( true | false ) "false"
get-method NMTOKEN #IMPLIED
set-method NMTOKEN #IMPLIED
create-method NMTOKEN #IMPLIED
collection ( array | vector | hashtable | collection | set | map ) #IMPLIED>

Uma breve explicação dos atributos do elemento <field> segue:


name – nome do atributo da classe mapeada. Caso o atributo “direct” seja omitido ou definido
como “false”, o objeto será acessado através dos métodos get/set relacionados a ele: Um atributo
“codigo” que seja acessado através dos métodos getCode() e setCode(), por exemplo, e defina
direct=“false” deve mapear o atributo name=“code”. Caso direct esteja definido como “true”, o
atributo deve ser name=“codigo”. Neste caso, o acesso através dos métodos get/set não é feito. É
importante lembrar que o atributo deve ter visibilidade pública (public), para tal acesso.
type – tipo do atributo em Java. Os tipos de objetos aceitos para o mapeamento incluem qualquer
tipo de objeto Java. Para facilitar a utilização de alguns tipos mais comuns, o pacote Castor define
um pequeno grupo de nomes curtos, mostrado na Tabela 2. Mesmo no caso de coleções (arrays,
vectors, hashtables), este atributo deve ser configurado com o tipo dos elementos da coleção. Para
definir que um dado atributo é na realidade uma coleção, o atributo “collection” deve ser utilizado.
required – indica se o atributo é requerido (não-nulo) ou não
lazy – atribuir “true” a este atributo faz com que os objetos só sejam carregados quando
requerido. Para tal, o atributo “collection” deve existir.
transient – se indicado como “true”, indica que este campo deve ser ignorado durante a
persistência/recuperação
7

get-method – determina o nome do método getter do atributo, caso a assinatura não siga o padrão
da especificação JavaBean (public <tipo> get<Atributo>())
set-method - determina o nome do método setter do atributo, caso a assinatura não siga o padrão
da especificação JavaBean (public void set<Atributo>(<tipo> valor))
collection – determina o tipo, no caso de um atributo que seja uma coleção. As coleções possíveis
são arrays, Vectors, Hashtables, ArrayLists, HashSets e HashMaps do tipo definido pelo atributo
“type”. O valor recebido por este atributo para cada caso é, respectivamente, “array”, “vector”,
“hashtable”, “collection”, “set” e “map”.

Tabela 2: Nomes curtos para mapeamento de atributos Java


A API Castor define um conjunto de nomes curtos para facilitar a especificação do tipo de atributos em um
mapeamento. Os nomes aceitos são enumerados na tabela a seguir:
Nome Curto Classe Java
other java.lang.Object
string java.lang.String
integer java.lang.Integer.TYPE (int)
long java.lang.Long.TYPE (long)
boolean java.lang.Boolean.TYPE (boolean)
double java.lang.Double.TYPE (double)
float java.lang.Float.TYPE (float)
big-decimal java.math.BigDecimal
byte java.lang.Byte.TYPE (byte)
date java.util.Date
short java.lang.Short.TYPE (short)
char java.lang.Character.TYPE (char)
bytes byte[]
chars char[]
strings String[]
locale java.util.Locale
stream java.io.InputStream

Dos elementos internos da tag <field>, apenas o elemento <sql> é interessante para nosso estudo
atual. A sua estrutura é mostrada na listagem 8.
Listagem 8. DTD do elemento XML <sql>
<!ELEMENT sql EMPTY>
<!ATTLIST sql
name NMTOKEN #REQUIRED
type NMTOKEN #IMPLIED
many-key NMTOKEN #IMPLIED
many-table NMTOKEN #IMPLIED
read-only ( true | false ) "false"
dirty ( check | ignore ) “check” >

Os atributos do elemento <sql> têm o seguinte significado:


name – o nome da coluna no banco de dados
type – o tipo do atributo no banco de dados – um nome literal dentre os tipos listados na tabela 3.
No caso de relacionamentos com outras classes, o atributo type deve ser omitido. Desta forma, o
Castor buscará pela chave primária na tabela relacionada para realizar a persistência do dado. Para
atributos do tipo char, é permitido um mecanismo de mapeamento especial. Supondo que se queira
mapear um atributo booleano para um banco de dados de forma que sejam armazenados os
caracteres “S” para representrar “true” e “N” para não, podemos utilizar o seguinte exemplo de
configuração:

Listagem 9. Exemplo de mapeamento utilizando máscara (valor booleano).


<field name=“meuAtributo” type=“boolean”>
8

<sql name=“atributoAtivo” type=“char[NS]”/>


</field>

Datas também podem ser mascaradas para armazenamento no banco de dados, em casos onde o
formato de armazenamento do banco seja diferente do formato fornecido pelo método toString() da
classe Date. O exemplo da listagem 10 ilustra um exemplo de formatação para armazenar datas no
formato “dd/mm/yyyy”

Listagem 10. Exemplo de mapeamento utilizando máscara (data).


<field name=“minhaData” type=“date”>
<sql name=“atributoData” type=“char[dd/MM/yyyy]”/>
</field>

many-key – atributo utilizado para relacionamentos 1-para-n ou n-para-n: indica o nome da chave
estrangeira que identifica unicamente objetos do tipo dado por “type” que se relacionam com a
classe que contém esta definição.
many-table – nome da tabela intermediária, no caso de relacionamentos n-para-n normalizados
que utilizam uma tabela para relacionar os objetos.

Tabela 3: Tipos SQL e tipos Java relacionados


Para permitir a armazenarem e recuperação correta de atributos no banco de dados, o Castor permite a
utilização dos seguintes tipos sql como valores possíveis para o parâmetro “type” do mapeamento de
campos:
Tipo SQL Tipo Java
bigint java.lang.Long
binary byte[]
bit java.lang.Boolean
blob java.io.InputStream
char java.lang.String
clob java.sql.Clob
date java.sql.Date
decimal java.math.BigDecimal
double java.lang.Double
float java.lang.Double
integer java.lang.Integer
longvarbinary java.lang.String
numeric java.math.BigDecimal
real java.lang.Float
smallint java.lang.Short
time java.sql.Time
timestamp java.sql.Timestamp
tinyint java.lang.Byte
varbinary byte[]
varchar java.lang.String

Mapeando a classe Funcionario


Terminadas todas estas explicações a respeito da estrutura do mapeamento de uma classe, é hora
de prosseguirmos com nosso exemplo. Mapeamos a classe Funcionario à tabela tb_func utilizando
o descritor de classe mostrado na listagem 11.
Listagem 11. Descritor de ligação entre a classe Funcionario e a tabela tb_func
<class name="example.Funcionario" identity="codigo" key-generator="IDENTITY">
<map-to table="tb_func" />
9

<field name="codigo" type="integer">


<sql name="codigo" type="integer"/>
</field>

<field name="foto" type="bytes">


<sql name="foto" type="blob"/>
</field>

<field name="nascimento" type="date">


<sql name="nasc" type="char[yyyy-MM-dd]"/>
</field>

<field name="nome" type="string">


<sql name="name" type="char"/>
</field>

<field name="salario" type="double">


<sql name="salario" type="double"/>
</field>

<field name="setor" type="example.Setor">


<sql name="setor" />
</field>

<field name="setoresGerenciados" type="example.Setor" collection="collection">


<sql name="cod_setor" many-key="cod_gerente" many-table="tb_gerente_setor" />
</field>

<field name="telefone" type="string">


<sql name="fone" type="char"/>
</field>
</class>

Podemos identificar, neste nosso caso, exemplos de aplicação de formatadores para data (atributo
“nascimento”), um relacionamento um-para-muitos (atributo “setor”), um relacionamento muitos-
para-muitos (atributos “setoresGrenciados”) e diversos outros atributos mapeados para tipos
diversos no banco de dados SQL. Dentre os atributos mapeados, o mais incomum é o nosso
relacionamento n-para-n com a classe Setor (atributo “setoresGerenciados”): este especifica o
atributo “many-table” com o nome da tabela que relaciona gerentes a setores e o atributo “name” da
tag <sql> como o campo chave que identifica setores na tabela “tb_gerente_setor”.

O mapeamento da classe Setor é ainda mais simples e pode ser visto na listagem 12.
Listagem 12. Mapeamento da classe Setor
<class name="example.Setor" identity="codigo" key-generator="IDENTITY">
<map-to table="tb_setor" />

<field name="codigo" type="integer">


<sql name="codigo" type="integer" />
</field>

<field name="gerentes" type="example.Funcionario" collection="vector">


<sql name="cod_gerente" many-key="cod_setor" many-table="tb_gerente_setor" />
</field>

<field name="funcionarios" type="example.Funcionario" collection="vector">


<sql many-key="setor" />
</field>

<field name="nome" type="string">


<sql name="nome" type="char"/>
</field>

<field name="telefone" type="string">


<sql name="fone" type="char"/>
</field>
</class>

Após escrever estes mapeamentos, podemos finalmente começar a trabalhar com as classes em
Java normalmente, sem preocupações maiores com utilização de SQL para persistência e
10

recuperação de objetos. Graças à camada de persistência, todo o código de acesso ao banco que
precise ser gerado será criado e executado automaticamente, como veremos nos exemplos a seguir.

Passo 3: Brincando de JavaBeans


Após todo o trabalho inicial de mapeamento de objetos para tabelas, a manipulação de objetos é
transparente. O código demonstrado na listagem 13 a seguir demonstra diversas operações
realizadas sobre um conjunto de objetos. Ao final, teremos objetos armazenados nas tabelas de
nosso banco de dados sem ter escrito nenhuma linha de código utilizando JDBC.
Listagem 13. Exemplos

Outros recursos da API de persistência


<<interface Persistence>>
<<OQL>>
<<DIRTY CHECKING>>

Conclusões
A utilização de uma camada de persistência para interação com o banco de dados trás uma série
de vantagens, especialmente visíveis no desenvolvimento de aplicações de maior porte.
A remoção de acessos a banco de dados utilizando SQL inserido no meio do código da aplicação,
a facilidade de modificação dos mapeamentos em tempo de deployment e a possibilidade de
configurar detalhes de armazenamento dos dados em um arquivo de configuração são apenas alguns
exemplos dos benefícios apresentados pelo Castor e em outras APIs de persistência de objetos. Sem
dúvida, a adoção de camadas de persistência como um elemento arquitetural durante o
desenvolvimento de aplicações garante produtividade, qualidade e confiabilidade de código.

Referências
http://www.exolab.org – site oficial do projeto Castor
http://Jakarta.apache.org/ojb – site do projeto OJB, uma camada de persistência compatível com a
especificação ODMG
http://www.AmbySoft.com/ - artigos sobre construção de camadas de persistência de objetos
http://hibernate.sourceforge.net - API Hibernate de persistência para bancos de dados relacionais

Herval Freire de A. Júnior (herval@users.sourceforge.net) é graduado em Telemática pelo CEFET-PB,


programador e desenvolvedor de componentes web certificado pela Sun. Atualmente, trabalha como
programador Java na Politec - João Pessoa.

S-ar putea să vă placă și