Friday, August 6, 2010MVP Em Flex

Post rápido de como implementar MVP com Flex.

Para entender melhor o funcionamento do MVP recomento a leitura deste artigo de Martin Fowler.

MVP é um design pattern projetado para facilitar testes unitários automatizados e melhorar a separação de interesses em lógica de apresentação (SOC).

Modelo é uma interface que define os dados que serão utilizados na view.

A view é uma interface que faz a exibição dos dados  e envia para o presenter todas as interações do usuário em que o presenter estiver interessado.

Presenter atua sobre o modelo e sobre a visão, formatando dados, contendo lógica de negócio,  solicitando para  a view comportamentos mas sem definir como ela o fará.


A estrutura do projeto está definida conforme imagem abaixo:

mvp_estutura


View
: Pacote onde contem nossa classe de view, neste exemplo componente de Login.

Components:   Componentes customizados para expor apenas os métodos necessários da view ao Presenter.

Interfaces: Interfaces que definem os métodos que a view irá expor.

Presenter: Presenter de login.

Model: Contém interface de modelo que o presenter irá conhecer e uma implementação do modelo.

Vamos aos códigos:

Login.mxml

Este componente define a view, implementando a interface ILoginDisplay onde contém os métodos acessiveis ao presenter, logo mais entraremos em maiores detalhes.


<?xml version="1.0" encoding="utf-8"?>
<mx:Panel creationComplete="bindUi()" implements="interfaces.ILoginDisplay" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="300" height="180" xmlns:components="components.*">
<mx:Script>
<![CDATA[
import model.ILoginModel;
import model.LoginModel;

/********
* THE MOST IMPORTANT PART THAT SHOULD NOT BE FORGOTTEN IS TO CALL bingUi() METHOD
* IN creationComplete() OF YOUR VIEW.
* This is a login sample of how to use MVP in your flex applications.
* AUTOR : rrigoni@gmail.com
********/
import interfaces.ICustomButton;
import interfaces.ILoginDisplay;

import mx.controls.Alert;
import mx.core.IButton;

import presenters.LoginPresenter;

/**
* Exposes only the methods defined in ICustomButton interface,and it's all the presenter
* needs to work.
**/
public function buttonClear():ICustomButton{
return this.clear;
}
/**
* Exposes only the methods defined in ICustomButton interface,and it's all the presenter
* needs to work.
**/
public function buttonLogin():ICustomButton{
return this.loggin;
}
/**
* This method will be called when a login error occur.
* The presenter will send what was the error.
**/
public function displayErrorMessage(message:String):void{
Alert.show(message, "ERROR!");
}
/**
* This method will notify the user and perform with the login success.
* Its her responsability define what will be done.
**/
public function loginSuccess():void{
Alert.show("Login success with MVP.");
}
/**
* Bind the view with the presenter.
* Keep on eye on this method, need to called on creationComplete of your view!
**/
public function bindUi():void{
new LoginPresenter(this);
}
/**
* This method clean fields as needed
* How to clean, or witch fields need to be clean the view will define
**/

public function cleanFields():void{
this.username.text = null;
this.password.text = null;
}
/**
* Return the current model.
**/
public function model():ILoginModel{
return new LoginModel(username.text, password.text);
}
]]>
</mx:Script>
<mx:Form label="Login">
<mx:FormItem label="Username:">
<mx:TextInput id="username" />
</mx:FormItem>
<mx:FormItem label="Password:">
<mx:TextInput displayAsPassword="true" id="password" />
</mx:FormItem>
<mx:HBox>
<components:CustomButton id="loggin" label="Loggin" />
<components:CustomButton id="clear" label="Clear" />
</mx:HBox>
</mx:Form>
</mx:Panel>
<pre>

CustomButton.as, componente customizado apenas para que ele implemente a nossa interface e exponha apenas oque for realmente necessário para LoginPresenter.as trabalhar.

</pre>
package components
{
import interfaces.ICustomButton;

import mx.controls.Button;
/**
*Custom Button, need extends Button and implement ICustomButton
* interface because we need to expose only the ICustomButton methodos
* to the presenter.
* @author ronaldo
*
*/
public class CustomButton extends Button implements ICustomButton{
public function CustomButton(){
}
}
}
<pre>
<pre>

ICustomButton.as, nesta interface precisamos definir todos os métodos que o presenter irá utilizar do componente CustomButton, que neste caso eu apenas precisei definir addEventListener.


package interfaces{</pre>
import flash.events.Event;
/**
* This interface exposes only the addEventListener function
* Until now is only we need to work.
* @author ronaldo
*
*/
public interface ICustomButton{
function addEventListener (type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void;
}
}
<pre>
<pre>
<pre>

IDisplay.as, interface padrão ,todas as interfaces que irão ser implementadas na view deverão estender esta interface e implementar o método bindUi(), para efetuar o bind da view
com o presenter.


package interfaces{
public interface IDisplay{
/**
* Bind the view with the presenter, obey to call this method on creationComplete of your view.
*
*/
function bindUi():void;
}
}

ILoginDisplay, esta interface é tudo oque o presenter precisa conhecer sobre o componente da view, a view irá implementar e o presenter terá uma referência esta interface apenas,
e com acesso apenas ao que lhe diz respeito, adicionar listeners nos botões, capturar os valores dos campos, validar login do usuário e informar a view sucesso ou erro,
e todo o comportamento visual deverá ficar a cargo da view, o presenter apenas armazena a lógica de negócio e notifica a view através da interface.
Você deve estar se perguntando, Porque criamos interfaces e componentes customizados?, Pelo fato de que a view irá expor estas interfaces ao presenter, e não expoe os próprios componentes.
A única coisa que o presenter conhece são os métodos da interface.

package interfaces{
import model.ILoginModel;

/**
* interface to Login.mxml
* Exposes only what you need to work.
* @author ronaldo
*
*/
public interface ILoginDisplay extends IDisplay{
/**
*Return the model
* @return  the model
*
*/
function model():ILoginModel;
/**
*Exposes the clear button interface
* @return the button clear interface
*
*/
function buttonClear():ICustomButton;
/**
*Exposes the login button interface
* @return the login button interface
*
*/
function buttonLogin():ICustomButton;
/**
* Define in the view method to display error messages
* @param message The message to display.
*
*/
function displayErrorMessage(message:String):void;
/**
* The login logic remains on the presenter, this method notify
* the view to perform with login success.
*
*/
function loginSuccess():void;
/**
*
*Notify the view to clean the fields.
*/
function cleanFields():void;
}
}

LoginPresenter.as, Presenter que irá ter uma instância da interface implementada pela view, será responsável por adicionar listeners nos botões, manipular os eventos,
em caso de login irá capturar o modelo e não acessar os campos diretamente, e validar, caso login efetuado com sucesso avisa a view e ela deverá saber como se comportar.
Em caso de login errado, repassa para a view uma mensagem e ela processa.
Neste momento você deve ter entendido porque a criação de componentes customizadoe e de interfaces customizadas, desta forma você restringe que todo comportamento visual deverá ficar
sobre responsabilidade da camada de visão, apenas expõe ao Presenter oque lhe for necessário e uma interface do modelo.
Repare no método display.loginSuccess() ele é chamado para avisar a view que o login foi processado com sucesso, quem deverá mudar o comportamento visual não deve ser o presenter,
por isso foi restringido apenas alguns métodos a ele, se em algum momento for necessário mudar a implementação da view nada muda com o presenter.

package presenters{
import flash.events.MouseEvent;

import interfaces.ILoginDisplay;

import model.ILoginModel;
import model.LoginModel;
/**
* Presenter for the Login.mxml view.
* @author ronaldo
*
*/
public class LoginPresenter{

/**
*The view instance
*/
private var dislpay:ILoginDisplay = null;

/**
* Construct the presenter instance and bind to the local view interface.
* @param view The view instance.
*
*/
public function LoginPresenter(view:ILoginDisplay){
this.dislpay = view;
addListeners();
}

/**
*Apply the listeners
*
*/
private function addListeners():void{
dislpay.buttonClear().addEventListener(MouseEvent.CLICK, buttonClear_eventHandler);
dislpay.buttonLogin().addEventListener(MouseEvent.CLICK, buttonlogin_eventHandler);
}
/**
* Button login event handler.
* Validate de login and notify the view.
* @param event The click event.
*
*/
private function buttonlogin_eventHandler(event:MouseEvent):void{
var mod:ILoginModel  = dislpay.model();
if(mod.password && mod.password.length < 1 && mod.username && mod.username.length < 1){
dislpay.displayErrorMessage("Username and passwords cannot be blank!");
}
if(mod.username == 'admin' && mod.password == 'admin'){
dislpay.loginSuccess();
}else{
dislpay.displayErrorMessage("Wrong username and password.");
dislpay.cleanFields();
}
}
/**
* Button clear fields event handler,
* Notify the view when the button was pressed, and the view manage how to proceed.
* @param event The click event
*
*/
private function buttonClear_eventHandler(event:MouseEvent):void{
dislpay.cleanFields();
}

}

}

mvp.mxml, é nossa Application, ela apenas centraliza o componente de login e adiciona-o

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" minWidth="955" minHeight="600" xmlns:view="view.*">
<mx:HBox verticalAlign="middle" horizontalAlign="center" width="100%" height="100%">
<view:Login  />
</mx:HBox>
</mx:Application>

ILoginModel.as, define o modelo para acessar os dados.


package model{
public interface ILoginModel{
function get username():String;
function get password():String;
}
}
LoginModel.as, implementação do modelo.
package model{
public class LoginModel implements ILoginModel{

private var _password:String;
private var _username:String;

/**
*Generate new instance of LoginModel
* @param user The username
* @param pass The password
*
*/
public function LoginModel(user:String, pass:String){
this._password = pass;
this._username = user;
}

/**
*Return the username
* @return the username
*
*/

public function get username():String{
return _username;
}

/**
*Return the password.
* @return  the password.
*
*/
public function get password():String{
return _password;
}

}
}

Desta forma o Presenter apenas conhece o modelo e os  comportamentos dos Buttons, e a lógica de negócio.

Código fonte disponível para download mvp.

Qualquer dúvida que eu possa ajudar rrigoni@gmail.com.

Ronaldo.

Dica rápida, configurando teclado Ubuntu layout Inglês Internacional.

Abra seu console e altere seguinte arquivo:


sudo gedit /usr/lib/gtk-2.0/2.10.0/immodule-files.d/libgtk2.0-0.immodules
<pre>

Procure a linha abaixo e adicione <strong>:en</strong> ao final da linha como segue:


"/usr/lib/gtk-2.0/2.10.0/immodules/im-cedilla.so"
"cedilla" "Cedilla" "gtk20" "/usr/share/locale" "az:ca:co:fr:gv:oc:pt:sq:tr:wa<strong>:</strong>en"

Salve  arquivo, neste ponto as aplicações GTK, agora vamos configurar para as aplicações QT<strong>.</strong>
Altere o arquivo "Compose" conforme abaixo alterando todas as ocorrências de <strong>ć</strong> e <strong>Ć</strong> para <strong>ç </strong>e<strong> Ç </strong>respectivamente.


sudo gedit /usr/share/X11/locale/en_US.UTF-8/Compose

Após alterado todas as ocorrências reinicie e pronto.

Abraços.

Ronaldo.

Sunday, August 2, 2009Rodando um jar pelo console no Linux

Salve galera,

Dica rápida,

Se quiser executar um jar pelo console de forma que  o console não fique "preso", isso ocorre com frequência quando precisamos inicializar um serviço java remotamente você precisa fechar os Streams de saida do Java na classe System conforme abaixo.


/**
* Fecha o fluxo de saida para o console.
*/
private static void closeOutStreams(){
System.out.close();
System.err.close();
}

Após isso basta inicializar seu jar conforme abaixo.


java -jar meuJar.jar &

Ronaldo.

Buenas Galera, vamos abordar aqui um framework simples para se trabalhar com Xml Sockets entre Java e Flex.

O Framework abordado se chama Apache Mina Framework que implementa a nova API Java NIO , onde foi otimizado muito o processamento de IO em Java.

O que é Apache Mina?

Apache Mina é indicado para quem precisa desenvolver um servidor utilizando um protocolo expecífico, ou em um protocolo novo
onde você mesmo poderá implementa-lo, e que necessite de escalabilidade e boa performace. Onde você possue tempo apertado e o cronograma não permite desenvover um servidor do zero.

Apache MINA ( A Multi-purpose Infrastructure for Network Applications)

MINA e um framework para aplicações em rede, desenvolvido em java, com um conjunto de API para capturar eventos assincronamente, auxilia facilmente a desenvolver aplicações que requer conectividade, com uma alta performace e alta escalabilidade.
Pode ser desenvolvido com o MINA um servidor com um protocolo especifico sem ficar escovando bits, e com uma performace e desempenho considerável, permitindo separar a logica de conectividade da logica do protocolo. Diversas camadas de transporte(*Acceptor) já esta implementado no framework como TCP/IP , UDP/ID e Porta Serial, mas você pode desenvolver a sua e plugar.
Podemos resumir o MINA como um conjunto de classes `templates`, bastando o desenvolvedor se preocupar, com a logica do seu protocolo.

Documentação

Start Guide

Oque é Xml Sockets?

Há dois tipos diferentes de conexões de Socket possíveis no ActionScript 3.0: Conexões por XML Sockets e conexões por Binary Socket. Um XML Socket permite que você se conecte com um servidor remoto e crie uma conexão de servidor que permaneça aberta até que seja fechada explicitamente. Isso permite a troca de dados XML entre um servidor e um cliente sem necessidade de abrir continuamente novas conexões de servidor. Outro benefício do uso de um XML Socket é que o usuário não precisa solicitar dados explicitamente. É possível enviar dados do servidor sem solicitações e enviar dados a cada cliente conectado com o XML Socket.

As conexões de XML Socket requerem a presença de uma política de Socket no servidor de destino.

Uma conexão de Binary Socket é semelhante a um XML Socket, exceto que o cliente e o servidor não precisam trocar pacotes XML especificamente. Em vez disso, a conexão pode transferir dados como informações binárias. Isso permite conectar-se a uma ampla faixa de serviços, incluindo servidores de email (POP3, SMTP e IMAP) e novos servidores (NNTP).

Bom vamos ao que intereça.

Baixe os fontes do Mina aqui

Crie um projeto Java e adicione os Jars do Mina no seu classpath.

O Mina possue um procolo já implementado chamado TextLineCodecFactory que se baseia em procolo de texto puro,

onde  o final de cada requisição é um \n ou seja um ENTER.

Mas este procolo não nos ajuda muito, então iremos implementar nosso próprio protocolo chamado XmlProtocolCodecFactory e em seguida vamos implementar nosso servidor e o Handler que receberá e tratará as requisições.

Aqui a criação do XmlDecoder, responsável por decodificar uma requisição e transformar a mesma em um Document.


package br.com.ronaldorigoni.mina.codec;

import java.io.ByteArrayInputStream;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderAdapter;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
* Decoder de XML, responsável por interceptar a requisiçao do usuário
* e transformar a mesma em um Objeto DOM, e passar o mesmo para o Handler
* manipular.
* @author Ronaldo Rigoni
*/
public class XmlDecoder extends ProtocolDecoderAdapter {
/**
* Decodifica a requisiçao e passa a mesma para o handler.
*/
public void decode(IoSession session, IoBuffer buffer, ProtocolDecoderOutput output) throws Exception {
output.write(parserXML(buffer));
}

/**
* Efetua o parser dos bits recebidos e transforma em um Document
* @param xmlBuffer O Buffer de entrada.
* @return O documento xml
* @throws ParserConfigurationException Caso nao consiga efetuar o parser.
* @throws SAXException  Caso houver erro da biblioteca SAX
* @throws IOException Caso ouver erro de IO
*/
public Object parserXML(IoBuffer xmlBuffer) throws ParserConfigurationException, SAXException, IOException {
// captura os bits da requisicao e aloca
byte[] data = new byte[xmlBuffer.limit()];
xmlBuffer.get(data);
// transforma em string removendo espacoes em branco
String xml = new String(data).trim();
// cria o documento e retorna.
Document document = DocumentBuilderFactory.newInstance().
newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes()));
return (document);
}

}

Aqui o XMLEncoder responsável por escrever os bits para o cliente.


package br.com.ronaldorigoni.mina.codec;

import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.textline.LineDelimiter;

/**
* Encoder de XML, responsavel por transformar a saida ao usuário e escrever a mesma.
* @author Ronaldo Rigoni
*/
public class XmlEncoder extends ProtocolEncoderAdapter {

/**
* ENCODER final
*/
private final AttributeKey ENCODER = new AttributeKey(getClass(), "encoder");

/**
* Charset da Requisição
*/
private final Charset charset;
/**
* Delimitador da requisição.
*/
private final LineDelimiter delimiter;
/**
* Tamanho máximo da requisição.
*/
private int maxLineLength = Integer.MAX_VALUE;

/**
* Construtor do Encoder
* Define o Charset e o Delimitador
*/
public XmlEncoder() {
this.charset = this.charset = Charset.forName("UTF-8");
this.delimiter = new LineDelimiter("\0");
}

/**
* Retorna o tamanho maximo da requisição.
* @return O Tamanho maximo da requisição.
*/
public int getMaxLineLength() {
return maxLineLength;
}

/**
* Codifica a saida ao Usuário.
*/
public void encode(IoSession session, Object message,ProtocolEncoderOutput out) throws Exception {
// capturando o charset da sessao.
CharsetEncoder encoder = (CharsetEncoder) session.getAttribute(ENCODER);
// caso for nulo é criado e setado na sessão do usuário.
if (encoder == null) {
encoder = charset.newEncoder();
session.setAttribute(ENCODER, encoder);
}
// mensagem a ser escrita.
// Por se tratar de XML e nao ser nada mais que uma string
// pegamos o toString() dela.
String value = message.toString();
//Cria o buffer de saida.
IoBuffer buf = IoBuffer.allocate(value.length()).setAutoExpand(true);
// adiciona ao buffer de saida a mensagem e o charset
buf.putString(value, encoder);
if (buf.position() > maxLineLength) {
throw new IllegalArgumentException("Tamanho da linha muito grande: " + buf.position());
}
// seta o delimitador \0
buf.putString(delimiter.getValue(), encoder);
// fecha o buffer
buf.flip();
// escreve na saida.
out.write(buf);
}
}

Agora criaremos nossa Factory que fabricará os objetos para as requisições.


package br.com.ronaldorigoni.mina.codec;

import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;

/**
* Classe Factory para Encoder e Decoder de XML utilizando Apache MINA
* @author Ronaldo Rigoni
*/
public class XmlProtocolCodecFactory implements ProtocolCodecFactory {

/**
* Cria um novo Encoder de XML
*/
public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {
return new XmlEncoder();
}

/**
* Cria um novo Decoder de XML
*/
public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
return new XmlDecoder();
}

}

Agora vamos criar A classe de servidor, que será responsável por abrir a porta no servidor e inicializar o Handler que manipulará as requisições do usuário.


package br.com.ronaldorigoni.mina.core;

import java.net.InetSocketAddress;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.filter.logging.MdcInjectionFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

import br.com.ronaldorigoni.bingo.codec.XmlProtocolCodecFactory;
import br.com.ronaldorigoni.bingo.config.Config;

/**
* Inicializa o servidor.
*
* @author Ronaldo Rigoni
*/
public class Server {

static Logger logger = Logger.getLogger(Server.class);

private static void start(){
try {// objeto que receberá novas conecoes
NioSocketAcceptor acceptor = new NioSocketAcceptor();
// filtro de requisicoes
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
// logger
LoggingFilter loggingFilter = new LoggingFilter();
chain.addLast("logging", loggingFilter);
// injecao do filtro
MdcInjectionFilter mdcInjectionFilter = new MdcInjectionFilter();
chain.addLast("mdc", mdcInjectionFilter);
// atribuicao do protocolo de parser de XML que criamos.
chain.addLast("codec", new ProtocolCodecFilter(new XmlProtocolCodecFactory()));
// adicionando logger.
addLogger(chain);
// adicionando  Handler, que tratará as requisiçoes
acceptor.setHandler(new ChatServerHandler());
// setando porta para ouvir.
acceptor.bind(new InetSocketAddress(8090));
logger.debug("Servidor de chat ouvindo na porta:" + 8090);
} catch (Exception e) {
logger.error("Erro ao inicializar servidor de chat.",e);
}
}
/**
* Adiciona um logger para o filtro de requisicoes.
**/
private static void addLogger(DefaultIoFilterChainBuilder chain) throws Exception {
chain.addLast("logger", new LoggingFilter());
}
/**
* Inicializa o servidor de chat.
**/
public static void main(String[] args) {
try {
start();
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}

}

Agora vamos criar um objeto para encapsular as requisiçoes chamado Request, ele irá armazenar o tipo de requisiçao e os parametros contidos nela.


package br.com.ronaldorigoni.mina.codec;

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
* Classe para encapsular uma requisição.
* @author Ronaldo Rigoni
*/
public class Request {

private static final Logger logger = Logger.getLogger(Request.class);

//Possíveis campos de um input
public static final String FIELD_USER_NAME = "userName";
public static final String FIELD_USER_LOGIN = "login";
public static final String FIELD_USER_PASSWORD = "password";
public static final String FIELD_TO = "to";
public static final String FIELD_FROM = "from";
public static final String FIELD_MESSAGE = "message";

public static final String OPERATION_CHAT_USER_IN ="add-user-in-chat-room";
public static final String OPERATION_CHAT_USER_IN ="remove-user-in-chat-room";
public static final String OPERATION_CHAT_SEND_MESSAGE ="chat-send-message";
public static final String OPERATION_XML_POLICY ="<policy-file-request/>";

private String operation;
private HashMap<String, String> parameters = new HashMap<String, String>();
private String inputText;

public Request(Document document) {
parseDocument(document);
}

/**
* @return the operation
*/
public String getOperation() {
return operation;
}

/**
* @param operation the operation to set
*/
public void setOperation(String operation) {
this.operation = operation;
}

/**
* @return the parameters
*/
public HashMap<String, String> getParameters() {
return parameters;
}

/**
* @param parameters the parameters to set
*/
public void setParameters(HashMap<String, String> parameters) {
this.parameters = parameters;
}

/**
* @return the inputText
*/
public String getInputText() {
return inputText;
}

/**
* @param inputText the inputText to set
*/
public void setInputText(String inputText) {
this.inputText = inputText;
}

private void parseDocument(Document document){
if(document == null)
throw new NullPointerException("Documento nulo.");
else{
NodeList nodes = document.getDocumentElement().getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
if(nodes.item(i).getFirstChild() != null){
logger.debug(nodes.item(i).getNodeName()+","+ nodes.item(i).getFirstChild().getNodeValue());
parameters.put(nodes.item(i).getNodeName(), nodes.item(i).getFirstChild().getNodeValue());
}
}
if(document.getDocumentElement() != null){
this.setOperation("<"+document.getDocumentElement().getTagName()+"/>");
logger.debug("Operation:"+document.getDocumentElement().getTagName());
}
}
}

@Override
public String toString() {
return "Operation:"+getOperation()+",parameters{"+getParameters()+"}";
}
}

Agora vamos criar o Manipulador de Requisiçoes que será responsavel por tratar todas as requisições dos usuários tanto de entrada como de saida, login no chat, envio de mensagem e saida do usuáro.


package br.com.ronaldorigoni.mina.core;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.w3c.dom.Document;

import br.com.ronaldorigoni.mina.codec.Request;

/**
* Classe manipuladora de requisiçoes, é onde toda a lógica do chat acontece.
* É nela que toda requisição após passar pelo Codec XML é encaminhado para cá/
* @author Ronaldo Rigoni rrigoni@gmail.com
*
*/
public class ChatHandler extends IoHandlerAdapter {
/** Logger estatico do Handler **/
private static final Logger logger = Logger.getLogger(ChatHandler.class);
/**
* Constante para armazenamento do usuario na sessao.
*/
private static final String USER_NAME = "username";

/**
* XML policy caso a requisicao venha de outro host diferente ao do servidor.
*/
private static final String XML_POLICY = "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"8090\"/></cross-domain-policy>";

/**
* Lista para armazenar todas as sessoes de chat.
* Esta lista necessita estar sincronizada pois em um ambiente multi-thread precisamos
* proteger acesso concorrente a mesma.
*/
private List<IoSession> sessions = Collections.synchronizedList(new ArrayList<IoSession>());

/**
* Metodo invocado quando ocorrer alguma exception na escrita de mensagens ou
* no recebimento.
* @param session Sessao do usuario.
* @param cause Exception lancada
*/
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
super.exceptionCaught(session, cause);
}
/**
* Metodo que valida um usuario baseado nos seguintes criterios para poder ingressar no chat.
* 1 - Nao pode haver a sessao do usuario na lista de chat.
* 2 - Nao pode haver uma sessao com o mesmo atributo <code>username</code> que a do usuario
* que esta tentando logar.
* @param session A sessao do usuario que requisitou login.
* @return <code>true</code> caso esta apto a ingressar no chat,
*             <code>false</code> caso nao esta apto.
*/
private boolean loginUser(IoSession session){
boolean isValid = true;
String userLogin = (String) session.getAttribute(USER_NAME);
if(userLogin != null && userLogin.length() > 5){
if(!sessions.contains(session)){
synchronized (session) {
for(IoSession ses : sessions){
// capturando login da sessao
String username = (String)session.getAttribute(USER_NAME);
if(username.equalsIgnoreCase(userLogin)){
isValid  = false;
}
}
}
}
}else{
isValid = false;
}
return isValid;
}

/**
* Invocado a cada mensagem recebida.
* Pelo fato de construirmos um Codec XML o objeto message sempre ser'a uma instancia
* de org.w3c.dom.Document, caso nao for a mensagem ser'a ignorada.
* @param session Sessao do usuario.
* @param message Mensagem recebida.
*/
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
if(message instanceof org.w3c.dom.Document){
// efetuando a conversao do documento recebido.
Document document = (Document) message;
Request request = new Request(document);
if (request.getOperation().equalsIgnoreCase(Request.OPERATION_XML_POLICY)) {
session.write(XML_POLICY);
} else if (request.getOperation().trim().equalsIgnoreCase(Request.OPERATION_CHAT_USER_IN)) {
// adiciona o username na sessao e envia para login.
session.setAttribute(USER_NAME,request.getParameters().get(USER_NAME));
boolean logged = loginUser(session);
if(!logged){
session.write("<forbiden/>");
}else{
// adiciona usuario nas sessoes do chat.
sessions.add(session);
//processa entrada do usuario.
processUserEnterInChat(session);
}
}else if(request.getOperation().trim().equalsIgnoreCase(Request.OPERATION_CHAT_USER_EXIT)){
// remove sessao do usuario da lista de sessoes do chat.
sessions.remove(session);
// processa saida do usuario
processUserExitInChat(session);
// fecha sessao do usuario.
session.close(true);
}else if(request.getOperation().trim().equalsIgnoreCase(Request.OPERATION_CHAT_SEND_MESSAGE)){
String chatMessage = request.getParameters().get(Request.FIELD_MESSAGE);
// processa mensagem de chat.
processChatMessage(chatMessage, session);
}
}else{
logger.error("Formato da requisicao invalido.");
}
}

/**
* Processa o envio de uma mensagem de chat.
* @param message Mensagem
* @param session Sessao do usuario que est'a enviando.
*/
private void processChatMessage(String message, IoSession session){
logger.debug("Usuario:"+session.getAttribute(USER_NAME)+" enviou mensagem:"+message);
writeBroadCastChatMessage(message);
}
/**
* Processa a entrada de um usu'ario no chat.
* @param session Sessao do usuario.
*/
private void processUserEnterInChat(IoSession session){
StringBuffer buff = new StringBuffer("<response>");
buff.append("<type>user-enter-in-chat</type>");
buff.append("<username>"+session.getAttribute(USER_NAME)+"</username>");
buff.append("</response>");
writeBroadCastChatMessage(buff.toString());
}
/**
* Processa a sa'ida de um usu'ario no chat.
* @param session Sessao do usuario.
*/
private void processUserExitInChat(IoSession session){
StringBuffer buff = new StringBuffer("<response>");
buff.append("<type>user-exit-in-chat</type>");
buff.append("<username>"+session.getAttribute(USER_NAME)+"</username>");
buff.append("</response>");
writeBroadCastChatMessage(buff.toString());
}

/**
* Envia uma mensagem de chat para todos os usuarios.
* @param message A mensagem.
*/
private void writeBroadCastChatMessage(String message){
synchronized (sessions) {
for(IoSession session : sessions){
session.write(message);
}
}
}
/**
* Invocao apos uma mensagem ser enviada com sucesso.
* Aqui poderemos estar efetuando um log das mensagens que foram enviadas, ou descartarmos.
* @param session Sessao de destino da mensagem
* @param message Mensagem enviada.
*/
@Override
public void messageSent(IoSession session, Object message) throws Exception {
logger.debug(session+" envia >>> "+message);
}

/**
* Metodo invocado quando a sessao do usuario for fechada, quando o socket do cliente for fechado ou
* pelo servidor de chat ou pelo cliente.
* Aqui poderemos remover atributos da sessao do usuario, ou quaisquer operacoes que precisarmos quando
* o mesmo se desconectar.
* @param session Sessao do Usuario.
*/
@Override
public void sessionClosed(IoSession session) throws Exception {
// quando conexao fechada 'e processado a saida do usuario.
logger.debug("Sessao fechada:"+session);
processUserExitInChat(session);
}

/**
* Metodo invocado quando um usuario solicitar uma conexao, (quando ela for criada).
* @param session Sessao do usuario.
*/
@Override
public void sessionCreated(IoSession session) throws Exception {
logger.debug("Sessao criada:"+session);
super.sessionCreated(session);
}

/**
* Invocado quando uma sessao entrar em timeout.
* @param session Sessao do usuario
* @param status Status da sessao.
*/
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
logger.debug("Sessao em timeout:"+session);
super.sessionIdle(session, status);
}

/**
* Metodo invocado quando uma sessao for aberta.
* @param session Sessao do usuario.
*/
@Override
public void sessionOpened(IoSession session) throws Exception {
logger.debug("Sessao aberta:"+session);
super.sessionOpened(session);
}
}

<span>

Toda a parte de servidor está concluída, em um próximo post vamos criar o cliente em Adobe Flex, e abordaremos toda a integração e funcionamento detalhado do mesmo.

Um abraço, Ronaldo.

Dúvidas? ronaldo@ronaldorigoni.com.br

[/sourcecode]
[/sourcecode]

Olá galera,

Após muitos post e tutoriais pela internet, de tanto quebrar a cabeça para gerar FLV apartir dos formatos AVI,MPEG, MPG, WMV e MOV, criei uma classe para conversão de vídeos e geração de imagens a partir do vídeo ( crop de um frame do vídeo), segue abaixo o código compartilar com vocês.

Primeiramente você precisa ter instalado em seu sistema (Linux) as seguintes bibliotecas, execute o comando abaixo como root em seu console:


sudo apt-get install libfaad-dev libmp3lame-dev libmp3lame0 libfaac-dev libfaad2-dev

Lembrando que estas bibliotecas foram instaladas em ambiente 32 bits, se você instalar em ambiente 64bits os nomes das bibliotecas podem mudar.

Só agora instale o ffmpeg:


sudo apt-get install ffmpeg

A classe abaixo utiliza o Design Pattern Singleton, onde apenas uma única instância do conversor de vídeos ficará ativa por se tratar de um processo que utiliza muitos recursos do sistema operacional foi optado por criar uma Thread interna que converterá um único vídeo por vez, os restantes ficarão na fila de espera.

Segue abaixo classe em questão, lembrando que não foi abordado aqui o JavaBean Midia, que contem apenas dois atributos "caminho" e "destino" com seus respectivos getters e setters. Crie você mesmo este bean e modifique a lista de pool de vídios a converter.

package br.com.ronaldorigoni.videoconverter.util;

import br.gov.mec.portaldoprofessor.client.cms.JornalRemote;
import br.gov.mec.portaldoprofessor.client.vo.MidiaAProcessar;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.naming.InitialContext;
import org.apache.log4j.Logger;

/**
* Classe utilitária para conversão de vídeos usando a biblioteca natíva <code>ffmpeg</code> * Utiliza o mecanismo de pool de conversão.
* Para um correto funcionamento deve-se instalar a biblioteca com as seguintes dependências.
* ffmpeg - A própria biblioteca
* libfaad-dev - Biblioteca de desenvolvimento de áudio é vídeo.
* libfaac-dev  - Biblioteca de desenvolvimento para otimizaçãoes no streaming de áudio.
* libfaad2-dev - Biblioteca de desenvolvimento de áudio e vídeo versão dois.
* Testado em ambiente Ubuntu - Debian
* @author Ronaldo Rigoni
*/
public class VideoConverter {

/**
* Logger statico para o Conversor de Vídeo
*/
private static final Logger logger = Logger.getLogger(VideoConverter.class);
/**
* Pool de mídias a converter.
*/
private static final List<Midia> midiasAProcessar = new ArrayList<Midia>();

/**
* Adiciona uma midia no pool.
* @param midia Midia a converter.
*/
public void addMediaInPool(MidiaAProcessar midia) {
midiasAProcessar.add(midia);
}
/**
* Thread responsável por verificar a todo momento se existem vídeos a serem convertidos
* Caso possuir algum vídeo, efetua a conversão gera as imágens do respectivo vídeo,
* atualiza em banco as informaçãoes da mídia e notifica ao usuário que inseriu este vídeo.
*/
private Runnable worker = new Runnable() {
public void run() {
while (true) {
if (!midiasAProcessar.isEmpty() &amp;amp;amp;&amp;amp;amp; midiasAProcessar.get(0) != null) {
MidiaAProcessar midiaAProcessar = midiasAProcessar.get(0);
String command = command(midiaAProcessar.getCaminho(), midiaAProcessar.getDestino());
String commandCropImagemMedia = commandImagemMedia(midiaAProcessar.getDestino());
String commandCropImagemMicro = commandImagemMicro(midiaAProcessar.getDestino());
Process videoProcess = null;
Process imageMediaProcess = null;
Process imageMicroProcess = null;
try {
videoProcess = Runtime.getRuntime().exec(command);
int code = videoProcess.waitFor();
if (code == 0) {
logger.debug("Gerado video com sucesso.");
} else {
logger.error("Erro ao converter video");
processConsoleOut(videoProcess.getErrorStream());
}
if (code == 0) {
imageMediaProcess = Runtime.getRuntime().exec(commandCropImagemMedia);
int imageMediaCode = imageMediaProcess.waitFor();
if (imageMediaCode == 0) {
logger.debug("Imagem media gerada com sucesso.");
} else {
logger.error("Erro ao gerar imagem media." );
processConsoleOut(imageMediaProcess.getErrorStream());
}
}
logger.debug(" Executando:"  + commandCropImagemMicro);
if (code == 0) {
imageMicroProcess = Runtime.getRuntime().exec(commandCropImagemMicro);
int imageMicroCode = imageMediaProcess.waitFor();
if (imageMicroCode == 0) {
logger.debug(" Imagem micro gerada com sucesso." );
} else {
logger.error(" Erro ao gerar imagem micro." );
processConsoleOut(imageMicroProcess.getErrorStream());
}
}
// codigo de saida igual a zero significa sucesso.
if (code == 0) {
// aqui voce pode notificar o usuário que solicitou ao conversão do vídeo
// de que o mesmo ja se encontra disponível, ou tratar de alguma forma
// a conversao com sucesso.
}
} catch (Exception e) {
e.printStackTrace();
}
midiasAProcessar.remove(0);
}
}
}
};
private static VideoConverter instance;

private VideoConverter() {
init();
}

/**
* Inicializa o mecanizmo de conversão de videos.
*/
private void init() {
new Thread(worker).start();
logger.info(" Mecanizmo de vídeos inicializado com sucesso." );
}

/**
* Retorna uma instância singleton do conversor de vídeos.
* @return Instancia singleton VideoConverter
*/
public static synchronized VideoConverter getInstance() {
if (instance == null) {
instance = new VideoConverter();
}
return instance;
}
/**
* Formato MPEG
*/
public static final String MPEG_FORMAT = " mpeg";
/**
* Formato MPG
*/
public static final String MPG_FORMAT = "mpg";
/**
* Formato AVI
*/
public static final String AVI_FORMAT = "avi";
/**
* Formato MOV
*/
public static final String MOV_FORMAT = "mov";

/**Formato WMV **/
public static final String WMV_FORMAT = "wmv";
/**
* Representa o comando para conversão de vídeos AVI
* {0} = Caminho completo do arquivo em disco ( vídeo de entrada).
* {1} = Caminho completo com extenção do arquivo de saída.
* Importante: para otimizar qualidade apenas modifique os parâmetros -qmin e -qmax
* -qmin 0 a 30
* -qmax 0 a 30
* Em todas as hipotezes -qmax deve ser um valor maior que -qmin, -qmin reduz o tamanho do video
* e -qmax assegura manter a qualidade, portando deve ser alto e alterar prefeferivelmente -qmin.
*/
private static final String COMMAND_AVI = "ffmpeg -i   {0}  -qmin 6 -qmax 26 -ar 22050 -f flv -y  {1}";
/**
* Representa o comando para converter um vídeo no formato MOV para FLV.
* {0} = Caminho completo do arquivo em disco ( vídeo de entrada).
* {1} = Caminho completo com extenção do arquivo de saída.
* Apenas altere os parâmetros -qmin e -qmax.
* -qmin 0 a 30
* -qmax 0 a 30
* -s significa o tamanho da saida do vídeo, resolução.
*/
private static final String COMMAND_MOV = " ffmpeg -y -i  {0}  -vcodec flv -b 400kb -s 500x376 -qmin 10 -qmax 30 -ar 22050 -ac 1  {1} ";
/**
* Representa o comando para converter um vídeo no formato MPG ou MPEG para FLV.
* {0} = Caminho completo do arquivo em disco ( vídeo de entrada).
* {1} = Caminho completo com extenção do arquivo de saída.
* -r frames por segundo
* -s tamanho de saida do vídeo.
*/
private static final String COMMAND_MPG_MPEG = " ffmpeg -i  {0}  -y -r 25 -b 500k -ar 22050 -s 500x376 -ab 24k  {1}";
/**
* Representa o comando para efetuar o crop da imagem media de um vídeo.
* {0} = Caminho completo do arquivo em disco ( vídeo de entrada).
* {1} = Caminho completo com extenção do arquivo de saída.
* -itsoffset representa o segundo em que será efetuado o crop
* -vframes significa o respectivo frame para o segundo expecificado acima
* -s tamanho da imagem. Só aceita medidas pares.
*/
private static final String COMMAND_CROP_VIDEO_IMAGEM_MEDIA = "ffmpeg  -itsoffset -10  -i  {0}  -vcodec mjpeg -vframes 1 -an -f rawvideo -s 130x96 -y {1}";
/**
* Representa o comando para efetuar o crop da imagem pequena de um vídeo.
* {0} = Caminho completo do arquivo em disco ( vídeo de entrada).
* {1} = Caminho completo com extenção do arquivo de saída.
* -itsoffset representa o segundo em que será efetuado o crop
* -vframes significa o respectivo frame para o segundo expecificado acima
* -s tamanho da imagem. Só aceita medidas pares.
*/
private static final String COMMAND_CROP_VIDEO_MICRO_IMAGEM = "ffmpeg  -itsoffset -10  -i  {0}  -vcodec mjpeg -vframes 1 -an -f rawvideo -s 60x46 -y {1}";

/**
* Representa comando de conversão de videos WMV para FLV.
* {0} = Caminho completo do arquivo em disco ( vídeo de entrada).
* {1} = Caminho completo com extenção do arquivo de saída.
* -s = Tamanho do vídeo ( resolução), só aceita medidas pares.
* -qmin e -qmax são parâmetros de qualidade.
* -qmin 0 a 30
* -qmax 0 a 30
*/
private static final String COMMAND_WMV = "ffmpeg -y -i {0} -ar 22050 -ab 32 -s 500x376 -qmin 6 -qmax 26 -f flv -s 320x320 {1}";

/**
* Gera o comando expecífico de acordo com a extenção do arquivo.
* @param videoIn Caminho de entrada do vídeo.
* @param videoOut Caminho de saída do video.
* @return O comando formatado.
*/
private static String command(String videoIn, String videoOut) {
String command = &amp;amp;quot;&amp;amp;quot;;
String fileName = videoIn.substring(videoIn.lastIndexOf("/") + 1, videoIn.length());
String extension = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
// formato AVI
if (extension.equalsIgnoreCase(AVI_FORMAT)) {
command = new MessageFormat(COMMAND_AVI).format(new Object[]{videoIn, videoOut});
// formato MOV
} else if (extension.equalsIgnoreCase(MOV_FORMAT)) {
command = new MessageFormat(COMMAND_MOV).format(new Object[]{videoIn, videoOut});
// formato MPEG ou MPG
} else if (extension.equalsIgnoreCase(MPEG_FORMAT) || extension.equalsIgnoreCase(MPG_FORMAT)) {
command = new MessageFormat(COMMAND_MPG_MPEG).format(new Object[]{videoIn, videoOut});
// formato WMV
}else if(extension.equalsIgnoreCase(WMV_FORMAT)){
command = new MessageFormat(COMMAND_WMV).format(new Object[]{videoIn, videoOut});
}else {
throw new IllegalArgumentException("Formato de arquivo não suportado:"+extension);
}
return command;
}

/**
* Processa o stream de saída de erros caso alguma conversão não tenha ocorrido com sucesso.
* @param is InputStream
* @throws java.io.IOException Em caso de excessão de IO
*/
private void processConsoleOut(InputStream is) throws IOException {
byte[] buffer = new byte[512];
int bytes = 0;
StringBuffer stringBuffer = new StringBuffer();
do {
buffer = new byte[512];
bytes = is.read(buffer);
if (bytes > 0) {
stringBuffer.append(new String(Arrays.copyOf(buffer, bytes)));
}
} while (bytes >= buffer.length);
String output = stringBuffer.toString();
logger.error(output);
}

/**
* Gera o comando de crop para uma imagem tamanho médio.
* IMPORTANTE: Altere o tamanho para geraçao da sua imagem média para -s alturaXlargura
* @param videoOut Caminho do vídeo.
* @return Comando formatado para crop de imagem média.
*/
private static String commandImagemMedia(String videoOut) {
String imagenOut = videoOut + ".media.jpeg";
String commandImagemMedia = new MessageFormat(COMMAND_CROP_VIDEO_IMAGEM_MEDIA).format(new Object[]{videoOut, imagenOut});
return commandImagemMedia;
}

/**
* Gera o comando de crop para uma imagem micro.
* @param videoOut O caminho completo do vídeo de entrada.
* @return O Comando de crop de imagem micro formatado.
*/
private static String commandImagemMicro(String videoOut) {
String imageOut = videoOut + ".micro.jpeg";
String commandImagemMicro = new MessageFormat(COMMAND_CROP_VIDEO_MICRO_IMAGEM).format(new Object[]{videoOut, imageOut});
return commandImagemMicro;
}

}

Espero ajudar de alguma forma e que você tenha sucesso em suas conversões de vídeos.

Qualquer dúvida ronaldo at ronaldorigoni.com.br

Monday, May 25, 2009Google Developers Day 2009

Ola galera,

Lançameto do Google Developer Day, acontecerá em SP dia 29/06. Para maiores informações e inscrições acesse http://code.google.com/intl/pt-BR/events/developerday/2009/about.html

Uma das palestras ocorridas em 2008, outras palestras estão disponíveis em http://www.youtube.com/googlebrasil .

vagas limitadas e inscrição gratuita. Eu vou.

e você?

Ronaldo

Tuesday, May 12, 2009Desmistificando o Google Analytics

Com Engenheiro de Vendas do Google na América Latina Rodrigo Vale, video onde explica passo a passo uso e implantação. Quem conhece a ferramente sabe o valor dos dados que ela traz.
Fica ali a dica.

Até um próximo post.

Ronaldo

Monday, May 11, 2009Singleton em Java

Simples post para demonstrar o uso do Design Pattern Singleton em Java.

Assegura que uma determinada classe tenha apenas uma única instância, e apenas um meio de acesso a mesma.

Por exemplo em um sistema operacional é o uso de impressoras, temos vários clientes conectados a impressora, mas a mesma possui apenas um spool de documentos.

Segundo Gof temos o diagrama abaixo:

singleton_gofE como codificar isso? Imagine um cenário seguinte, precisamos desenvolver um chat entre múltiplos clientes,

onde todos podem se comunicar com todos, de que forma podemos implementar e assegurar que todos os usuários do nosso chat acessem exclusivamente a mesma instância da classe?


package br.com.ronaldorigoni.singleton;
/**
* Exemplo de Singleton - ChatRoom
* @author ronaldorigoni.com.br
*/
public class ChatRoom {
/**
* Única instancia da sala de bate papo, deve ser privada para
* não permitir acesso direto.
*/
private static ChatRoom instance = null;

/**
* Construtor privado para não permitir a
* criação de novas instancias da classe.
*/
private ChatRoom(){
// aqui codigo para a inicialização do chat.
// verifica configurações em banco ou outras
// operações.
}
/**
* Única forma de acesso para a instancia &amp;amp;amp;amp;quot;instance&amp;amp;amp;amp;quot; da classe
* @return  - A única instância da sala de Chat
*/
public static ChatRoom getInstance(){
if(instance == null)
instance = new ChatRoom();
return instance;
}

}

Através do método getInstance(), qualquer cliente tem apenas uma forma de acesso e em uma única instancia da sala de Chat.

Agora criaremos um método writeMessage(String message):


/**
* Envia uma mensagem a todos os usuários
* @param message
*/
public void writeMessage(String message){
for(User user : userList){
user.sendMessage(message);
}
}

Mesmo o método sendo public, só conseguiremos acessar ele através de uma instancia da classe desta forma:


ChatRoom.getInstance().writeMessage("Mensagem de teste");

Mas cuidado, em um ambiente MultiThread não está seguro que existe apenas uma instancia da classe, se em um determinado momento duas threads acessarem o método getInstance() ao mesmo tempo será criada duas instancias.

Como bloquear isso?  Adicionaremos um bloco s no método getInstance(), desta forma garantimos que toda Thread que entrar neste bloco irá bloquear o objeto instance para si mesmo, e só fará o desbloqueio quando o bloco de código terminar, assim uma vez desbloqueado estará disponível para uma nova thread utilizar.


public static ChatRoom getInstance() {

// garante que apenas uma thread por vez

// executará este bloco de código
synchronized (instance) {
if (instance == null)
instance = new ChatRoom();
}
return instance;
}

Para deixarmos nosso exemplo mais seguro, muitos desenvolvedores acham este método bizarro, mas eu o utilizei varias vezes, altere o método getInstance() para o modificador de acesso private, em seguida crie uma classe interna conforme abaixo, esta classe deve ser public static abstract, mas espere aí? Classe estatica é possível? Sim classe static é possível desde que a mesma seja aninhada ( classe interna).


private static ChatRoom getInstance() {
synchronized (instance) {
if (instance == null)
instance = new ChatRoom();
}
return instance;
}

E agora nossa classe interna aninhada:


public abstract static class ChatRoomHolder{
public static ChatRoom getInstance(){
return ChatRoom.getInstance();
}
}

Repare que a classe é abstrata, com isso fica impossível instanciá-la, mas por ela ser interna ela tem acesso aos métodos private de nossa ChatRoom, invoca o método getInstance() e retorna a instancia criada no construtor.

Desta forma estamos garantindo de duas maneiras que a classe ChatRoom tenha apenas uma única instancia:

  • Com o uso de construtor privado e um bloco sincronizado no método getInstance().
  • Com o uso de uma classe abstrata interna que não permite ser instanciada por ser abstrata.

Singleton pode ser aplicado em diversos casos, ( conexão a banco de dados, mecanismo de Log, Objetos de configuração, etc…).

Dúvidas, ronaldo@ronaldorigoni.com.br

Até um próximo post.

Saturday, May 9, 2009Como Instalar ISPCONFIG Ubuntu

Salve galera,
Hoje tive uma experiência ótima que me proporcionou um bom conhecimento na área de hospedagem, tive que instalar uma suíte de aplicativos de controle de hospedagem em um servidor dedicado recém formatado onde irá hospedar um projeto de Jogos de Bingo no qual estamos desenvolvendo para a 58 Pixels . A suíte de aplicativos que utilizei é chamada ISPCONFIG ( Apache2, Mysql, PhpMyAdmin, RoudCube, WebMiau, SVN, PostgreSQL,dentre outros). Se trata de uma solução completa em termos de hospedagem e o melhor de tudo é FREE,  fica alí minha indicação.

Vou relatar abaixo todas as bibliotecas necessárias para instalação deste Hosting Manager, varias delas não está n0 manual de instalação , se não instalar todas elas a instalação tem grandes chances de erro (passei por isso).

Importante: A instalação foi feita em Ubuntu Server 8.04 (Hardy Heron) 64bits.

Antes de instalar, siga os passos no  manual de instalação para a adição dos repositórios e para a atualização do servidor. Após isso siga os passos no manual de instalação e voe lá.

Bibliotecas necessárias:

proftpd libhtml-parser-perl libdb-file-lock-perl libnet-dns-perllibssl-dev

libxml2 openssl libmysql++-dev sendmail procmail quota vsftpd

openssl bind9 apache2 subversion apache2 libapache2-svn php5

libapache2-mod-php5 flex g++

Valeu e até um próximo post,  dúvidas ronaldo@ronaldorigoni.com.br

Ronaldo

Monday, May 4, 2009Observer em Java

Salve galera.
Hoje iremos abordar um padrão de projeto designado Observer.
Observer é um padrão de projeto que se caracteriza em uma associação 1 x N entre objetos. Em que quando um dos objetos muda de estado ou característica, todos os objetos relacionados sejam notificados.
Um objeto do lado 1 da relação deve permitir acesso a seus elementos sem que sua estrutura interna seja exposta.
Como podemos implementar de uma forma abstrata e sem acoplamento a relação, sendo que qualquer alteração no objeto todos os seus relacionados sejam notificados  e que eles não se conhecem em tempo de compilação?

Devemos ter em mente os seguintes requisitos:

  • Os objetos relacionados devem conhecer o objeto de interesse, mas não devem estar
  • O objeto de interesse ( lado 1 da relação), deve notificar os objetos relacionados e interessados sobre ocorrência modificações.

Deve ser implementado de uma forma que os objetos não se conheçam em tempo de compilação, possibilitando acoplamento em tempo de execução e desfazê-lo a qualquer momento.

Aplicabilidade do padrão é quando necessitamos encapsular dois objetos por uma interface em que eles não se conheçam, sendo assim eles ficam completamente desacoplados, possibilitando o reuso separadamente de cada objeto.

Bem, vamos ao que interessa.

Imagine a seguinte situação de envio de email.

  • O remetente deseja saber quando o email chegou a caixa postal do destinatário e também saber quando o email foi lido.
  • O destinatário deseja saber quando chegou um novo email na caixa postal.

Como podemos implementar isso sem que destinatário e remetente estejam acoplados?

Primeiramente criaremos uma classe EmailEvent que representará os eventos ocorridos sobre um email.


package br.com.ronaldorigoni.observer;

/**
* Classe EmailEvent, representa todo e qualquer evento ocorrido com um email.
* Classe herda de java.util.EventObject para podermos manipular o método
* getSource(), que nos devolve o email que ocorreu o evento.
*
* @author ronaldorigoni.com.br
*/
public class EmailEvent extends java.util.EventObject {

public EmailEvent(Object source) {
super(source);
}

}

Em seguida deveremos criar uma interface EmailListener que será implementada pelo Remetente e Destinatário, que será através dela que eles serão notificados.


package br.com.ronaldorigoni.observer;

/**
* Interface de Listeners para email.
* @author ronaldorigoni.com.br
*
*/
public interface EmailListener {
public void emailRecebido(EmailEvent emailEvent);
public void emailLido(EmailEvent emailEvent);
}

Agora criaremos a classe Email, conforme abaixo.


package br.com.ronaldorigoni.observer;

import java.util.ArrayList;
import java.util.Collection;

/**
* Representa um email.
*
* @author ronaldorigoni.com.br
*
*/
public class Email {
/**
* Lista de objetos interessados nos eventos ocorridos com um email.
*/
private Collection&amp;amp;amp;amp;amp;lt;EmailListener&amp;amp;amp;amp;amp;gt; emailListeners =
new ArrayList&amp;amp;amp;amp;amp;lt;EmailListener&amp;amp;amp;amp;amp;gt;();

/**
* Envia um email.
*
* @param email
*/
public void enviar() {
disparaEmailRecebido();
disparaEmailLido();
}

/**
* Dispara um evento quando este email for recebido.
*/
public void emailRecebido() {
this.disparaEmailRecebido();
}

/**
* Dispara um evento quando este email for lido.
*/
public void emailLido() {
this.disparaEmailLido();
}

/**
* Método sincronizado para impedir que seja adicionado o mesmo listener
* mais de uma vez.
*
* @param emailListener
*            - o Listener
*/
public synchronized void addEmailListener(EmailListener emailListener) {
this.emailListeners.add(emailListener);
}

/**
* Remove um listener.
*
* @param emailListener
*            O listener.
*/
public synchronized void removeEmailListener(EmailListener emailListener) {
this.emailListeners.remove(emailListener);
}

/**
* Notifica todos os listeners que um email foi lido.
*/
private void disparaEmailLido() {
// criando um evento do próprio email
EmailEvent event = new EmailEvent(this);
// iterando sobre todos os listeners
// para notificar evento de email lido.
for (EmailListener listener : emailListeners) {
listener.emailLido(event);
}
}

/**
* Notifica todos os listeners que um email foi recebido.
*/
private void disparaEmailRecebido() {
// criando um evento do próprio email
EmailEvent event = new EmailEvent(this);
// iterando sobre todos os listeners
// para notificar evento de email recebido.
for (EmailListener listener : emailListeners) {
listener.emailRecebido(event);
}
}

}

Em seguida criaremos as classes Remetente e Destinatario, que deverão implementar a interface EmailListeners pois se tratam de interessados nos eventos ocorridos sobre os emails.


package br.com.ronaldorigoni.observer;

/**
* Representa um destinatário para um email.
* @author ronaldorigoni.com.br
*
*/
public class Destinatario implements EmailListener {

@Override
public void emailLido(EmailEvent emailEvent) {
Email email = (Email) emailEvent.getSource();
System.out.println(&amp;amp;amp;amp;amp;quot;DESTINATÁRIO: Você acabou de ler o email:&amp;amp;amp;amp;amp;quot; + email + &amp;amp;amp;amp;amp;quot;.&amp;amp;amp;amp;amp;quot;);
}

@Override
public void emailRecebido(EmailEvent emailEvent) {
Email email = (Email) emailEvent.getSource();
System.out.println(&amp;amp;amp;amp;amp;quot;DESTINATÁRIO: Você acabou de receber um novo email:&amp;amp;amp;amp;amp;quot;+email);
}

}

E a classe Remetente.


package br.com.ronaldorigoni.observer;

/**
* Representa um
* @author ronaldorigoni.com.br
*
*/
public class Remetente implements EmailListener {

@Override
public void emailLido(EmailEvent emailEvent) {
Email email = (Email) emailEvent.getSource();
System.out.println(&amp;amp;amp;amp;amp;quot;REMETENTE: O email &amp;amp;amp;amp;amp;quot; + email + &amp;amp;amp;amp;amp;quot; acaba de ser lido.&amp;amp;amp;amp;amp;quot;);
}

@Override
public void emailRecebido(EmailEvent emailEvent) {
Email email = (Email) emailEvent.getSource();
System.out.println(&amp;amp;amp;amp;amp;quot;REMETENTE: O email &amp;amp;amp;amp;amp;quot; + email
+ &amp;amp;amp;amp;amp;quot; acaba de ser recebido pelo destinatário.&amp;amp;amp;amp;amp;quot;);
}

}

Agora criaremos a classe CaixaPostal que é onde a nossa brincadeira acontece.


package br.com.ronaldorigoni.observer;

/**
* Representa uma caixa postal para teste sobre emails.
* @author ronaldorigoni.com.br
*
*/
public class CaixaPostal {
public static void main(String[] args) {
// criando dois emails
Email email1 = new Email();
Email email2 = new Email();
// criando destinatário e remetente
Destinatario destinatario = new Destinatario();
Remetente remetente = new Remetente();
// Adicionaodo os listeners aos emails
email1.addEmailListener(destinatario);
email1.addEmailListener(remetente);

email2.addEmailListener(destinatario);
email2.addEmailListener(remetente);
/**
* Aqui é onde tudo acontece, brinque com os codigos abaixo,
* comente e descomente as linhas.
*/
email1.enviar();

email2.emailRecebido();
//email2.emailLido();
}
}

Execute o arquivo CaixaPostal.java e veja que quando um email é enviado tanto o remetente quando o destinatário são notificados nenhum conhece a implementação do outro.

Segue aqui o aqui para download do projeto

Bom por hoje fico por aqui, qualquer duvidas ou sugestões segue meu email ronaldo@ronaldorigoni.com.br

Até um próximo post.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes

© 2007 Ronaldo Rigoni | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress