CDI, do básico ao
avançado
Thursday, September 5, 13
Alberto Souza
• @alberto_souza
• github.com/asouza
Thursday, September 5, 13
Usar ou criar?
@ManagedBean
public class ProdutosBean {
	 @PostConstruct
	 public void carregaProdutos(){ 	
	 	 ProdutoDAO...
Usar!
@ManagedBean
public class ProdutosBean {
	
	 private ProdutoDAO produtos;
	
	 private ProdutosBean(ProdutoDAO produt...
Injeção de
Dependências
• JAVAEE 6/7
• CDI 1.0/1.1
• WELD 2
Thursday, September 5, 13
Uso fácil
@ManagedBean
public class ProdutosBean {
	
	 @Inject
	 private ProdutoDAO produtos;
	
	 @PostConstruct
	 public ...
Injeção de
Dependências
• E como configura?
Thursday, September 5, 13
Injeção de
Dependências
• Não precisa mais
Thursday, September 5, 13
Outro exemplo
class ProdutoDAO{
	 @Inject
	 private EntityManager em;
	
	 public List<Produto> lista(){
	 	 return em.crea...
Criação de
EntityManager
	 	 EntityManagerFactory emf = Persistence.
	 	 	 	 createEntityManagerFactory("loja");
	 	 Entit...
Tem que ensinar
class EntityManagerProducer{
	
private EntityManagerFactory emf =
Persistence.createEntityManagerFactory("...
Escopo?
class EntityManagerProducer{
	
private EntityManagerFactory emf =
Persistence.createEntityManagerFactory("loja");
...
Escopo?
class EntityManagerProducer{
	
private EntityManagerFactory emf =
Persistence.createEntityManagerFactory("loja");
...
Qual é o remetente?
public class FinalizadorDeCompra{
	
	 private String remetente;
	
	 public void envia(){
	 	 mailer.se...
Trocar a injeção de
String?
public class FinalizadorDeCompra{
	
	 @Inject
	 private String remetente;
	
	 public void envi...
Só quero para
determinada String
public class FinalizadorDeCompra{
	
	 @Inject @RemetenteCompra
	 private String remetente...
Qualifier
//outras annotations chatas
@Qualifier
@interface RemetenteCompra{
	
}
Thursday, September 5, 13
Qualifier
@ApplicationScoped
class RemetentesProducer {
	
	 @Produces
	 @RemetenteCompra
	 public String remetenteCompra(){...
Indo além
• VRaptor CDI e integração com o
Container
Thursday, September 5, 13
Injeção de coisas do
container
• Quero usar um EJB, EntityManager...
Thursday, September 5, 13
Injeção de coisas do
container
@Component
public class ProdutoDAO {
@Inject
	 private final EntityManager entityManager;
	...
Injeção de coisas do
container
@Stateless
@Component
public class ProdutoDAO {
@Inject
	 private final EntityManager entit...
Injeção de coisas do
container
@Resource
public class ProdutosController {
//@EJB
	 @Inject
	 private ProdutoDAO produtoDA...
EJB?
• Por sinal o DAO é um EJB porque?
Thursday, September 5, 13
EJB?
@Component
public class ProdutoDAO {
@Inject
	 private final EntityManager entityManager;
@Transactional
	 public voi...
Exemplo de uso
• Coloco no interceptor e tá tudo certo!
Thursday, September 5, 13
Componente padrão
@RequestScoped
public class DefaultPathResolver implements PathResolver {
	
	 public String pathFor(Reso...
Componente
customizado
class CustomPathResolver implements PathResolver{
	 public String pathFor(ResourceMethod method) {
...
Alternative
@Alternative
class CustomPathResolver implements PathResolver{
	 public String pathFor(ResourceMethod method) ...
Registra no beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<alternatives>
<class>br.com.caelum.vraptor.view.Cust...
ilhazinha
• Alternative habilitado só no bean archive
Thursday, September 5, 13
ilhazinha
• VRaptor não enxergava
Thursday, September 5, 13
Prioridade
@Alternative
@Priority(Interceptor.Priority.APPLICATION)
class CustomPathResolver implements PathResolver{
	 pu...
Prioridade
• Podemos sempre sobreescrever!
Thursday, September 5, 13
Servidor já injeta
Request
• Request doVRaptor é modificado
Thursday, September 5, 13
Aproveita o que já
existe
public class VRaptorRequest implements
MutableRequest,HttpServletRequest {
	 private final Hasht...
Aproveitamos o
existente
@Decorator
public class VRaptorRequest implements MutableRequest,HttpServletRequest
{
	 @Delegate...
Configuramos
<decorators>
<class>br.com.caelum.vraptor.http.VRaptorRequest</class>
</decorators>
Thursday, September 5, 13
Priority
• Pode usar Priority também!
Thursday, September 5, 13
Injetar Requests
public class DefaultFormatResolver implements FormatResolver {
	 private final HttpServletRequest request...
Trocar o request o
tempo todo
@Inject
	 private CDIHttpServletRequestFactory requestFactory;
	 @Inject
	 private CDIHttpSe...
Notifica o povo
	 @Inject
	 private Event<HttpServletRequest> eventosDeRequest;
	
	 public <T> T provideForRequest(RequestI...
Quero ser notificado
public class CDIHttpServletRequestFactory implements
ComponentFactory<HttpServletRequest>{
	
	 private...
VRaptor já tem
@RequestScoped
public @interface RequestScoped {
}
Thursday, September 5, 13
Eu sou você!
@Stereotype
@javax.enterprise.context.RequestScoped
@Named
public @interface RequestScoped {
}
Thursday, Sept...
VRaptor não usa
@Inject
@Resource
public class ProdutoDAO {
	 private final EntityManager entityManager;
	 public CDIResou...
Oxi, sem @Inject?
• Cadê o @Inject?
Thursday, September 5, 13
Pedimos a ele para
colocar
public class AddInjectToConstructorExtension implements Extension{
	 public void processAnnotat...
VRaptor já tem
Factories
@Component
public class CDIComponent implements ComponentFactory {
	 private final MyRequestCompo...
Oxi, e o @Produces?
• E o @Produces?
Thursday, September 5, 13
@Produces em cima
do método
public class ComponentFactoryExtension implements Extension{
	 public void addProduces(@Observ...
Configura extensão
• META-INF/services/
javax.enterprise.inject.spi.Extension
Thursday, September 5, 13
Configura extensão
br.com.caelum.vraptor.ioc.cdi.extensions.AddInjectToConstructorExtensi
on
br.com.caelum.vraptor.ioc.cdi....
E para testar?
• E para testar?
Thursday, September 5, 13
Arquillian
@RunWith(Arquillian.class)
public class CDIProviderRegisteringComponentsTest {
	
	 @Deployment
public static Ja...
Arquillian
public static JavaArchive createDeployment() {
return ShrinkWrap.create(JavaArchive.class)
.addPackage("com.tho...
Problemas
• Falta de controle sobre o container
Thursday, September 5, 13
DeltaSpike
	 	 cdiContainer = CdiContainerLoader.getCdiContainer();
	 	 cdiContainer.boot();
Thursday, September 5, 13
Controle do escopo
	 public void start(Class<? extends Annotation> scope) {
	 	 cdiContainer.getContextControl().startCont...
Controle do escopo
	 	 	 	 start(RequestScoped.class);
	 	 	 	 start(SessionScoped.class);
	 	 	 	 	 //logica
	 	 	 	 stop...
Últimos detalhes
• Funcionalidade com muitos passos
Thursday, September 5, 13
Funcionalidade com
vários passos
@Controller
public class PagamentoController implements Serializable{
private Informacoes...
Funcionalidade com
vários passos
@Controller
public class PagamentoController implements Serializable{
//resto que não cou...
Como implementar?
• 2 Maneiras?
Thursday, September 5, 13
Exemplo de
implementação
• Stateless?
Thursday, September 5, 13
Outro exemplo
• Stateful?
Thursday, September 5, 13
Meio termo
• Mangue moderado!
Thursday, September 5, 13
Conversation
@Controller
@ConversationScoped
public class PagamentoController implements Serializable{
@Inject
private Con...
Conversation
@Controller
@ConversationScoped
public class PagamentoController implements Serializable{
@Inject
private Con...
Conversation
@Controller
@ConversationScoped
public class PagamentoController implements Serializable{
	 @Inject
	 public ...
E esse result?
• O Result tem que ser passivavel :(
Thursday, September 5, 13
Não mais :)
@Controller
@ConversationScoped
public class PagamentoController implements Serializable{
	 @Inject
	 public P...
Valeu!
@alberto_souza
github.com/asouza
Thursday, September 5, 13
Upcoming SlideShare
Loading in...5
×

CDI do básico ao avançado

2,273

Published on

0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,273
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
68
Comments
0
Likes
4
Embeds 0
No embeds

No notes for slide

CDI do básico ao avançado

  1. 1. CDI, do básico ao avançado Thursday, September 5, 13
  2. 2. Alberto Souza • @alberto_souza • github.com/asouza Thursday, September 5, 13
  3. 3. Usar ou criar? @ManagedBean public class ProdutosBean { @PostConstruct public void carregaProdutos(){ ProdutoDAO produtos = new ProdutoDAO(); produtos.setConnection(ConnectionFactory.createConnection()); this.lista = dao.lista(); } } Thursday, September 5, 13
  4. 4. Usar! @ManagedBean public class ProdutosBean { private ProdutoDAO produtos; private ProdutosBean(ProdutoDAO produtos){ this.produtos = produtos; } @PostConstruct public void carregaProdutos(){ this.lista = dao.lista(); } } Thursday, September 5, 13
  5. 5. Injeção de Dependências • JAVAEE 6/7 • CDI 1.0/1.1 • WELD 2 Thursday, September 5, 13
  6. 6. Uso fácil @ManagedBean public class ProdutosBean { @Inject private ProdutoDAO produtos; @PostConstruct public void carregaProdutos(){ return dao.lista(); } } Thursday, September 5, 13
  7. 7. Injeção de Dependências • E como configura? Thursday, September 5, 13
  8. 8. Injeção de Dependências • Não precisa mais Thursday, September 5, 13
  9. 9. Outro exemplo class ProdutoDAO{ @Inject private EntityManager em; public List<Produto> lista(){ return em.createQuery("select p from Produto p"); } } Thursday, September 5, 13
  10. 10. Criação de EntityManager EntityManagerFactory emf = Persistence. createEntityManagerFactory("loja"); EntityManager em = emf.createEntityManager(); Thursday, September 5, 13
  11. 11. Tem que ensinar class EntityManagerProducer{ private EntityManagerFactory emf = Persistence.createEntityManagerFactory("loja"); @Produces public EntityManager create(){ return factory.createEntityManager(); } } Thursday, September 5, 13
  12. 12. Escopo? class EntityManagerProducer{ private EntityManagerFactory emf = Persistence.createEntityManagerFactory("loja"); @Produces @RequestScoped public EntityManager create(){ return factory.createEntityManager(); } } Thursday, September 5, 13
  13. 13. Escopo? class EntityManagerProducer{ private EntityManagerFactory emf = Persistence.createEntityManagerFactory("loja"); //outro método aqui... public void close(@Disposes EntityManager entityManager){ entityManager.close(); } } Thursday, September 5, 13
  14. 14. Qual é o remetente? public class FinalizadorDeCompra{ private String remetente; public void envia(){ mailer.send(remente,...); } } Thursday, September 5, 13
  15. 15. Trocar a injeção de String? public class FinalizadorDeCompra{ @Inject private String remetente; public void envia(){ mailer.send(remente,...); } } Thursday, September 5, 13
  16. 16. Só quero para determinada String public class FinalizadorDeCompra{ @Inject @RemetenteCompra private String remetente; public void envia(){ mailer.send(remente,...); } } Thursday, September 5, 13
  17. 17. Qualifier //outras annotations chatas @Qualifier @interface RemetenteCompra{ } Thursday, September 5, 13
  18. 18. Qualifier @ApplicationScoped class RemetentesProducer { @Produces @RemetenteCompra public String remetenteCompra(){ return properties.getProperty("remetente.compra"); } @Produces @RemetenteNewsletter public String remetenteNewsletter(){ return properties.getProperty("remetente.newsletter"); } } Thursday, September 5, 13
  19. 19. Indo além • VRaptor CDI e integração com o Container Thursday, September 5, 13
  20. 20. Injeção de coisas do container • Quero usar um EJB, EntityManager... Thursday, September 5, 13
  21. 21. Injeção de coisas do container @Component public class ProdutoDAO { @Inject private final EntityManager entityManager; public void salva(Produto produto) { entityManager.persist(produto); } Thursday, September 5, 13
  22. 22. Injeção de coisas do container @Stateless @Component public class ProdutoDAO { @Inject private final EntityManager entityManager; public void salva(Produto produto) { entityManager.persist(produto); } Thursday, September 5, 13
  23. 23. Injeção de coisas do container @Resource public class ProdutosController { //@EJB @Inject private ProdutoDAO produtoDAO; ... } Thursday, September 5, 13
  24. 24. EJB? • Por sinal o DAO é um EJB porque? Thursday, September 5, 13
  25. 25. EJB? @Component public class ProdutoDAO { @Inject private final EntityManager entityManager; @Transactional public void salva(Produto produto) { entityManager.persist(produto); } Thursday, September 5, 13
  26. 26. Exemplo de uso • Coloco no interceptor e tá tudo certo! Thursday, September 5, 13
  27. 27. Componente padrão @RequestScoped public class DefaultPathResolver implements PathResolver { public String pathFor(ResourceMethod method) { String format = resolver.getAcceptFormat(); String suffix = ""; if (format != null && !format.equals("html")) { suffix = "." + format; } //WEB-INF/jsp/controller/metodo.jsp String name = method.getResource().getType().getSimpleName(); String folderName = extractControllerFromName(name); return getPrefix() + folderName + "/" + method.getMethod().getName() + suffix + "."+getExtension(); } Thursday, September 5, 13
  28. 28. Componente customizado class CustomPathResolver implements PathResolver{ public String pathFor(ResourceMethod method) { String format = resolver.getAcceptFormat(); String suffix = ""; if (format != null && !format.equals("html")) { suffix = "." + format; } //WEB-INF/resources/controller/metodo.jsp String name = method.getResource().getType().getSimpleName(); String folderName = extractControllerFromName(name); return "WEB-INF/resources" + folderName + "/" + method.getMethod().getName() + suffix + "."+getExtension(); } } Thursday, September 5, 13
  29. 29. Alternative @Alternative class CustomPathResolver implements PathResolver{ public String pathFor(ResourceMethod method) { String format = resolver.getAcceptFormat(); String suffix = ""; if (format != null && !format.equals("html")) { suffix = "." + format; } //WEB-INF/jsp/controller/metodo.jsp String name = method.getResource().getType().getSimpleName(); String folderName = extractControllerFromName(name); return "WEB-INF/resources" + folderName + "/" + method.getMethod().getName() + suffix + "."+getExtension(); } } Thursday, September 5, 13
  30. 30. Registra no beans.xml <?xml version="1.0" encoding="UTF-8"?> <beans> <alternatives> <class>br.com.caelum.vraptor.view.CustomPathResolver</class> </alternatives> </beans> Thursday, September 5, 13
  31. 31. ilhazinha • Alternative habilitado só no bean archive Thursday, September 5, 13
  32. 32. ilhazinha • VRaptor não enxergava Thursday, September 5, 13
  33. 33. Prioridade @Alternative @Priority(Interceptor.Priority.APPLICATION) class CustomPathResolver implements PathResolver{ public String pathFor(ResourceMethod method) { String format = resolver.getAcceptFormat(); String suffix = ""; if (format != null && !format.equals("html")) { suffix = "." + format; } //WEB-INF/jsp/controller/metodo.jsp String name = method.getResource().getType().getSimpleName(); String folderName = extractControllerFromName(name); return "WEB-INF/resources" + folderName + "/" + method.getMethod().getName() + suffix + "."+getExtension(); } } Thursday, September 5, 13
  34. 34. Prioridade • Podemos sempre sobreescrever! Thursday, September 5, 13
  35. 35. Servidor já injeta Request • Request doVRaptor é modificado Thursday, September 5, 13
  36. 36. Aproveita o que já existe public class VRaptorRequest implements MutableRequest,HttpServletRequest { private final Hashtable<String, String[]> extraParameters = new Hashtable<String, String[]>(); //qual que ele vai injetar? @Inject private HttpServletRequest request; } Thursday, September 5, 13
  37. 37. Aproveitamos o existente @Decorator public class VRaptorRequest implements MutableRequest,HttpServletRequest { @Delegate private HttpServletRequest delegate; @Override public String getParameter(String name) { if (extraParameters.containsKey(name)) { String[] values = extraParameters.get(name); if (values.length == 1) { return values[0]; } else { return Arrays.toString(values); } } return delegate.getParameter(name); } Thursday, September 5, 13
  38. 38. Configuramos <decorators> <class>br.com.caelum.vraptor.http.VRaptorRequest</class> </decorators> Thursday, September 5, 13
  39. 39. Priority • Pode usar Priority também! Thursday, September 5, 13
  40. 40. Injetar Requests public class DefaultFormatResolver implements FormatResolver { private final HttpServletRequest request; private final AcceptHeaderToFormat acceptHeaderToFormat; @Inject public DefaultFormatResolver(HttpServletRequest request, AcceptHeaderToFormat acceptHeaderToFormat) { this.request = request; this.acceptHeaderToFormat = acceptHeaderToFormat; } public String getAcceptFormat() { String format = request.getParameter("_format"); if (format != null) { return format; } format = request.getHeader("Accept"); Thursday, September 5, 13
  41. 41. Trocar o request o tempo todo @Inject private CDIHttpServletRequestFactory requestFactory; @Inject private CDIHttpSessionFactory sessionFactory; public <T> T provideForRequest(RequestInfo request) { //configurando produtores requestFactory.setRequest(request); sessionFactory.setSession(request); return execution.insideRequest(container); } Thursday, September 5, 13
  42. 42. Notifica o povo @Inject private Event<HttpServletRequest> eventosDeRequest; public <T> T provideForRequest(RequestInfo requestInfo, Execution<T> execution) { //configurando produtores eventosDeRequest.fire(requestInfo.getRequest()); return execution.insideRequest(container); } Thursday, September 5, 13
  43. 43. Quero ser notificado public class CDIHttpServletRequestFactory implements ComponentFactory<HttpServletRequest>{ private HttpServletRequest request; public void handle(@Observes HttpServletRequest request){ this.request = request; } public HttpServletRequest getInstance(){ return request; } } Thursday, September 5, 13
  44. 44. VRaptor já tem @RequestScoped public @interface RequestScoped { } Thursday, September 5, 13
  45. 45. Eu sou você! @Stereotype @javax.enterprise.context.RequestScoped @Named public @interface RequestScoped { } Thursday, September 5, 13
  46. 46. VRaptor não usa @Inject @Resource public class ProdutoDAO { private final EntityManager entityManager; public CDIResourceComponent(EntityManager entityManager) { this.entityManager = entityManager; } } Thursday, September 5, 13
  47. 47. Oxi, sem @Inject? • Cadê o @Inject? Thursday, September 5, 13
  48. 48. Pedimos a ele para colocar public class AddInjectToConstructorExtension implements Extension{ public void processAnnotatedType(@Observes final ProcessAnnotatedType pat) { AnnotatedTypeBuilder builder = new AnnotatedTypeBuilder(); builder.readFromType(pat.getAnnotatedType()); if (hasArgsConstructorAndNoInjection) { Constructor constructor = constructors.get(0); //pulo do gato builder.addToConstructor(constructor,new AnnotationLiteral<Inject>() {}); //trocando a configuração original pat.setAnnotatedType(builder.create()); } } Thursday, September 5, 13
  49. 49. VRaptor já tem Factories @Component public class CDIComponent implements ComponentFactory { private final MyRequestComponent component; public CDIComponent(MyRequestComponent component) { this.component = component; } public ComponentToBeProduced getInstance() { return new ComponentToBeProduced(); } } Thursday, September 5, 13
  50. 50. Oxi, e o @Produces? • E o @Produces? Thursday, September 5, 13
  51. 51. @Produces em cima do método public class ComponentFactoryExtension implements Extension{ public void addProduces(@Observes ProcessAnnotatedType pat) { builder.readFromType(pat.getAnnotatedType()); if (ComponentFactory.class.isAssignableFrom(javaClass)) { builder.addToMethod(getInstance,new ProducesAnnotion()); } } } class ProducesAnnotion extends AnnotationLiteral<Produces>{ } Thursday, September 5, 13
  52. 52. Configura extensão • META-INF/services/ javax.enterprise.inject.spi.Extension Thursday, September 5, 13
  53. 53. Configura extensão br.com.caelum.vraptor.ioc.cdi.extensions.AddInjectToConstructorExtensi on br.com.caelum.vraptor.ioc.cdi.extensions.ComponentFactoryExtension Thursday, September 5, 13
  54. 54. E para testar? • E para testar? Thursday, September 5, 13
  55. 55. Arquillian @RunWith(Arquillian.class) public class CDIProviderRegisteringComponentsTest { @Deployment public static JavaArchive createDeployment() { return ShrinkWrap.create(JavaArchive.class) .addClass(DefaultPathResolver.class) .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); } Thursday, September 5, 13
  56. 56. Arquillian public static JavaArchive createDeployment() { return ShrinkWrap.create(JavaArchive.class) .addPackage("com.thoughtworks.xstream") } Thursday, September 5, 13
  57. 57. Problemas • Falta de controle sobre o container Thursday, September 5, 13
  58. 58. DeltaSpike cdiContainer = CdiContainerLoader.getCdiContainer(); cdiContainer.boot(); Thursday, September 5, 13
  59. 59. Controle do escopo public void start(Class<? extends Annotation> scope) { cdiContainer.getContextControl().startContext(scope); } Thursday, September 5, 13
  60. 60. Controle do escopo start(RequestScoped.class); start(SessionScoped.class); //logica stop(SessionScoped.class); stop(RequestScoped.class); Thursday, September 5, 13
  61. 61. Últimos detalhes • Funcionalidade com muitos passos Thursday, September 5, 13
  62. 62. Funcionalidade com vários passos @Controller public class PagamentoController implements Serializable{ private InformacoesDoComprador info = new InformacoesDoComprador(); @Inject public PagamentoController(Result result) { super(); this.result = result; } @Post("compra/endereco") public void associaEndereco(String endereco){ info.setEnderecoDeEntrega(endereco); } Thursday, September 5, 13
  63. 63. Funcionalidade com vários passos @Controller public class PagamentoController implements Serializable{ //resto que não coube e eu não soube ajeitar. @Post("compra/endereco") public void associaEndereco(String endereco){ info.setEnderecoDeEntrega(endereco); } @Post("compra/cartao") public void associaCartao(String numero){ //como mantém as informações do comprador? info.setNumeroDoCartao(numero); result.include("dadosComprador",info); } } Thursday, September 5, 13
  64. 64. Como implementar? • 2 Maneiras? Thursday, September 5, 13
  65. 65. Exemplo de implementação • Stateless? Thursday, September 5, 13
  66. 66. Outro exemplo • Stateful? Thursday, September 5, 13
  67. 67. Meio termo • Mangue moderado! Thursday, September 5, 13
  68. 68. Conversation @Controller @ConversationScoped public class PagamentoController implements Serializable{ @Inject private Conversation conversation; @Post("compra/endereco") public void associaEndereco(String endereco){ conversation.begin(); info.setEnderecoDeEntrega(endereco); result.redirectTo("/compra/cartao? cid="+conversation.getId()); } Thursday, September 5, 13
  69. 69. Conversation @Controller @ConversationScoped public class PagamentoController implements Serializable{ @Inject private Conversation conversation; @Post("compra/cartao") public void associaCartao(String numero){ info.setNumeroDoCartao(numero); //grava e vamo aí.. conversation.end(); result.include("dadosComprador",info); } Thursday, September 5, 13
  70. 70. Conversation @Controller @ConversationScoped public class PagamentoController implements Serializable{ @Inject public PagamentoController(Result result) { super(); this.result = result; } Thursday, September 5, 13
  71. 71. E esse result? • O Result tem que ser passivavel :( Thursday, September 5, 13
  72. 72. Não mais :) @Controller @ConversationScoped public class PagamentoController implements Serializable{ @Inject public PagamentoController(@TransientReference Result result) { super(); this.result = result; } Thursday, September 5, 13
  73. 73. Valeu! @alberto_souza github.com/asouza Thursday, September 5, 13
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×