Crux Framework Manual - 5.2 - Crux Framework

Transcrição

Crux Framework Manual - 5.2 - Crux Framework
Crux Framework Manual - 5.2
Capítulo 1. Introdução
Bem vindo ao manual do Crux Framework.
Manual referente a versão 5.2 do Crux Framework.
1.1. Apresentação
O Crux é um framework para desenvolvimento de aplicações web que necessitam de um desempenho superior, alta escalabilidade e suporte para múltiplos dispositivos, como desktops, tablets,
smartphones e TVs. Ele foi projetado para tirar proveito da velocidade e da facilidade do desenvolvimento de aplicações corporativas utilizando HTML5, sendo um verdadeiro framework para Web 2.0.
O Crux Framework é opensource e licenciado sobre a Apache License 2.0.
Utilizando o Crux o desenvolvedor codifica em Java e define as páginas utilizando XHTML. A aplicação terá o seu código fonte convertido para JavaScript, em tempo de compilação, sendo gerada uma
versão otimizada para cada um dos navegadores modernos. A grande vantagem desta abordagem é que o código de controle da interface é executado no lado do cliente. Não é necessário
comunicação com o servidor para manutenção de telas e o servidor também não precisa manter estado das telas. Assim, o consumo de recursos no servidor e de rede reduz significativamente
possibilitando uma maior escalabilidade da aplicação.
Oferecendo um conjunto de componentes visuais ricos e flexíveis, o Crux também permite o desenvolvimento de aplicações que proporcionam uma boa experiência para o usuário. Além disso o Crux
oferece nativamente suporte a diversas APIs, como: para trabalho off-line e acesso a serviços REST.
Neste manual é apresentado esse poderoso framework através de exemplos que ilustram os recursos disponíveis. Desta forma é possível entender os conceitos aplicados no Crux e como explorá-los
da melhor forma.
O Crux foi construído sobre o GWT versão 2.6.1. Consulte a documentação do Google Web Toolkit (GWT) caso não esteja familiarizado com o seu uso.
1.2. Exemplos
Nesta seção são apresentados exemplos de aplicações feitas com o Crux Framework.
1.2.1. Primeiro Projeto Crux
O exemplo a seguir ilustra como criar uma página HTML simples com o Crux e como interagir usando um Java controller. Este é o exemplo gerado pelo Quickstart do Crux.
Esta é uma screen, que será o ponto de entrada da aplicação.
Exemplo 1.1. Screen Crux (index.crux.xml)
1 <!DOCTYPE html>
2 <html
3
xmlns="http://www.w3.org/1999/xhtml"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt">
7
<head>
8
<title>My App</title>
9
</head>
10
<body>
11
<script type="text/javascript" src="../myapp/myapp.nocache.js"></script>
12
<core:screen
13
useView="*"
14
smallViewport="user-scalable=no, width=320"
15
largeViewport="user-scalable=no, width=device-width, height=device-height"/>
16
<crux:simpleViewContainer id="views">
17
<crux:view name="main"/>
18
</crux:simpleViewContainer>
19
</body>
20 </html>
Esta é uma view, que nada mais é que uma página Web que foi invocada a partir da screen acima:
Exemplo 1.2. View Crux (main.view.xml)
1 <v:view
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:v="http://www.cruxframework.org/view"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
7
useResource="xStandardResources"
8
useController="mainController"
9
onLoad="mainController.onLoad">
10
11
<crux:storyboard id="mainPanel" style="display:block; margin: 50px">
12
<gwt:textBox id="nameTextBox"/>
13
<crux:button id="okButton" text="Go!" onSelect="mainController.sayOk"/>
14
</crux:storyboard>
15
16 </v:view>
Este é o Controller, presente no lado do Cliente:
Exemplo 1.3. Controller Crux (MainController.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Controller("mainController")
public class MainController
{
@Inject
public MainView screen;
@Expose
public void onLoad()
{
//do something
}
@Expose
public void sayOk()
{
FlatMessageBox.show("OK : " + screen.nameTextBox().getText() , MessageType.INFO);
}
@BindView("main")
public static interface MainView extends WidgetAccessor
{
TextBox nameTextBox();
}
}
A seguir são apresentadas as imagens das telas do exemplo em execução em um desktop ou notebook.
Figura 1.1. Primeira Tela Desktop
Tela da aplicação exemplo executada em Desktop.
Figura 1.2. Segunda Tela Desktop.
Tela da aplicação exemplo executada em Desktop quando alerta é acionado.
A seguir são apresentadas as imagens das telas do mesmo exemplo em execução no smartphone.
Figura 1.3. Primeira Tela Mobile
Tela da aplicação exemplo executada no dispositivo mobile (Android).
Figura 1.4. Segunda Tela Mobile
Tela da aplicação exemplo executada em dispositivo mobile (Android) quando o alerta é acionado
Note que podemos customizar o estilo CSS e o posicionamento das widgets na tela, dependendo do dispositivo onde a aplicação será executada.
1.2.2. Media Manager
O objetivo do Media Manager é servir de exemplo para desenvolvedores que querem conhecer mais sobre o Crux, seu funcionamento e sua integração com tecnologias de backend. Visite o no site do
Media Manager e saiba mais a respeito.
O Media Manager é uma aplicação de controle de mídias. Com ele é possível cadastrar seus CDs e DVDs e controlar se eles estão emprestados. Veja a aplicação em execução: Media Manager.
1.2.3. Showcase Crux
O showcase do Crux apresenta uma série de exemplo de utilização de componentes do Crux. Para conhecer o funcionamento dos componentes do Crux, em uma aplicação real, pode ser visitada a
página do Showcase 5.
Além dos exemplos e códigos que são exibos, o showcase completo está disponivel para download no Google Code.
Capítulo 2. Visão Arquitetura
Neste capítulo é dada um visão geral da arquitetura do Crux Framework, de seus artefatos e do seu modelo de suporte a aplicações cross-device.
2.1. Crux MVC
O Crux possui uma implementação de MVC (Model, View e Controller) inteligente e de fácil utilização.
A camada de visão é definida através dos elementos de Screen, Templates e Views. Todos eles são arquivos XML onde a interface é codificada de forma declarativa. O capítulo Camada de Visão
detalha como codificar arquivos desta camada.
A camada de controle da interface no Crux, está localizada no cliente e não no servidor como é usual nos frameworks. Esta estratégia garante um desempenho consideravelmente superior às
arquiteturas convencionais. O tráfego pela rede somente transmite dados e não páginas HTML inteiras. O capítulo Camada de Controle detalha como codificar arquivos desta camada.
A figura a seguir ilustra a disposição de cada camada no modelo cliente-servidor adotado pelo Crux em comparação ao adotado por outros frameworks com o controle no servidor.
Figura 2.1. Controller do lado do Cliente
Na camada de modelo são definidas as regras de negócio e o controle de acesso aos dados. No exemplo dado acima, ela é composta por serviços no servidor. É possível termos objetos da camada
de modelo no cliente. Existem aplicações que tem essa necessidade, para permitir o trabalho offline. O capítulo Camada de Modelo detalha como codificar arquivos desta camada.
A figura a seguir ilustra a arquitetura do Crux, com suas camadas, seus principais componentes e modo de interação entre eles.
Figura 2.2. Visão geral da arquitetura
Detalhando a figura temos:
1. A screen é uma página que contém um ou mais containers de views. Cada container gerencia as suas views que, por sua vez, representam uma tela da aplicação. Por exemplo, podemos ter a
view de busca de um usuário, a view de o cadastro de um fornecedor ou qualquer outra view desejada.
A screen e as views são arquivos XHTML que aceitam tags de diversas bibliotecas declaradas em seus cabeçalhos, dentre elas, tags do GWT, do Crux ou mesmo templates personalizados.
2. Os controllers são classes escritas em Java que realizam a gestão dos componentes da tela. Desde pequenas validações (independentes de regras de negócio) até as chamadas de funções do
servidor. Quando a aplicação é compilada as classes Java são convertidas em javascript para serem executadas no cliente.
A relação de comunicação entre as views e as controllers é de muitas para muitas (NxN), ou seja, uma view pode comunicar com vários controllers e, dado um controller, este pode estar
associado a várias views. A comunicação entre o cliente e servidor parte da controller em direção à camada de modelo, e pode ser feita através de RPC ou REST.
3. Os services são classes Java responsáveis pela lógica de negócio da aplicação. A partir deste ponto, o arquiteto pode escolher a tecnologia que melhor se enquadra em seu
contexto/necessidade. Por exemplo, pode-se utilizar as classes services como dispatchers para EJBs, que irão processar a lógica de negócio da aplicação. Ou mesmo, caso uma arquitetura
mais simples seja desejada, as próprias classes services podem processar as regras de negócio, já que elas são instanciadas e chamadas através de servlets gerenciados pelo Crux.
2.2. Crux Artefatos
A distribuição do Crux inclui um conjunto de bibliotecas (JARs) do core do Crux e as bibibliotecas de componentes. A distribuição padrão do Crux inclui o seguinte conjunto de bibliotecas (JARs):
crux-runtime.jar - contém as classes de runtime (pertencem ao war final da aplicação).
crux-dev.jar - contém as classes para a compilação do projeto.
A partir da versão 5.1 foi adotado o Maven para a compilação e e montagem das bibliotecas do Crux. Os repositórios no SVN foram separados por projeto. O projeto crux-core possui apenas o POM e
os arquivos .launch.
A antiga biblioteca de temas do Crux (crux-themes.jar) não existe mais nesta versão, pois foi incorporada às bibliotecas de componentes, como o crux-widgets e crux-smart-faces. Esta estratégia deve
ser adotada para cada bibliotecas de componentes, isto é, cada biblioteca terá os seus próprios temas.
As bibliotecas de componentes do Crux são:
crux-widgets.jar - Biblioteca de componentes de tela padrões do Crux. Esta biblioteca contém as implementações compatíveis com browsers antigos.
crux-smart-faces.jar - Nova biblioteca de componentes do Crux. Essa biblioteca somente garante compatíbilidade com browsers mais modernos, além de adotar padrões como ser tableless.
crux-smart-gwt.jar - Versão da biblioteca Smart-GWT para o Crux.
Além das bibliotecas existem plugins para o Crux. Consulte o projeto Crux Plugins para maiores informações.
Alguns plugins disponíveis:
crux-zooming-tools.jar - adiciona a opção de aplicar zoom em qualquer widget presente na tela.
crux-gadgets.jar e crux-gadgets-runtime.jar - biblioteca de componentes acopláveis a diversos contextos, como por exemplo, em páginas do Google.
2.3. Suporte Cross-device
Uma das grandes inovações do Crux é oferecer suporte Cross-device, isto é, o Crux permite ao criar uma nova aplicação abstrair da maioria dos problemas de se construir aplicações para diversos
dispositivos. Para isso, ele oferece um toolbox completo que se alimenta em parte dos benefícios que o próprio GWT proporciona. Através de um mecanismo para construção de telas específicas para
cada tipo de dispositivo e de componentes otimizados e customizados, o Crux oferece suporte específico para vários tipos de dispositivos. Através da propriedade user.agent é possível saber qual o
tipo de dispositivo e qual o browser.
Para diferenciar os dispositivos são considerados dois fatores principais: tamanho da tela e modo de interação. O tamanho da tela pode ser pequeno (smartphones) ou grande (televisões,
computadores e tablets). O modo de interação pode ser através de "setas" (teclado, controle remoto), através de mouse ou touchscreen. Para as combinações de tamanho e modo de interação
existem as seguintes opções:
smallDisplayArrows: dispositivos pequenos e que oferecem uma interação através das setas do seu input padrão.
smallDisplayTouch: dispositivos pequenos e que oferecem uma interação através de touchscreen.
largeDisplayArrows: dispositivos grandes e que oferecem uma interação através das setas do seu input padrão.
largeDisplayTouch: dispositivos grandes e que oferecem uma interação através do touchscreen.
largeDisplayMouse: dispositivos grandes e que oferecem uma interação através do mouse.
A partir de cada um destes dispositivos, a aplicação pode ser acessada através de diferentes browsers. O GWT por sua vez gera uma versão para cada um dos tipos de browser listados a seguir:
IE8, IE9 e IE10: Internet Explorer e suas versões. IE10 representa os Internet Explorer 10 e 11.
Gecko1_8: Firefox e todas as suas versões.
Safari: browsers baseados em webkit, geralmente o Safari e Chrome.
Opera: Opera e todas as suas versões.
Como resultado final é gerada uma compilação para cada combinação de: tipo de dispositivo e browser. Ao acessar a aplicação de um dispositivo e browser específico, recebe-se apenas o código
para o ambiente utilizado, compilado e otimizado para ele.
Capítulo 3. Instalação e Configuração
Neste capítulo é apresentado como instalar e configurar o Crux através de archetypes do Maven e manualmente em qualquer outro tipo de projeto no Eclipse ou outra IDE. Também são tratadas outras
opções de configuração do Crux.
Para que os exemplos desta documentação sejam feitos os seguintes softwares já devem estar devidamente instalados e configurados:
O Java SE Development Kit (JDK), versão 1.6 ou superior.
O Eclipse IDE (ou uma IDE de sua preferência).
3.1. Crux via Maven Archetype
O Crux pode ser instalado utilizando os Archetypes do Maven. Estes archetypes estão contidos em um catálogo que está disponível no Maven Central. Dentre os artefatos deste catálogo, são
destacados:
crux-helloworld-war - A aplicação de exemplo com o Crux Framework.
crux-project-jar - Um módulo de uma aplicação. É distribuída no formato de JAR.
crux-project-war - Esta é uma aplicação vazia container. É distribuída no formato WAR.
3.1.1. Quick Start
Aprenda a criar um projeto Crux em 5 minutos. Com o Maven é possível criar projetos Crux de forma rápida. Neste exemplo vamos mostrar como criar um projeto Hello World usando o Maven integrado
ao ambiente Eclipse (plugin M2E).
Veja aqui o QuickStart para o Crux Framework.
3.2. Instalando Crux Manualmente
Há também como instalar o Crux no seu projeto sem utilizar os archetypes do Maven. Geralmente, você precisará destes passos caso opte por não trabalhar com o Maven ou já tenha um projeto legado
onde queira instalar o Crux.
Para isso, siga os seguintes passos:
1. Adicionar os JARs do Crux no buildpath do seu projeto. Eles podem ser obtidos no site do Crux, na seção de Downloads. Vide seção Crux Artefatos
Os JARs crux-dev.jar e gwt-dev.jar devem ser utilizados apenas em tempo de compilação, então devem estar localizados em uma pasta build/lib que deve estar no classpath da aplicação.
Os jars crux-runtime.jar, gwt-runtime.jar e gwt-servlet.jar, devem ser utilizados em tempo de execução, então devem estar presentes na pasta WEB-INF/lib do war da aplicação.
2. Adicionar as seguintes entradas no web.xml.
O projeto deve ser codificado em UTF-8, o que também segue a recomendação do GWT de sempre trabalhar com arquivos .properties nesta codificação.
Exemplo 3.1. Configurar UTF-8 (web.xml)
1 <context-param>
2
<param-name>outputCharset</param-name>
3
<param-value>UTF-8</param-value>
4 </context-param>
Adicionar o listener InitializerListener, que será responsável por escanear as classes da aplicação e montar um mapa que será utilizado posteriormente durante o processamento do
Crux. É importante mencionar que em ambiente de produção este processamento não será realizado, uma vez que as classes já se encontram compiladas.
Exemplo 3.2. Configurar Listener (web.xml)
1 <listener>
2
<listener-class>org.cruxframework.crux.core.server.InitializerListener
3
</listener-class>
4 </listener>
Caso haja chamadas RPC na aplicação, deve ser adicionado o servlet do Crux RemoteServiceServlet, que é uma especialização do Servlet de mesmo nome do GWT, adicionando novas
funcionalidades como tokens de sincronização, pré-processadores, etc.
Exemplo 3.3. Configurar Servlet RPC (web.xml)
1
2
3
4
5
6
7
8
9
10
<servlet>
<servlet-name>remoteServiceServlet</servlet-name>
<servlet-class>org.cruxframework.crux.core.server.dispatch.RemoteServiceServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>remoteServiceServlet</servlet-name>
<url-pattern>*.rpc</url-pattern>
</servlet-mapping>
Caso haja chamadas REST na aplicação, deve ser adicionado o servlet do Crux RestServlet, que é responsável por processar as requisições REST vindas do servidor. Este servlet tratará
de diversas funcionalidades de segurança, deserialização dos objetos e a eventual chamada do método correspondente na classe de serviço.
Exemplo 3.4. Configurar Servlet REST (web.xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<servlet>
<servlet-name>restServiceServlet</servlet-name>
<servlet-class>org.cruxframework.crux.core.server.rest.servlet.RestServlet
</servlet-class>
<init-param>
<param-name>preprocessors</param-name>
<param-value>XXXX.infrastructure.SecurityPreprocessor
</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>restServiceServlet</servlet-name>
<url-pattern>/rest/*</url-pattern>
</servlet-mapping>
Esta é uma configuração opcional e expõe um servlet para testes de views, onde o desenvolvedor pode informar apenas o nome do módulo e o nome da view na url e o Crux irá carregar e
devolver a view para o cliente.
Exemplo 3.5. Configurar Servlet de teste (web.xml)
1
2
3
4
5
6
7
8
9
<servlet>
<servlet-name>testViewServlet</servlet-name>
<servlet-class>org.cruxframework.crux.core.server.development.ViewTester
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>testViewServlet</servlet-name>
<url-pattern>/viewTester/*</url-pattern>
</servlet-mapping>
3. Devem ser adicionado um arquivo na pasta de source da aplicação.
Crux.properties: responsável por definir as configurações utilizadas pelo Crux. Exemplo:
Exemplo 3.6. Configurações do Crux (Crux.properties)
1
2
3
4
5
6
7
8
#Define qual será a classe responsável por processar as dependências do classpath.
classPathResolver=org.cruxframework.crux.core.server.classpath.ClassPathResolverImpl
#Indica que a estratégia de busca dos recursos deve se basear em um ambiente de cluster.
restServiceResourceStateHandler=org.cruxframework.crux.core.server.rest.state.ClusteredResourceStateHandler
#Indica se os prefixos de URLs de REST devem ser baseados no Host ao invés Módulo.
enableRestHostPageBaseURL=false
4. Deve ser adicionado um launcher para iniciar o codeserver. Este arquivo conterá as configurações para executar a aplicação através do SuperDevMode. Exemplo:
Exemplo 3.7. Configurar Launcher (app.launcher)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<<APPLICATION_NAME>>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/<<APPLICATION_NAME>>"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.m2e.
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/<<APPLICATION
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.cruxframework.crux.tools.codeserver.CodeServer"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-sourceDir src/main/java -webDir target/<<APPLICATION_NAME>> -moduleName <<APPLICATION_MODULE_NAME>>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="<<APPLICATION_NAME>>"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xss10768k -Xms1024M -Xmx2048M -XX:MaxPermSize=512m"/>
</launchConfiguration>
No exemplo o padrão <<APPLICATION_NAME>> deve ser substituído pelo nome da aplicação.
No exemplo o padrão <<APPLICATION_MODULE_NAME>> deve ser substituído pelo nome do módulo da aplicação.
5. Deve ser adicionado um launcher para iniciar o servidor de aplicação desejado. No caso do Jetty, segue o exemplo:
Exemplo 3.8. Configurar Launcher (app.launcher)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/<<APPLICATION_NAME>>"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="4"/>
</listAttribute>
<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true"/>
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true"/>
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry path=&quot;3&quot; projectName=&quot
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.m2e.
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/<<APPLICATION
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.m2e.launchconfig.classpathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.cruxframework.crux.tools.server.JettyDevServer"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-appRootDir target/<<APPLICATION_NAME>>"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="<<APPLICATION_NAME>>"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.m2e.launchconfig.sourcepathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xss10768k -Xms1024M -Xmx2048M -XX:MaxPermSize=512m -DCrux.dev=true"/>
</launchConfiguration>
No exemplo o padrão <<APPLICATION_NAME>> deve ser substituído pelo nome da aplicação.
6. Para gerar o WAR da aplicação, as seguintes ferramentas estão disponíveis:
CruxCompiler: Este é o compilador do Crux. Ele pode ser chamado via linha de comando, scripts MAVEN, ANT, etc. Abaixo segue um exemplo de um script ANT:
Exemplo 3.9. Script ANT CruxCompiler (build.xml)
1 <cruxcompiler srcDir="..." outputDir="..." webDir="..."
2
keepPagesGeneratedFiles="true" inputCharset="UTF-8" outputCharset="UTF-8">
3
4
<!-- VM parameters. For intancem if you need to modify memory settings -->
5
<jvmarg value="-Xmx2560M"/>
6
<jvmarg value="-Xms512M"/>
7
<jvmarg value="-XX:MaxPermSize=256m"/>
8
<jvmarg value="-XX:+UseParNewGC"/>
9
<jvmarg value="-XX:+UseConcMarkSweepGC"/>
10
<jvmarg value="-XX:+UseGCOverheadLimit"/>
11
12
<!-- GWT and CRUX parameters -->
13
<arg value="-gen"/>
14
<arg value="/temp/crux/gen"/>
15
<arg value="-style"/>
16
<arg value="DETAILED"/>
17
<arg value="-logLevel"/>
18
<arg value="DEBUG"/>
19
<arg value="-localWorkers"/>
20
<arg value="8"/>
21
<arg value="-draftCompile"/>
22 </cruxcompiler>
ServiceMapper: Esta é uma ferramenta para criar um mapa das interfaces remotas de serviço (RPC ou REST). Estes mapas serão armazenados em arquivos "crux-rest" e "crux-remote" e
são necessários durante a execução do código compilado. Abaixo segue um exemplo de um script ANT:
Exemplo 3.10. Script ANT ServiceMapper (build.xml)
1 <serviceMapper projectDir="...">
2
<classpath>
3
<path refid="build.classpath"/>
4
<pathelement path="..."/>
5
</classpath>
6 </serviceMapper>
3.3. Configurações
3.3.1. Crux.properties
Abaixo seguem os atributos de configuração do Crux, definidos no arquivo Crux.properties
Tabela 3.1. Atributos de configuração do Crux
Propriedade
Descrição
Valor default
classPathResolver
Define qual será a classe responsável por processar as dependências do classpath.
org.cruxframework.crux.core.server. /
classpath.ClassPathResolverImpl
enableRestHostPageBaseURL
Indica se os prefixos de URLs de REST devem ser baseados no Host ao invés Módulo
false
serviceFactory
Classe fábrica responsável por instanciar e devolver um determinado serviço REST dado o seu nome
org.cruxframework.crux.core.server. /
dispatch.ServiceFactoryImpl
localeResolver
Classe responsável por definir como Locale é retornado para o usuário corrente
org.cruxframework.crux.core.i18n. /
LocaleResolverImpl
screenResourceResolver
Classe responsável por definir de onde uma determinada Screen é lida
org.cruxframework.crux.core. /
declarativeui.DeclarativeUIScreenResolver
enableHotDeploymentForWebDirs
Habilita o hotdeploy para artefatos que estão localizados na pasta web da aplicação
true
scanAllowedPackages
Indica quais pacotes são permitidos para serem escaneados durante os class scanners
-
scanIgnoredPackages
Indica quais pacotes devem ser ignorados ao serem escaneados durante os class scanners
-
enableGenerateHTMLDoctype
Indica se deve ser gerado o cabeçalho "!DOCTYPE" no topo da página
true
cruxCompilationTempFolder
Indica qual o nome da pasta ou o caminho completo onde o Crux armazenará os arquivos temporários para a
compilação da aplicação.
crux_comp
isRelativeCruxCompilationTempFolder Indica se o valor definido no parâmetro cruxCompilationTempFolder é relativo ao contexto (pasta de temporário do true
usuário) ou não.
preferWebSQLForNativeDB
Indica se deve ser utilizado o WebSQL ao invés do IndexedDB para navegadores que suportam ambas as APIs
false
renderWidgetsWithIDs
Indica se as widgets devem ser renderizadas com seus IDs
false
useCompileTimeClassScanning
ForDevelopment
Indica qual é a estratégia de localização das classes para a utilização no servidor. Caso esta opção esteja
habilitada, os arquivos que contém as classes devem ser previamente gerados através de diretivas de scanners.
false
restServiceResourceStateHandler
Indica qual será a classe tratadora para caches do REST. Este tratador é fundamental para um ambiente de cluster org.cruxframework.crux.core.server. /
e vai impactar na leitura correta dos valores da ETAG, data de modificação e data de expiração dos recursos
rest.state.ClusteredResourceStateHandler
enableResourceStateCacheFor
RestServices
Indica se será o tratador para caches do REST será habilitado ou não
true
disableRefreshByDefault
Caso habilitado, bloqueia o a utilização do botão F5, impossibilitando o recarregamento das páginas e a
consequente perda de estado da tela
false
restServiceFactory
Define a fábrica que construirá os serviços Rest a partir de suas classes originais. É útil quando queremos
instanciar classes através de um container específico, como por exemplo um container de IoC.
org.cruxframework.crux.core.server.rest. /
core.registry.RestServiceFactoryImpl
restErrorHandler
Define uma classe que irá tratar as exceções lançadas durante a execução de um serviço Rest.
org.cruxframework.crux.core.server.rest. /
core.dispatch.RestErrorHandlerImpl
sendCruxViewNameOnClientRequests Configura o crux para enviar, no cabeçalho Http, o nome da view que está chamando o serviço Rest.
false
scanIgnoredLibs
Define a lista de bibliotecas (arquivos .jar) que serão ignoradas quando os scanners do Crux varrerem o classpath
em busca de recursos. Ese parâmetro aceita wildcard.
-
scanAllowedLibs
Define a lista de bibliotecas (arquivos .jar) que serão incluidas quando os scanners do Crux varrerem o classpath
em busca de recursos. Ese parâmetro aceita wildcard..
-
3.3.2. Configuração de LOG
Log durante a compilação do módulo
Quando executada a diretiva install, os erros provenientes da compilação podem não aparecer no console. Nestes casos, é recomendável compilar a aplicação com um nível de LOG menos
restritivo, como o DEBUG.
Caso algumas classes que sofrem amarrações tardias não sejam encontradas durante o processo de compilação, verifique se as mesmas estão no padrão GWT-Cliente, ou seja, somente
possuem referências às classes no cliente e todas suas bibliotecas dependentes suportadas pelo compilador. Para ver o conjunto completo de classes que eventualmente apresentarem
problemas, adicione no arquivo de configuração <PROJETO>-module-build.xml o parâmetro valor <arg value="-strict"/>.
Esta diretiva obrigará o compilador do GWT parar caso encontre um erro de compilação, antes da adição deste parâmetro, a aplicação eventualmente funcionará, mas estas classes que
apresentam erro não ficam disponíveis no contexto para rebind. Logo, ao acessar uma página que dependa deste recurso, esta não funcionará. Vale lembrar que este parâmetro vai capturar
todos os erros de classes que contém problemas como "Classe server-side em seus imports" e algumas vezes, o determinado erro não é impeditivo para o funcionamento do sistema, então este
parâmetro somente deve ser ativado quando algum erro de compilação do GWT for detectado.
Caso o log ainda não esteja claro, provavelmente alguma biblioteca de log (como o log4j) está no classpath. Neste caso, esta biblioteca procurará por um arquivo de configuração, que geralmente
se localiza no root do seu projeto. Como a compilação do Crux acontece em uma pasta temporária, este deveria se localizar neste novo path, o que (por construção) não ocorre de forma
automática. Para tratar este caso, basta remover a biblioteca de log do classpath.
Log no DevMode
No DevMode, não há a diretiva "-strict", mas o log pode ser mais descritivo se adicionarmos os seguintes parâmetros para o launcher: logLevel TRACE -logdir <<DIRETORIO_DESEJADO>>.
Caso haja problemas na compilação, no <<DIRETORIO_DESEJADO>> procure pelos logs próximos à INFO "Compiling...".
3.4. Debug com Crux
O Crux/GWT converte todo o código Java do cliente para Javascript no momento de compilação. Para fazer o debug em tempo de desenvolvimento é necessário usar um recurso do GWT que irá
compilar o código para debug. Existem duas opções: o SuperDevMode e o DevMode.
O DevMode é baseado em um plugin que deve ser instalado no browser, porém este plugim não está sendo mais suportado pelos browsers atuais. Ele também é restrito a uso apenas com o Jetty.
Com o SuperDevMode o debug do GWT irá ficar independente do plugin e permitirá o debug do código java direto no Browser.
Vamos apresentar como usar as duas tecnologias, sendo o SuperDevMode a maneira recomendada de se trabalhar desenvolvendo com o Crux.
3.4.1. SuperDevMode
A feature super devmode do GWT foi adotada e estendida pelo Crux para tornar o desenvolvimento das aplicações mais intuitivo, ágil e robusto. Com esta nova feature, além dos benefícios já
apresentados em GWT SuperDevMode, foram inseridas novas funcionalidades, como:
Deploy automático do código recompilado diretamente para o servidor de aplicação (o servidor padrão é o Jetty, mas as configurações podem ser estendidas para quaisquer outros servidores);
Auto compilação das classes e demais recursos alterados durante o desenvolvimento: ao alterar qualquer recurso, o code server é automaticamente acionado e o browser utilizado ainda é
comunicado de que uma compilação está ocorrendo. Ao final do processo, o browser é atualizado para refletir o estado atual da página.
O debug das classes "client" através do Eclipse funciona naturalmente, assim como funcionava no antigo DevMode. Para esta funcionlidade, o Crux está apoiando e utilizando o plugin SDBG
Eclipse JavaScript debugger with Source maps support , que permite que o Chrome envie o SourceMaps diretamente para o Eclipse, onde este o interpreta e faz uma sincroniza com os
breakpoints cadastrados na IDE e o SourceMaps. Com isso, além da facilidade de se debugar todo o código dentro do Eclipse, ainda temos o vantagens como: termos uma representação fiel do
DOM na própria IDE, podermos debugar código JSNI, debugar aplicações rodando em outros dispositivos (como celulares), etc.
Configuração
Para habilitar o Crux SuperDevMode, basta abrir o arquivo de configuração do módulo .gwt.xml e acrescentar as linhas:
1 <!-- Crux SuperDevMode -->
2 <inherits name='org.cruxframework.crux.tools.codeserver.CruxCodeServer' />
A compilação do Crux gera várias permutações diferentes, variando entre tipos de browser, dispositivos de acesso e outros. Porém, o SuperDevMode parte do princípio de que, em tempo de
desenvolvimento, não é necessário compilar todas as permutações a cada novo teste.
Por isso, é uma boa prática configurar o projeto para processar apenas uma permutação. Ou seja, é deve-se definir apenas um tipo de dispositivo, um tipo de browser e, se a aplicação usar
internacionalização, um tipo de idioma.
Para definir essas configurações deve-se configurar as propriedades no arquivo de configuração do módulo .gwt.xml acrescentando as linhas:
1
2
3
4
<!-- Application Permutations -->
<set-property name="user.agent" value="safari" />
<set-property name="device.features" value="largeDisplayMouse"/>
<set-property name="locale" value="en_US" />
Obs: Esta configuração define que será usado um browser do tipo (user.agent) safari (Chrome ou Safari) em um dispositivo (devices.features) de tela grande e acessado pelo mouse (computador
desktop), com o idioma (locale) em inglês. Pronto, a página irá atualizar e carregará o SourceCode para o browser. A partir deste ponto já é possível debug o código diretamente no browser.
Rodando a aplicação
Para rodar a aplicação, primeiramente deve-se iniciar o servidor de aplicação desejado (pode ser qualquer um, como o Jetty, Glassfish, JBoss, Weblogic, Websphere, etc). No caso do Crux, o servidor
de aplicação padrão é o Jetty e este já está incluído nas bibliotecas do próprio GWT. Dessa forma, para iniciá-lo, basta rodar o launcher Start Jetty.launch, como apresentado na imagem abaixo:
Figura 3.1. Iniciando o servidor de aplicação Jetty
Por padrão, o Jetty será iniciado na porta 8080, e disponível através do endereço:
http://localhost:8080/
Em seguida, deve-se iniciar o servidor de código do cliente, que é o responsável por compilar o código Java e gerar o Javascript que será lido pelo navegador. Para iniciá-lo, basta rodar o launcher
Start CodeServer.launch, como apresentado na imagem abaixo:
Figura 3.2. Iniciando o CodeServer
Para acessar a aplicação, basta acessar o endereço a seguir no navegador definido através da propriedade user.agent:
http://localhost:8080/_applicationName_/index.html
Crux Hotdeploy
Como mencionado na introdução deste capítulo, o Crux provê uma funcionalidade de auto deploy, fazendo a auto compilação das classes e demais recursos alterados durante o desenvolvimento.
Para habilitar esta funcionlidade, basta acrescentar (se já não estiver presente) o parâmetro -startHotDeploymentScanner no arquivo Start CodeServer.launch, assim como mostrado na imagem
abaixo:
1 <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="... -startHotDeploymentScanner ..."/>
A partir daí, durante o desenvolvimento, ao alterar qualquer arquivo declarado como arquivo fonte "client", o CodeServer será acionado e o navegador será notificado do processo de compilação. Veja
as imagens abaixo:
Figura 3.3. Exemplo de Hotdeploy
Após modificarmos algum código na classe Java mostrada na imagem acima (ou em uma screen, view, template, etc), o CodeServer será automaticamente acionado e a aplicação será notificada,
assim como apresentado a seguir:
Figura 3.4. Exemplo de Hotdeploy
Ao final do processo, o navegador será automaticamente atualizado e corresponderá ao estado mais atual da aplicação.
Debugando no Browser
Para demostrar como debugar o código no browser, usaremos como exemplo o Google Chrome.
Depois da página atualizada e compilada pelo Super Dev Mode, aperte o botão F12 no Chrome. Que mostrará uma tela igual a essa:
Figura 3.5. Source Maps no Chrome
Observe que existe dois servidores nessa página, localhost:8080 e localhost:9876. Expandindo o localhost:9876 é possível encontrar todas as classes do projeto. Assim, pode se definir quais
serão os pontos de breakpoint do Debug, como o exemplo:
Figura 3.6. Breakpoint no Source Maps no Chrome
Debugando através do Plugin SDGB
Para realizar o debug na IDE eclipse é preciso instalar o plugin SDGB que será apresentado a seguir.
Além de realizar o debug do código no navegador, é possível debugar a aplicação diretamente no eclipse. Para isso é preciso:
Instalar o plugin SDGB no eclipse. Vá em: Help > Install New Software, clique em add e defina, o Name como SDGB e Location como http://sdbg.github.io/p2.
Figura 3.7. Instalação do plugin SDGB
Escolha Source Map Debbugger:
Figura 3.8. Instalação do plugin SDGB
Depois de instalar o plugin, vá na pasta do eclipse e acrescente no final do arquivo eclipse.ini a linha:
1 -Dchrome.location=C:\Program Files (x86)\Google\Chrome\Application\chrome.exe
Obs 1: Modifique a localização do Chrome caso o tenha instalado em um diretório diferente do caminho padrão.
Obs 2: É necessário reiniciar o eclipse depois de configurar o eclipse.ini.
Vá em Debug configurations e escolha Launch Chrome e configure o launch de acordo com a imagem a seguir:
Figura 3.9. Rodando o chrome a partir do plugin
Por fim, os procedimentos para execução são os mesmo para executar o debug no browser:
Iniciar o Servidor;
Iniciar o Start CodeServer;
Iniciar o launch do Chrome;
Obs: O launch do Chrome no eclipse ficará na lista de launch de debug:
Figura 3.10. Rodando o chrome a partir do plugin
Através destes passos, poderemos ter os breakpoints de Java / Javascript dentro da IDE Eclipse, assim como mostrado na imagem abaixo:
Figura 3.11. Exemplo de breakpoint parado em uma classe "client", utilizando o plugin SDBG.
Debugando em dispositivos mobiles
Para debugar a aplicação em um dispositivo externo (como, por exemplo, em um celular) temos de fazer a aplicação responder em um endereço IP fixo. Para isto, primeiramente deve-se verificar o
endereço IP da máquina na rede. Supondo que o endereço IP da máquina corrente seja 1.2.3.4, então devemos configurar 3 arquivos, sendo o primeiro dele o arquivo de configuração do módulo
.gwt.xml e acrescentar as linhas:
1 <!-- Crux SuperDevMode -->
2 <set-configuration-property name="devModeUrlWhitelistRegexp" value="http://(1\.2\.3\.4)(:\d+)?/.*" />
Em seguida, devemos informar para o servidor de aplicação para fazer um bind da aplicação no endereço IP determinado. No caso do Jetty, basta incluir o parâmetro -bindAddress, assim como feito
na imagem abaixo:
1 <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="... -bindAddress 1.2.3.4 ..."/>
Por fim, no CodeServer o parâmtro acima também deve ser informado, assim como feito na imagem abaixo:
1 <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="... -bindAddress 1.2.3.4 ..."/>
A partir daí, basta acessarmos a URL no dispositivo mobile, como em: http://1.2.3.4:8080/_applicationName_/index.html
Debugando em dispositivos mobiles utilizando o SDBG
Note que se quisermos ter os breakpoints funcionando na IDE eclipse, basta instalar o plugin SDBG (descrito nas seções anteriores) que poderemos debugar aplicações mobile rodando localmente na
máquina do desenvolvedor. Para isto, o deve-se conectar no dispositivo através do launcher correspondente, assim como indicado na figura abaixo:
Figura 3.12. Conectando a um browser Chrome a partir do eclipse
3.4.2. DevMode
O DevMode não será mais suportado pelo GWT e pelo Crux nas suas próximas versões, mas ainda pode ser utilizado com navegadores antigos. Para executá-lo, adicione as propriedades abaixo no
arquivo web.xml da sua aplicação:
Esta é uma configuração apenas para o ambiente de desenvolvimento e expõe um servlet iniciar os scanners do DevMode.
Exemplo 3.11. Configurar Servlet para DevMode (web.xml)
1 <listener>
2
<listener-class>org.cruxframework.crux.core.server.DevModeInitializerListener
3
</listener-class>
4 </listener>
Esta é uma configuração apenas para o ambiente de desenvolvimento e expõe um filtro utilizado para localizar recursos que ainda não foram gerados pela compilação da aplicação.
Exemplo 3.12. Configurar Filtro Recurso (web.xml)
1
2
3
4
5
6
7
8
9
10
11
<filter>
<display-name>DeclarativeUIFilter</display-name>
<filter-name>DeclarativeUIFilter</filter-name>
<filter-class>org.cruxframework.crux.core.declarativeui.filter.DeclarativeUIFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>DeclarativeUIFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
Em seguida, crie um launcher para iniciar o DevMode. Exemplo:
Exemplo 3.13. Configurar Launcher (app.launcher)
1 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2 <launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
3
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
4
<listEntry value="/<<APPLICATION_NAME>>" />
5
</listAttribute>
6
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
7
<listEntry value="4" />
8
</listAttribute>
9
<booleanAttribute key="org.eclipse.debug.core.appendEnvironmentVariables" value="true" />
10
<booleanAttribute key="org.eclipse.jdt.debug.ui.CONSIDER_INHERITED_MAIN" value="true" />
11
<booleanAttribute key="org.eclipse.jdt.debug.ui.INCLUDE_EXTERNAL_JARS" value="true" />
12
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true" />
13
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
14
<listEntry ...
15
...
16
</listAttribute>
17
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false" />
18
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE"
19
value="org.cruxframework.crux.tools.launcher.CruxLauncher" />
20
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS"
21
value="-startupUrl <<APPLICATION_MODULE_NAME>>/index.html
22
-war target/<<APPLICATION_NAME>> -gen generated
23
-bindAddress 0.0.0.0 -logLevel INFO" />
24
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="MyApp" />
25
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER"
26
value="org.eclipse.m2e.launchconfig.sourcepathProvider" />
27
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS"
28
value="-Xss10768k -Xms800M -Xmx800M -Xdebug -DCrux.dev=true
29
-Djava.naming.factory.initial=org.mortbay.naming.InitialContextFactory
30
-DdebugEnabled=true" />
31 </launchConfiguration>
No exemplo o padrão <<APPLICATION_NAME>> deve ser substituído pelo nome da aplicação.
No exemplo o padrão <<APPLICATION_MODULE_NAME>> deve ser substituído pelo nome do módulo da aplicação.
Por fim, rode o arquivo criado, assim como mostrado na imagem abaixo. Para maiores informações, consulte GWT DevMode.
Figura 3.13. Iniciando o DevMode
3.5. Melhores Práticas
Nesta seção serão apresentadas algumas dicas para iniciar um projeto com o Crux Framework. Para usufruir de todos os benefícios do Crux e evitar problemas futuros é fortemente recomendável
seguir estas dicas.
Codificação dos arquivos de código fonte deve ser UTF-8
O compilador do GWT assume que os fontes estão em UTF-8 ao processá-los, assim, se não for usada essa codificação poderão acontecer vários problemas ao compilar o projeto. Neste caso é
mais um pré-requisito do que apenas uma boa prática.
Utilização de Maven
Embora o Crux não restrinja o desenvolvedor a escolha de uma ferramenta de build, é aconselhável que se utilize o Maven, uma vez que suas diretivas de compilação e execução foram
implementadas nesta tecnologia. Vale lembrar que o Crux 5 é construído e constantemente testado utilizando Maven, o que garante um suporte natural para eventuais problemas decorrentes da
integração com esta tecnologia.
Utilize o mecanismo de ResourceBundle do GWT
Ao utilizar recursos estáticos, como arquivos css, javascript, imagens, etc. Deve-se dar preferência para a utilização do mecanismo de ResourceBundle do GWT. A utilização desta abordagem
acarreta um maior tempo de desenvolvimento, uma vez que o desenvolvedor deve mapear os recursos em uma classe java e acessá-los a partir de lá. Porém, a opção por utilizá-la garante uma
série de benefícios que culminarão em uma performance inigualável da aplicação. Dentre estes benefícios (são muitos!) podem ser citados: o cache constante das informações, otimizações
sobre o tamanho das imagens requisitadas através da utilização de sprites, garantia de utilizar apenas um recurso (mesmo que ele seja declarado várias vezes), etc.
Evite utilizar o recurso UiBinder do GWT
Este recurso, embora muito útil, não é compatível com o mecanismo de fábrica de widgets do Crux. Para contornar esta limitação, o Crux oferece o conceito de DeviceAdaptive, que é uma
ferramenta poderosa para construir páginas em XML que podem ser amarradas a uma controller. Este mecanismo ainda provê benefícios importantes para a construção de páginas cross-device,
baseando-se no conceito de templates. Para maiores informações sobre esta abordagem, consulte a seção xDevice.
Utilizar apenas um arquivo *.crux.xml
Ao rodar a aplicação, geralmente as páginas (Views) são carregadas a partir de uma única Screen. Ao declarar múltiplos arquivos *.crux.xml, o compilador do Crux entenderá que cada um destes
arquivos (Screens) deverá ser compilado separadamente, o que resultará na execução de todo o processo de compilação, incluindo o processamento de todas as permutações e,
consequentemente, em um maior tempo de compilação. A não ser que esta seja exatamente a intenção do desenvolvedor, deve-se dar preferência para o desenvolvimento com apenas uma
Screen e diversas Views, representando as páginas da aplicação.
Evite usar frameworks que reinventam a roda!
O Crux é um framework completo para desenvolvimento de páginas ricas em conteúdo. Porém, mesmo sendo um framework maduro e em constante evolução, é fato que sempre há algum
requisito de aplicação que geralmente não é atendido em sua totalidade pelo Crux. Para estes casos, o framework permite a utilização de extensões como, por exemplo, outros frameworks de
JavaScript. Não há nada de errado nisto! Mas deve-se evitar o uso desenfreado destes frameworks, uma vez que o Crux não pode garantir o suporte a terceiros, o que faz com que muitos dos
seus recursos se tornem limitados. Então, o uso de outros frameworks é permitido, mas com responsabilidade e consciência. Não faça das exceções uma regra!
Acelere o processo de compilação da sua aplicação!
A compilação do Crux envolve uma série de arquivos temporários, dentre eles, arquivos (bridges) para a comunicação entre as JVMs, arquivos de unitCache do GWT, recursos estáticos da
aplicação, etc. A criação destes arquivos gera um overhead de I/O na máquina, que pode ser evitado se mapearmos a pasta de sistema onde estes arquivos são armazenados para um endereço
de memória física (RAM). Com isso, o processamento ocorrerá diretamente em memória e, portanto, será muito mais rápido. Para que isto funcione, basta setar a variável
"cruxCompilationTempFolder" para a pasta mapeada em memória e a variável "isRelativeCruxCompilationTempFolder" para "false". Para maiores detalhes, consulte a seção Crux.properties.
Por fim, diversos programas estão disponíveis para se criar uma pasta física mapeada diretamente para a memória, por exemplo, o TMPFS para Linux ou mesmo o RamDisk para Windows.
Capítulo 4. Camada de Visão
A camada de visão no Crux é codificada de forma declarativa através de arquivos XML, onde as telas do sistema são projetadas e escritas em arquivos XHTML. O Crux possui um conceito que permite
criar páginas robustas, dinâmicas e velozes. Este conceito é fundamentado no modelo de views e é descrito nas seções seguintes.
4.1. Conceitos Principais
No Crux existem quatro tipos de arquivos da camada de visão, onde cada um é um arquivo XHTML usado em um contexto e com uma função específica. São eles:
Screen: é definida no arquivo .crux.xml e representa uma abstração para um documento Web.
View: é definida no arquivo .view.xml e representa o conceito de páginas, que estão contidas na Screen e compartilham o seu DOM principal.
Template: é definida no arquivo .template.xml que representa o conceito de Macro parametrizável.
xDevice: é definida no arquivo .xdevice.xml que representa o conceito de View Cross-device atrelável a um controller.
O conteúdo destes arquivos é uma combinação de tags de forma a construir a tela desejada. Estas tags têm seus namespaces (veja o exemplo abaixo) declarados no cabeçalho da página e possuem
um XSD gerado de forma automática, com base em uma tarefa descrita no arquivo de compilação do Crux. Este XSD é importante não somente como meta modelo de cada widget, mas
principalmente pela possibilidade de utilização da função de auto complete na IDE, que é muito útil para o desenvolvedor. O Crux também oferece um mecanismo completo para a construção destes
XSDs, descrito na seção Gerador de Schema.
Exemplo 4.1. Exemplo de namespaces em um arquivo da camada de Visão
1
2
3
4
5
xmlns="http://www.w3.org/1999/xhtml"
xmlns:core="http://www.cruxframework.org/crux"
xmlns:crux="http://www.cruxframework.org/crux/widgets"
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
xmlns:c="http://www.cruxframework.org/templates/custom"
Cada arquivo da camada de visão é descrito a seguir.
4.2. Screen
A Screen no Crux é uma abstração para um documento, ela é definida nos arquivos .crux.xml que são arquivos XHTML. A Screen é declarada no corpo do arquivo, onde pode-se inserir composições
de elementos. Por exemplo, um Container de views, um template etc. Esta composição forma a página a ser exibida para o usuário.
É importante mencionar que esta composição também se estende para os outros arquivos da camada de visão, ou seja, cada arquivo pode conter uma combinação de outros elementos. A seguinte
notação tenta exemplificar este conceito de forma simplificada:
Screen -> Templates | xDevices | View | Tags (GWT, HTML, Crux etc)
Esta notação mostra que pode ser inserido na Screen uma composição de Templates, arquivos xDevices e Views. Além disso, também podem ser inseridas Widgets Crux, GWT ou elementos HTML.
Veja o exemplo a seguir:
Exemplo 4.2. Elementos da camada de visualização (index.crux.xml)
1 <!DOCTYPE html>
2 <html
3
xmlns="http://www.w3.org/1999/xhtml"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt">
7
<head> <title>Screen Sample CRUX</title> </head>
8
<body>
9
<script type="text/javascript" src="../exemploscrux/exemploscrux.nocache.js"></script>
10
11
<core:screen useView="**/views/*"
12
smallViewport="user-scalable=no, width=320"
13
largeViewport="user-scalable=no, width=device-width, height=device-height" />
14
15
<crux:horizontalSwapViewContainer id="views" height="100%" width="100%">
16
<crux:view name="mainhello" active="true"/>
17
</crux:horizontalSwapViewContainer>
18
</body>
19 </html>
Neste exemplo, além das tags padrão HTML, podemos observar:
Inclusão do arquivo *.nocache.js: Este é o script que efetivamente carrega o módulo GWT na página. Este arquivo JS é produzido pela compilação do GWT.
Utilização da tag core:screen : Esta é a definição do componente de screen, detalhado neste capítulo.
Utilização da tag crux:horizontalSwapViewContainer : Este é um container de views desta Screen, que permite as trocas entre as Views com um efeito de "deslizamento".
O DOM associado a Screen é compartilhado por todos seus elementos. No caso das Views, estas estão encapsuladas em um ViewContainer que também compartilha o DOM da própria Screen e
possui um mecanismo inteligente de alocação e desalocação de recursos, para garantir o máximo de performance e baixo consumo de memória em uma página.
A Screen possui uma representação Java associada, que pode ser acessada na Controller da aplicação. A partir deste momento, pode-se interagir com a Screen de forma programática, acessando
suas widgets, métodos e propriedades. O capítulo Camada de Controle aborda com mais detalhes esta interação.
4.2.1. Atributos da Screen
Os atributos da screen podem ser inseridos no momento de sua declaração, no próprio arquivo XHTML:
Tabela 4.1. Atributos da Screen
Atributo
Descrição
useController
Define qual será a Controller da Screen
useView
Define, via expressão regular, o caminho onde se localizam as Views da aplicação
smallViewport
Define as configurações de renderização da página para dispositivos do tipo small. Um exemplo de configuração é: user-scalable=no, width=320
dataObject
Indica o nome dos dataObjects, separados por vírgula, que a tela irá utilizar. Para maiores informações, consulte o capítulo Camada de Controle.
disableRefresh
Desabilita o refresh da página através dos atalhos do teclado.
fragment
Indica a qual fragmento esta página vai pertencer. Isto é útil para implementar a otimização de carregamento tardio dos recursos na tela.
title
Indica o título da tela.
useDataSource
Indica o nome dos dataSources, separados por vírgula, que a tela irá utilizar. Para maiores informações, consulte o capítulo Camada de Controle.
useFormatter
Indica o nome dos formatters, separados por vírgula, que a tela irá utilizar.
4.2.2. Eventos da Screen
Os eventos da Screen podem ser inseridos no momento de sua declaração, no próprio arquivo XHTML:
Tabela 4.2. Eventos da Screen
Atributo
Descrição
onLoad
Define o método da Controller que será chamado assim que a página for carregada
onActivate
Evento acionado quando a tela é desenhada na tela.
onClose
Evento acionado no fechamento da tela.
onClosing
Evento acionado no fechamento da tela. Este pode ser cancelado.
onHistoryChanged
Evento disparado quando alguma função do histórico é acionada. Por exemplo, quando o botão back ou forward do navegador é acionado.
onResized
Evento disparado quando a tela é redimensionada.
onOrientationChange
Evento disparado em dispositivos móveis quando a tela muda o sentido de orientação (retrato x paisagem).
4.3. View
A View no Crux é uma abstração para uma página. Ela é definida nos arquivos .view.xml que são arquivos XHTML. Elas são carregadas dinamicamente na Screen, através de um ViewContainer, de
acordo com a navegação do usuário ou as ações da Controller. Em uma mesma Screen, podemos ter diversos ViewsContainers.
A View possui a propriedade ViewName, que é o nome da View e corresponde ao seu prefixo. Por exemplo, viewTest é o ViewName da view cujo arquivo é viewTest.view.xml. Esta propriedade
identifica a view de forma única e serve para acessá-la em todos os outros pontos da aplicação. Na view, ainda temos a propriedade ViewID que está relacionada com a instância de uma view, ou seja,
dada uma view cujo ViewName é viewTest, esta pode ter várias instâncias, do tipo viewTest1, viewTest2 etc.
O ViewContainer é um container responsável pela gestão de suas views associadas. Ele controla a alocação/desalocação das views, o disparo de eventos para cada uma delas, a maneira como elas
são trocadas etc. O ViewContainer é uma widget poderosa para a gestão das views.
Existem diferentes Containers, cada um responsável por um comportamento específico. Alguns exemplos:
MultipleViewsContainer: este container permite a gestão de várias views, mas abrindo somente uma de cada vez.
SingleViewContainer: este container permite a gestão de somente uma view.
DialogViewContainer: este container é semelhante ao MultipleViewsContainer, mas as abre em uma caixa de diálogo flutuante.
HorizontalSwapContainerFactory: este container é semelhante ao MultipleViewsContainer, mas permite a troca entre as views através de animação.
Observe a notação seguinte:
View -> Templates | xDevices | View | Tags (GWT, HTML, Crux etc)
Esta notação mostra que pode ser inserido na view uma composição de Templates, arquivos xDevices e até mesmo Views. Além disso, também podem ser inseridas Widgets Crux, GWT ou elementos
HTML.
A view possui uma representação Java associada, que pode ser acessada no Controller da aplicação. A partir deste momento, pode-se interagir com a view de forma programática, acessando suas
widgets, métodos e propriedades. O capítulo Camada de Controle aborda com mais detalhes esta interação.
O exemplo de código a seguir apresenta uma view que contém um campo de entrada e um botão. Essa é a view carregada pela screen (index.crux.xml) da seção anterior.
Exemplo 4.3. View principal (mainhello.view.xml)
1 <v:view
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:v="http://www.cruxframework.org/view"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
7
useResource="xStandardResources"
8
useController="helloController"
9
height="100%">
10
<crux:styledPanel id="outerPanel" height="200px">
11
<crux:formDisplay id="form">
12
<crux:entry label="What is your name?">
13
<gwt:textBox id="nameTextBox" width="200px"/>
14
</crux:entry>
15
<crux:entry horizontalAlignment="center">
16
<crux:button id="okButton" text="Ok!" onSelect="helloController.sayHello"
17
style="display:block"/>
18
</crux:entry>
19
</crux:formDisplay>
20
</crux:styledPanel>
21 </v:view>
Neste exemplo, além das tags padrão HTML, podemos observar:
Utilização das tags crux:styledPanel, crux:formDisplay, crux:entry, crux:button e gwt:textBox: que são tags do Crux e também do GWT, responsáveis pela formação dos elementos da
página.
Utilização da propriedade useResource: permite informar para a View quais recursos serão utilizados. Estes recursos são descritos em uma classe Java, apresentada nas seções seguintes.
Utilização da tag useController: determina o Controller da View.
Os eventos da view também podem acionar métodos da Controller. No exemplo anterior, no evento onSelect do botão, é acionado o método sayHello da Controller helloController. O método
sayHello irá manipular o valor do campo, e carregar uma nova view, a messagehello. Veja o método sayHello da controller a seguir:
Exemplo 4.4. Método sayHello (HelloController.java)
1
2
3
4
5
6
7
8
9
@Expose
public void sayHello()
{
HorizontalSwapContainer views = (HorizontalSwapContainer) Screen.get("views");
views.showView("messagehello");
String name = helloView.nameTextBox().getValue();
name = StringUtils.isEmpty(name) ? "Annonymous" : name;
messageView.messageLabel().setText("Hello, " + name + "!");
}
No capítulo Camada de Controle é apresentada em detalhe a controller, seus os métodos e como ela pode acessar os elementos da view.
4.3.1. Atributos da View
Alguns atributos da View:
Tabela 4.3. Atributos da View
Atributo
Descrição
useController
Define qual será a Controller da Screen
useView
Define, via expressão regular, o caminho onde se localizam as Views da aplicação
dataObject
Indica o nome dos dataObjects, separados por vírgula, que a tela irá utilizar. Para maiores informações, consulte o capítulo Camada de Controle.
fragment
Indica em qual fragmento esta página vai pertencer. Isto é útil para implementar a otimização de carregamento tardio dos recursos na tela.
Atributo
Descrição
title
Indica o título da tela.
useDataSource
Indica o nome dos dataSources, separados por vírgula, que a tela irá utilizar. Para maiores informações, consulte o capítulo Camada de Controle.
useFormatter
Indica o nome dos formatters, separados por vírgula, que a tela irá utilizar.
4.3.2. Eventos da View
Os eventos da View são:
Tabela 4.4. Eventos da View
Atributo
Descrição
onLoad
Evento acionado quando a view é carregada dentro do container de views.
onUnload
Evento acionado quando e view é removida do container de views.
onActivate
Evento acionado quando a tela é desenhada na tela pelo container de views.
onDeactivate
Evento acionado quando a view deixa de ser exibida na tela pelo container de views.
onClose
Evento acionado no fechamento da tela.
onClosing
Evento acionado no fechamento da tela. Este pode ser cancelado.
onHistoryChanged
Evento disparado quando alguma função do histórico é acionada. Por exemplo, quando o botão back ou forward do navegador é acionado.
onResized
Evento disparado quando a tela é redimensionada.
onOrientationChange
Evento disparado em dispositivos móveis quando a tela muda o sentido de orientação (retrato x paisagem).
4.4. Template
O template no Crux é semelhante ao conceito de macro, onde se pode definir um bloco de código parametrizável que será repetido em diferentes pontos da aplicação. Ele é definido no arquivo
.template.xml que é um arquivo XML parametrizado. Ele pode ser colocado em qualquer lugar do classpath, até mesmo em um arquivo .jar.
Os templates podem ser usados para:
Criar um fragmento que pode ser usado para compor páginas maiores.
Criar um componente simples de maneira declarativa.
Definir um layout de página reusável.
Os XSDs correspondentes aos templates da aplicação são automaticamente gerados pelo Crux. Dessa forma, o desenvolvedor tem acesso à biblioteca completa de templates na medida em que vai
construindo os arquivos da camada de visão. Então, por exemplo, caso o desenvolvedor tenha cadastrado um template para definir o cabeçalho de suas páginas (com informações de usuário logado,
botão de sair etc), esta biblioteca estará disponível em toda a aplicação, inclusive para a utilização de auto complete.
O código a seguir apresenta o exemplo de um template com um fragmento de uma página. O arquivo userinfo.template.xml define um cabeçalho simples que exibe o login e o nome do usuário.
Exemplo 4.5. Template de cabeçalho (userinfo.template.xml)
1 <t:template xmlns="http://www.w3.org/1999/xhtml"
2
xmlns:t="http://www.cruxframework.org/templates"
3
xmlns:crux="http://www.cruxframework.org/crux/widgets"
4
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
5
library="custom">
6
<crux:styledPanel id="panelUser">
7
<crux:formDisplay id="formUser">
8
<crux:entry label="Login:">
9
<gwt:label id="lbLogin" text="user login" />
10
</crux:entry>
11
<crux:entry label="Name:">
12
<gwt:label id="lbName" text="user name" />
13
</crux:entry>
14
</crux:formDisplay>
15
</crux:styledPanel>
16 </t:template>
Repare no exemplo acima que o template informa o nome da biblioteca a qual pertencerá o template definido. O atributo library=custom informa que este template deverá ser adicionado à biblioteca
chamada custom. Cada biblioteca dará origem a um arquivo XSD próprio e estará associado a um namespace próprio, na forma http://www.cruxframework.org/templates/<nomeDaBiblioteca>
Para usar um template, basta incluir na screen (ou na view) a referência ao template, no caso a <c:userinfo /> . Assim, quando a página for montada para exibição, o template é adicionado. Veja o
exemplo a seguir do uso do template:
Exemplo 4.6. Screen versão 2 (index.crux.xml)
1 <html
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:core="http://www.cruxframework.org/crux"
4
xmlns:crux="http://www.cruxframework.org/crux/widgets"
5
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
6
xmlns:c="http://www.cruxframework.org/templates/custom" >
7
<head> <title>Screen Sample CRUX</title> </head>
8
<body>
9
<script type="text/javascript" src="../exemploscrux/exemploscrux.nocache.js"></script>
10
<core:screen useView="**/views/*"
11
smallViewport="user-scalable=no, width=320"
12
largeViewport="user-scalable=no, width=device-width, height=device-height" />
13
14
<c:userinfo />
15
16
<crux:horizontalSwapViewContainer id="views" height="100%" width="100%">
17
<crux:view name="mainhello" active="true"/>
18
</crux:horizontalSwapViewContainer>
19
</body>
20 </html>
O resultado disso é uma página contendo a view mainhello, mas com as informações do usuário em seu cabeçalho.
4.4.1. Parametrizando Templates
Um template pode receber parâmetros (atributos) para exibir informações de acordo com o contexto onde será aplicado. Para definir um atributo no template, basta escrever o atributo no formato #
{attributeName} . Neste ponto será feita a substituição da variável pelo valor correspondente.
No exemplo a seguir é apresentada uma nova versão do template userinfo.template.xml agora com as variáveis userLogin e userName que serão recebidas pelo template quando o mesmo for
inserido e não mais com valores fixos.
Exemplo 4.7. Template de cabeçalho versão 2 (userinfo.template.xml)
1 <t:template xmlns="http://www.w3.org/1999/xhtml"
2
xmlns:t="http://www.cruxframework.org/templates"
3
xmlns:crux="http://www.cruxframework.org/crux/widgets"
4
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
5
library="custom">
6
<crux:styledPanel id="panelUser">
7
<crux:formDisplay id="formUser">
8
<crux:entry label="Login:">
9
<gwt:label id="lbLogin" text="#{userLogin}" />
10
</crux:entry>
11
<crux:entry label="Name:">
12
<gwt:label id="lbName" text="#{userName}" />
13
</crux:entry>
14
</crux:formDisplay>
15
</crux:styledPanel>
16 </t:template>
Para essa nova versão do template, na screen ao incluir o template userinfo agora são passados como parâmetros os nomes e os valores para os atributos do template.
Exemplo 4.8. Screen versão 3 (index.crux.xml)
1 <html
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:core="http://www.cruxframework.org/crux"
4
xmlns:crux="http://www.cruxframework.org/crux/widgets"
5
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
6
xmlns:c="http://www.cruxframework.org/templates/custom" >
7
<head> <title>Screen Sample CRUX</title> </head>
8
<body>
9
<script type="text/javascript" src="../exemploscrux/exemploscrux.nocache.js"></script>
10
<core:screen useView="**/views/*"
11
smallViewport="user-scalable=no, width=320"
12
largeViewport="user-scalable=no, width=device-width, height=device-height" />
13
14
<c:userinfo userLogin="johnc" userName="John Carpenter" />
15
16
<crux:horizontalSwapViewContainer id="views" height="100%" width="100%">
17
<crux:view name="mainhello" active="true"/>
18
</crux:horizontalSwapViewContainer>
19
</body>
20 </html>
Os valores dos atributos dos templates também podem ser manipulados por métodos da controller.
4.4.2. Templates de Blocos
Uma aplicação para templates é definir um bloco de código para reuso, assim, estruturas que são usadas regularmente podem ser definidas como se fossem um novo componente. O exemplo a seguir
é basicamente uma de caixa de texto com um label, que foi agrupada em um template. Neste exemplo foi usado o recurso de parametrização dos elementos para deixar o componente mais flexível.
Veja o exemplo:
Exemplo 4.9. Template de Bloco (entrytextbox.template.xml)
1 <t:template xmlns="http://www.w3.org/1999/xhtml"
2
xmlns:t="http://www.cruxframework.org/templates"
3
xmlns:core="http://www.cruxframework.org/crux"
4
xmlns:crux="http://www.cruxframework.org/crux/widgets"
5
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
6
library="custom">
7
<crux:entry label="#{label}:">
8
<gwt:textBox id="#{id}" value="#{value}" width="200px"/>
9
</crux:entry>
10 </t:template>
A utilização do template de bloco apresentado é da mesma maneira de um template personalizado. Veja o exemplo a seguir do template em uma view do Crux:
Exemplo 4.10. View com Template de Bloco (textbox.view.xml)
1 <v:view
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:v="http://www.cruxframework.org/view"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
7
xmlns:c="http://www.cruxframework.org/templates/custom"
8
useResource="xStandardResources"
9
useController="helloController"
10
height="100%">
11
<crux:styledPanel id="outerPanel" height="200px">
12
<crux:formDisplay id="form">
13
14
<c:entrytextbox id="nameTextBox" label="What is your name?" value="Your name here" />
15
16
<crux:entry horizontalAlignment="center">
17
<crux:button id="okButton" text="Ok!" onSelect="helloController.sayHello"
18
style="display:block"/>
19
</crux:entry>
20
</crux:formDisplay>
21
</crux:styledPanel>
22 </v:view>
Os templates de blocos podem ser utilizados para quaisquer situações onde seja necessário o uso de estruturas mais "elaboradas" deixando o código da view mais simples.
4.5. xDevice
Os arquivos de extensão .xdevice.xml possibilitam a criação de páginas renderizadas para cada dispositivo específico. Quando utilizamos estes arquivos, estamos criando componentes que serão
executados nos diferentes dispositivos descritos na seção Suporte Cross-device. O processo de criação destes componentes envolve a utilização das camadas de visão e controle e é documentado
no manual específico de criação de componentes.
4.6. Chamada de métodos e acessos aos Controllers
Uma View se comunica com os controllers através de eventos. Para ligar uma view a um ou mais controllers, deve-se utilizar a propriedade useController, já mencionada anteriormente.
Uma declaração de eventos deve seguir o seguinte padrão: "onEventName" = "controllerName.methodName". Veja o exemplo:
Exemplo 4.11. View chamada de evento (index.crux.xml)
1 <html xmlns="http://www.w3.org/1999/xhtml"
2
xmlns:crux="http://www.cruxframework.org/crux"
3
xmlns:gwt="http://www.cruxframework.org/crux/gwt" >
4
<head>
5
<script language="javascript" src="cruxtest/cruxtest.nocache.js"></script>
6
</head>
7
<body>
8
<crux:Screen onClose='clientHandler.onClose' useController="clientHandler" >
9
<gwt:textBox id="myBox" />
10
<gwt:button id="myButton" text="Hello" onClick="clientHandler.helloWorld" />
11
</crux:Screen>
12
</body>
13 </html>
No exemplo acima, quando o evento close ocorrer, este será tratado pelo método onClose do controller clientHanlder, definido na propriedade useController. O mesmo acontece para o evento
click, que será tratado pelo mesmo controller, mas pelo método helloWorld.
No controller, estes métodos são declarados com a visibilidade pública e anotados com a anotação @Expose. Estas e outras informações são discutidas no capítulo Camada de Controle.
Capítulo 5. Camada de Controle
Este capítulo apresenta a camada de controle do Crux Framework. As classes de controle são chamadas para tratar os eventos de tela da aplicação. Esse processamento é todo executado no próprio
cliente, sendo o código da controller transformado em um JavaScript para distribuição da aplicação.
5.1. Definir uma Controller
Para definir uma controller basta criar uma classe Java com a anotação @Controller. Na anotação deve ser informado o nome pelo qual a classe de controle será referenciada nas páginas. Veja o
exemplo a seguir.
Exemplo 5.1. Definição da Controller
1 @Controller("myController")
2 public class MyController {
3
// ... event handlers here
4 }
A classe de controle define os métodos para tratar eventos nas telas da aplicação. Os métodos de controle devem:
Ter visibilidade pública (public).
Estar marcado com a anotação @Expose.
Deve declarar apenas um ou nenhum parâmetro. Se tiver um parâmetro, este deve ser um subtipo de GwtEvent e o método somente pode ser utilizado para tratar esse tipo de evento.
Veja o exemplo a seguir, com alguns métodos:
Exemplo 5.2. Métodos em uma Controller
1 @Controller("clientHandler")
2 public class MyController {
3
@Expose
4
public void helloWorld() {
5
// code here
6
}
7
8
@Expose
9
public void onClose(CloseEvent<Window> event) {
10
// code here
11
}
12
13
protected void myMethod(String string) {
14
// code here
15
}
16 }
Note que nesta controller existe um método que não atende as condições para tratamento de eventos, o myMethod. Este método não pode ser chamando de forma declarativa.
5.2. Anotações das Controllers
O Crux disponibiliza uma série de anotações para auxiliar na codificação das classes de controle. A seguir são apresentadas as anotações que podem ser utilizadas nesta camada.
5.2.1. @Controller
Esta anotação é utilizada para marcar uma classe como sendo uma Controller. Toda classe de controle deve estar marcada com esta anotação. A tabela abaixo mostra todos os seus atributos:
Tabela 5.1. Atributos da @Controller
Propriedade
Requerida
Valor
Padrão
Descrição
value
Sim
Sem
valor
Define o nome da Controller. Usado nas páginas para referenciar a Controller.
stateful
Não
true
Se for "true" uma mesma instância da classe de controle é usada para tratar todos os eventos a ela direcionados, dentro da mesma View. Se for "false"
uma nova instância é usada para cada novo evento.
lazy
Não
true
Se for "true" o objeto Controller é construído apenas quando é chamado pela primeira vez.
supportedDevices Não
Device.all Pode-se optar por usar a classe anotada apenas para uma lista restrita de dispositivos. Isso permite que se crie duas classes diferentes, usando o mesmo
nome do controlador, mas uma voltada para um conjunto de dispositivos e a outra voltada para outro conjunto.
5.2.2. @Inject
O Crux oferece um container IoC para ser utilizado no lado cliente da aplicação. Desta forma, pode-se anotar as classes de controle da aplicação para definir a forma como objetos utilizados são
instanciados e associados às classes de controle.
Veja abaixo um pequeno exemplo:
Exemplo 5.3. Injeção de valores na Controller
1 @Controller("myIoCController")
2 public class MyIoCController {
3
@Inject
4
private GreetingServiceAsync service;
5
6
public void setService(GreetingServiceAsync service) {
7
this.service = service;
8
}
9
// other event handlers
10 }
A seção Inversion of Control (IoC) apresenta o funcionamento deste container IoC do Crux.
5.2.3. @Expose
O @Expose é a anotação usada para expor um método como tratador de evento. Todo método que vá ser usado para tratamento de um evento declarado em uma tela da aplicação deve estar anotado
com esta anotação.
5.3. Inversion of Control (IoC)
O Crux disponibiliza um container IoC para o lado cliente da aplicação. Classes no lado cliente podem ser anotadas com a anotação @Inject para popular suas propriedades a partir do container IoC.
Para que uma propriedade da classe cliente possa ser atribuída pelo container IoC, é necessário que a mesma:
Tenha visibilidade pública. Para isso, o atributo deve ser público ou possuir um método de escrita (setter) público.
Esteja marcada com a anotação @Inject.
Veja o exemplo abaixo:
Exemplo 5.4. Propriedades injetadas pelo container IoC
1 @Controller("myIoCController")
2 public class MyIoCController {
3
@Inject
4
private GreetingServiceAsync service;
5
6
public void setService(GreetingServiceAsync service) {
7
this.service = service;
8
}
9
10
@Inject
11
public AnotherPojo property;
12
13
@Inject
14
private OtherPojo property2; //NOT VALID.
15
16
// other event handlers
17 }
Repare que a propriedade property2 não pode ser atribuída pelo container, pois não obedece os critérios definidos acima.
Outro ponto importante é que a classe AnotherPojo, referenciada no exemplo acima, também pode definir propriedades a serem injetadas pelo container, da mesma forma como foi demonstrado nesta
classe de controle.
5.3.1. Escopo dos objetos criados
Pode-se definir o escopo utilizado para leitura dos objetos a serem injetados pelo container IoC. A anotação @IoCResource possui uma propriedade chamada scope que define o escopo do objeto
injetado. Veja o exemplo abaixo:
Exemplo 5.5. Escopo dos objetos junto ao container IoC
1 @Controller("myIoCController")
2 public class MyIoCController {
3
@Inject
4
public MainView screen;
5
6
7
@BindView("main")
8
@IoCResource(scope=Scope.LOCAL)
9
public static interface MainView extends WidgetAccessor
10
{
11
}
12 }
Os três tipos de escopo suportados pelo Crux estão descritos na tabela abaixo:
Tabela 5.2. Tipos de Escopo suportados pelo container IoC
Nome
Descrição
Scope.LOCAL
O objeto injetado tem escopo apenas local. Cada vez que um objeto é injetado com escopo local, uma nova instância é criada pelo container.
Scope.SINGLETON Todas as classes carregadas por todas as views que injetarem um objeto com este escopo irão compartilhar a mesma referência.
Scope.VIEW
O objeto injetado tem escopo associado à View corrente. Todas as classes carregadas dentro da View corrente que injetarem um objeto com este escopo irão compartilhar a
mesma referência.
Caso não seja informado nada, o valor padrão para o escopo da injeção é Scope.LOCAL.
A anotação @IoCResource possibilita definirmos um mecanismo IOC dinâmico e será descrita na sessão IOC dinâmico.
5.3.2. Configurações globais do container
Para realizar configurações adicionais junto ao container IoC do Crux, deve-se criar uma classe que estenda IocContainerConfigurations. Veja o exemplo abaixo:
Exemplo 5.6. Configuração do Container IoC
1 public class MyConfigurations extends IocContainerConfigurations {
2
3
public void configure() {
4
bindType(List.class).toClass(ArrayList.class);
5
bindType(MyBaseType.class).toProvider(MyBaseTypeProvider.class);
6
}
7 }
O exemplo acima instruí o Crux a retornar instâncias do tipo ArrayList toda vez que um objeto do tipo List for solicitado para o container. Além disso, ele associa um provider para a classe
MyBaseType. Desta forma, sempre que um objeto deste tipo precisar ser criado pelo container, este provider será acionado para criação do objeto.
Para objetos de quaisquer outros tipos, além dos mapeados no arquivo de configuração acima, o Crux utilizará um provider padrão que simplesmente executa o comando GWT.create() para a criação
do objeto do tipo solicitado.
O exemplo abaixo mostra um provider que poderia ser usado para criação de objetos do tipo MyBaseType:
Exemplo 5.7. Definição de um provider para o Container IoC
1 public class MyBaseTypeProvider extends IocProvider<MyBaseType> {
2
3
public MyBaseType get() {
4
if (Permissions.hasRole("specialRole")) {
5
return new MySpecialType();
6
}
7
return new MySimpleType();
8
}
9 }
Para adicionar uma nova classe de configuração, basta criar uma classe que estenda IocContainerConfigurations. Pode-se adicionar quantas classes de configuração se deseje. Estas classes
devem estar dentro do classpath da aplicação e o Crux automaticamente as identificará.
As classes de configuração são executadas pelo Crux para a geração do código do container. Desta forma, elas são chamadas apenas durante a compilação do código java para javascript. Por isso,
elas não necessitam estar na pasta que contém o código cliente da aplicação (pasta client do módulo GWT, que será convertida para javascript). Elas não são necessárias no código gerado, mas
apenas durante seu processo de geração.
As classes configuradas no container, no entanto, são classes que serão usadas na execução do código no cliente, assim como as classes providers definidas. Todas elas devem pertencer à pasta
cliente da aplicação.
5.3.3. IOC dinâmico
O Crux oferece a possibilidade de definirmos um mecanismo dinâmico de IOC, substituindo as configurações via arquivo definidas anteriormente. Para isto, basta anotarmos a classe desejada com a
anotação @IoCResource e configurarmos o seu comportamento seguindo as seguintes opções:
Scope: Define o escopo da classe.
bindClass: a classe que fará bind com a classe sendo anotada.
provider: a classe que será o provider do IOC.
runtimeAccessible: define se esta classe estará disponível a tempo de execução ou não. É importante observar que por questões de otimização é uma boa prática não utilizarmos indevidamente
e abusivamente esta propriedade, uma vez que a presença dela indica uma geração adicional de código durante o mecanismo de deferred binding.
Veja o exemplo a seguir:
Exemplo 5.8. Utilização do IOC dinâmico (declaração)
1 @IoCResource(
2 bindClass=LogDAO.class,
3 provider=FactoryDAO.class,
4 runtimeAccessible=true
5 )
6 public class TestDAO{}
A propriedade bindClass utilizada acima está informando que a classe LogDAO será injetada a todo momento que solicitarmos a classe TestDAO. Por sua vez, a propriedade provider utilizada sugere
que vamos utilizar uma fábrica para injetar aquele DAO, que é onde podem ser inseridas novas propriedades no objeto a ser injetado.
O código abaixo mostra um exemplo de utilização do objeto acima:
Exemplo 5.9. Utilização do IOC dinâmico (exemplo de uso)
1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller("myController")
public class MyController
{
//Usage 1
@Inject
public TestDAO testDAO;
//Usage 2
public void testMethod()
{
View.getIOCContainer().get(TestDAO.class);
}
}
Caso tenhamos setado a propriedade runtimeAccessible para true, podemos acessar a classe maneira definida em Usage 2. Caso contrário, a classe não estará disponível em runtime, então somente
poderemos acessá-la através da maneira definida em Usage 1.
5.4. Interação entre Controllers e Views
A seção Chamada de métodos e acessos aos Controllers descreve como uma View pode associar métodos de uma Controller a eventos da tela. Esta seção detalha melhor a interação entre
artefatos destas duas camadas da aplicação.
Uma view referencia a Controller usada para tratar seus eventos de tela através do nome dado a elas, declarado na anotação @Controller. Cada view possui um escopo próprio, que armazena uma
instância de cada Controller declarada por ela.
Em outras palavras, as duas Views apresentadas no exemplo abaixo, utilizarão instâncias distintas da classe de controle para tratamento dos seus eventos.
Exemplo 5.10. Uso de controllers na view
1 <v:view
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:v="http://www.cruxframework.org/view"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
7
useController="mainController"
8
onLoad="mainController.onLoad">
9
10
<crux:storyboard id="mainPanel" style="display:block; margin: 50px">
11
<gwt:textBox id="nameTextBox"/>
12
<crux:button id="okButton" text="Go!" onSelect="mainController.sayOk"/>
13
</crux:storyboard>
14 </v:view>
1 <v:view
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:v="http://www.cruxframework.org/view"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
7
useController="mainController">
8
9
<crux:storyboard id="mainPanel" style="display:block; margin: 50px">
10
<crux:button id="backButton" text="Back!" onSelect="mainController.goBack"/>
11
</crux:storyboard>
12 </v:view>
5.4.1. Acessando Views na Controller
O exemplo abaixo mostra como se pode acessar uma view a partir de uma classe de controle da aplicação.
Exemplo 5.11. Acesso a uma view (MyController.java)
1 @Controller("myController")
2 public class MyController {
3
@Expose
4
public void myEventHandler() {
5
View view = View.of(this);
6
Window.alert("ID da View corrente = " + view.getId() + ". Título = " + view.getTitle());
7
Window.alert("Título da view \"myView\"= " + View.getView("myView").getTitle());
8
}
9 }
A chamada View.of(this) retorna a view corrente associada à instância da classe de controle que está tratando o evento corrente. O método View.getView(String) também é usado para recuperar
uma view a partir do seu identificador informado no momento de sua abertura por um ViewContainer.
É possível, também, definir uma interface de acesso a Views. Esta abordagem é recomendada, uma vez que ela torna o código mais seguro, pois evita a referência às Views diretamente a partir de
Strings. Veja o exemplo abaixo:
Exemplo 5.12. Interface de um ViewAccessor (MyViews.java)
1 public interface MyViews extends ViewAccessor {
2
View myView();
3 }
Exemplo 5.13. Controller de um ViewAccessor (MyController.java)
1
2
3
4
5
6
7
@BindView("myView")
public interface MyViewWidgets extends WidgetAccessor
{
@Target("myTextBoxID")
TextBox nameTextBox();
Button okButton();
}
Para se criar uma interface de acesso a Views, basta definir uma interface que estenda ViewAccessor e definir métodos para acessar as Views. Cada método é usado para acessar uma view e deve
obedecer as seguinte regras:
O retorno do método deve ser compatível com a classe view.
Não deve possuir parâmetros.
Para fazer a referência com a View, podem ser utilizadas duas abordagens:
1) O nome do método deve coincidir com o nome da View.
2) A anotação org.cruxframework.crux.core.client.screen.views.Target pode ser utilizada na declaração da View, informando qual é nome da View.
Para utilizar o accessor, basta criar uma instância a partir de uma chamada GWT.create(), ou através do container IoC do Crux, conforme mostrado no exemplo acima.
5.4.2. Acessando componentes da view na controller
O exemplo abaixo mostra como é possível acessar componentes de uma view a partir de uma classe de controle da aplicação:
Exemplo 5.14. Acesso a componentes de tela (MyController.java)
1 @Controller("myController")
2 public class MyController {
3
@Expose
4
public void myEventHandler() {
5
TextBox textBox = View.of(this).getWidget("nameTextBox");
6
Window.alert(textBox.getValue());
7
}
8 }
A partir do objeto view, pode-se acessar um componente de tela através do método getWidget(String), passando como parâmetro o ID do componente na tela.
É possível, também, definir uma interface de acesso a componentes de tela em uma view, de forma similar ao mostrado na seção anterior, para acessar as Widgets. Para isso, basta definir uma
interface que estenda WidgetAccessor e definir métodos para acessar cada componente. Cada método é usado para acessar um componente e deve obedecer as seguintes regras:
O retorno do método deve ser compatível com a interface IsWidget.
Não deve possuir parâmetros.
Para fazer a referência com a widget, podem ser utilizadas duas abordagens:
1) O nome do método deve coincidir com o identificador do componente na view.
2) A anotação org.cruxframework.crux.core.client.screen.views.Target pode ser utilizada na declaração da Widget, informando qual é o o identificador do componente na view.
Veja o exemplo a seguir:
Exemplo 5.15. Interface de um WidgetAccessor
1 @BindView("myView")
2 public interface MyViewWidgets extends WidgetAccessor{
3
TextBox nameTextBox();
4
Button okButton();
5 }
O próximo trecho de código mostra como este accessor é utilizado dentro de uma controller:
Exemplo 5.16. Controller um WidgetAccessor
1 @Controller("myController")
2 public class MyController {
3
@Inject
4
private MyViewWidgets myViewWidgets;
5
// setters ommited
6
7
@Expose
8
public void myEventHandler() {
9
Window.alert(myViewWidgets.nameTextBox().getValue());
10
}
11 }
Repare no exemplo acima que a interface de acesso aos componentes utiliza a anotação @BindView. Esta é importante para informar ao Crux qual o identificador da view que deverá ser consultado
para se obter os componentes de tela. Caso esta anotação não esteja presente, o Crux assumirá que o mapeamento deve ser feito para apontar sempre para view corrente no momento da execução
(similar ao View.of(this)).
A anotação @BindRootView pode ser utilizada no lugar da @BindView, para se associar o objeto de acesso à view raiz da aplicação (aquela associada à página .crux.xml).
5.4.3. Value Binding
O Crux possui dois mecanismos distintos para auxiliar na transmissão de dados entre as camadas de visão e controle.
O primeiro mecanismo permite a criação de objetos de acesso para os dados dos componentes de uma tela, de forma similar ao mecanismo mostrado nas seções anteriores que utilizam o
ViewAccessor e o WidgetAccessor. Para isso basta criar uma interface herdando a interface ViewBinder. Este mecanismo é descrito na seção ViewBinder
O segundo mecanismo permite que propriedades de um objeto de dados possam ser associadas, de forma declarativa na tela, a componentes. Este mecanismo é descrito na seção Utilização de
DataObjects.
ViewBinder
Para se criar uma interface de acesso a dados dos componentes nas telas, basta definir uma interface que estenda ViewBinder e definir métodos para acessar os valores dos componentes. Cada
método deve obedecer as seguinte regras:
Os métodos devem seguir a notação javaBean para definir métodos de acesso a uma propriedade (getters e setters). O método getIdWidget() seria usado para ler o valor do componente com
identificador "idWidget" na tela e o método setIdWidget(ValueType value) seria usado para escrever o valor do componente na tela.
Os tipos das propriedades referenciadas pelos métodos deve ser compatível com o tipo armazenado pelo componente na tela. O componente visual deve implementar a interface HasValue<T>,
onde T é o tipo da propriedade, ou implementar HasFormatter e possuir um formatter capaz de converter Strings para o tipo da propriedade na interface criada.
Assim como nas interfaces de acesso mostradas nas seções anteriores, pode-se utilizar as anotações @BindView e @BindRootView nas interfaces ViewBinder para informar ao Crux a view que deverá
ser acessada pela interface criada.
O exemplo abaixo mostra como se pode utilizar uma interface para acesso aos dados nos componentes de uma tela.
Exemplo 5.17. Acesso a dados da tela através de ViewBinders
1 public interface MyViewData extends ViewBinder {
2
String getName();
3
void setName(String name);
4
Date getDateOfBirth();
5
Address getAddress();
6 }
1 public interface Address extends ViewBinder {
2
String getStreet();
3
void setStreet(String street);
4
int getNumber();
5
void setNumber(int number);
6 }
1 @Controller("myController")
2 public class MyController {
3
@Inject
4
private MyViewData myViewData;
5
// setters ommited
6
7
@Expose
8
public void myEventHandler() {
9
Window.alert(myViewData.getName());
10
}
11 }
Utilização de DataObjects
É possível marcar classes da aplicação como objetos de dados. Estes objetos podem ser usados para transferir informações entre a camada de controle e de visão da aplicação. Veja o exemplo
abaixo:
Exemplo 5.18. Definição de um objeto de dados
1 @DataObjetct("person")
2 public class Person {
3
private String name;
4
private String phone;
5
private Address address;
6
7
public String getName() {
8
return name;
9
}
10
public void setName(String name) {
11
this.name = name;
12
}
13
public String getPhone() {
14
return phone;
15
}
16
public void setPhone(String phone) {
17
this.phone = phone;
18
}
19
public Address getAddress() {
20
return address;
21
}
22
public void setAddress(Address address) {
23
this.address = address;
24
}
25 }
1 public class Address {
2
private String street;
3
private int number;
4
5
public String getStreet() {
6
return street;
7
}
8
public void setStreet(String street) {
9
this.street = street;
10
}
11
public int getNumber() {
12
return number;
13
}
14
public void setNumber(int number) {
15
this.number = number;
16
}
17 }
Para marcar uma classe como objeto de dados, basta marcar a classe com a anotação @DataObjetct, informando um nome a ser associado a este objeto de dados. Este nome é utilizado nas telas
para referenciar o objeto de dados. Veja, no exemplo abaixo, como o objeto de dados mostrado acima poderia ser referenciado na tela.
Exemplo 5.19. Referenciando um objeto de dados (person.view.xml)
1 <v:view
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:v="http://www.cruxframework.org/view"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
7
useController="mainController"
8
dataObject="person"
9
onLoad="mainController.onLoad">
10
11
<crux:formDisplay id="form">
12
<crux:entry horizontalAlignment="center" label="${mainMessages.name}">
13
<gwt:textBox id="nameTextbox" bindPath="name"/>
14
</crux:entry>
15
<crux:entry horizontalAlignment="center" label="${mainMessages.phone}">
16
<gwt:textBox id="phoneTextbox" bindPath="phone"/>
17
</crux:entry>
18
<crux:entry horizontalAlignment="center" label="${mainMessages.street}">
19
<gwt:textBox id="streetTextbox" bindPath="address.street"/>
20
</crux:entry>
21
<crux:entry horizontalAlignment="center" label="${mainMessages.street}">
22
<gwt:integerBox id="numberTextbox" bindPath="address.number"/>
23
</crux:entry>
24
<crux:entry horizontalAlignment="center">
25
<crux:button id="okButton" text="${mainMessages.ok}" onSelect="mainController.okClick" />
26
</crux:entry>
27
</crux:formDisplay>
28
29 </v:view>
Para transferir dados entre as classes de controle e visão com o objeto de dados definido, basta utilizar os métodos getData e setData da view. Veja o exemplo abaixo:
Exemplo 5.20. Transferindo dados via um DataObject
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Controller("mainController")
public class MainController
{
@Inject
public Views views;
@Inject
public PersonServiceAsync personService; // Code ommited here
@Expose
public void onLoad(ViewLoadEvent event) {
Integer id = event.getParameterObject();
personService.loadPerson(id, new AsyncCallback<Person>(){
public void onFailure(Throwable caught) {
// handle error
}
public void onSuccess(Person result) {
views.person().setData(result);
}
});
}
@Expose
public void okClick() {
Person p = views.person().getData();
System.out.println(" Name: " + p.getName() );
}
public interface Views extends ViewAccessor {
BindableView<Person> person(); // "person" is a View name
}
}
Repare que para acessar a view na classe de controle mostrada acima, foi utilizado um ViewAccessor que define um método de acesso para a view person.view.xml, apresentada anteriormente. Este
método retorna um objeto do tipo BindableView<Person>.
Quando o Crux detecta que uma view declara um dataObject, ele cria o objeto que representa esta view de forma a fazer com que o mesmo seja uma sub classe de BindableView<T>, onde T é o tipo do
dataObject declarado. Desta forma, para acessá-la e manipular informações do objeto de dados a ela amarrado, basta fazer um cast do objeto da view para BindableView, como mostrado no exemplo
acima.
Utilização de Bind Converters
Muitas vezes, o tipo T do componente visual que implementa a interface HasValue<T>, não é o mesmo tipo do objeto para o qual estamos fazendo binding. Para estes casos, pode ser utilizada a
propriedade bindConverter, que indica qual será o conversor de tipo responsável por implementar os contratos "de" e "para".
Por exemplo, suponha que tenhamos a seguinte view:
Exemplo 5.21. Exemplo de view onde não é possível fazer o binding direto com um DTO
1 <v:view
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:v="http://www.cruxframework.org/view"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
7
useController="mainController"
8
dataObject="person"
9
onLoad="mainController.onLoad">
10
11 <crux:singleSelect id="selectPropertyID" bindPath="id">
12
<crux:item label="Property 1" value="1"/>
13
<crux:item label="Property 2" value="2"/>
14 </crux:singleSelect>
15
16 </v:view>
E o seguinte DTO:
Exemplo 5.22. DTO relacionado com a view acima
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@DataObject("person")
public class Person
{
private Integer id;
private String name;
public Integer getId()
{
return id;
}
public void setId(Integer id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
O Tipo retornado pelo item na view (String) não é o mesmo tipo esperado no DTO (Integer). Para isto, devemos definir um conversor de tipos da seguinte forma:
Exemplo 5.23. Converters de String "de" e "para" Integer.
1 public abstract class TypeConverters
2 {
3 private TypeConverters(){}
4 @TypeConverter.Converter("stringInteger")
5 public static class StringIntegerConverter implements TypeConverter<String, Integer>
6 {
7
@Override
8
public Integer to(String a)
9
{
10
return (a!=null?Integer.parseInt(a):null);
11
}
12
@Override
13
public String from(Integer b)
14
{
15
return (b!=null?b.toString():null);
16
}
17 }
18
19 @TypeConverter.Converter("integerString")
20 public static class IntegerStringConverter implements TypeConverter<Integer, String>
21 {
22
@Override
23
public String to(Integer a)
24
{
25
return (a!=null?a.toString():null);
26
}
27
@Override
28
public Integer from(String b)
29
{
30
return (b!=null?Integer.parseInt(b):null);
31
}
32 }
33 }
Com isto, podemos incrementar a view definida anteriormente com a nova propriedade bindConverter:
Exemplo 5.24. View alterada com a propriedade bindConverter
1 <v:view
2
xmlns="http://www.w3.org/1999/xhtml"
3
xmlns:v="http://www.cruxframework.org/view"
4
xmlns:core="http://www.cruxframework.org/crux"
5
xmlns:crux="http://www.cruxframework.org/crux/widgets"
6
xmlns:gwt="http://www.cruxframework.org/crux/gwt"
7
useController="mainController"
8
dataObject="person"
9
onLoad="mainController.onLoad">
10
11 <crux:singleSelect id="selectPropertyID" bindPath="id" bindConverter="integerString">
12
<crux:item label="Property 1" value="1"/>
13
<crux:item label="Property 2" value="2"/>
14 </crux:singleSelect>
15
16 </v:view>
O Crux define alguns converters padrões na classe TypeConverters, como por exemplo:
stringInteger
integerString
stringDouble
doubleString
Podemos também acrescentar novos converters bastando criar uma classe que implemente a interface TypeConverter<A, B>, onde A e B são os tipos "de" e "para".
5.5. Comunicação entre controladores
O exemplo abaixo mostra como se pode acessar outras classes de controle a partir de uma classe da aplicação.
Exemplo 5.25. Acesso a outra Controller
1 @Controller("myController")
2 public class MyController {
3 @Expose
4 public void myEventHandler() {
5
MainController mainController = View.of(this).getController("mainController");
6
mainController.myMethod();
7 }
8 }
A partir do objeto View, pode-se acessar um controller qualquer de tela através do método getController(String), passando como parâmetro o nome da controller.
É possível, também, definir uma interface de acesso a controllers, de forma similar ao mostrado na seção anterior, para acessar Views e componentes de tela. Veja o exemplo abaixo:
Exemplo 5.26. Usando um ControllerAccessor
1 @BindView("myView")
2 public interface MyViewControllers extends ControllerAccessor {
3 MainController mainController();
4 }
1 @Controller("myController")
2 public class MyController {
3 @Inject
4 private MyViewControllers myViewControllers;
5 // setters ommited
6
7 @Expose
8 public void myEventHandler() {
9
myViewControllers.mainController().myMethod();
10 }
11 }
Repare no exemplo acima que a interface de acesso às classes de controle utiliza a anotação @BindView. Esta é importante para informar ao Crux qual o identificador da view que deverá ser consultada
para se obter as classes de controle. Caso esta anotação não esteja presente, o Crux assumirá que o mapeamento deve ser feito para apontar sempre para view corrente no momento da execução
(similar ao View.of(this)).
A anotação @BindRootView pode ser utilizada no lugar da @BindView, para se associar o objeto de acesso à view raiz da aplicação (aquela associada à página .crux.xml).
Para se criar uma interface de acesso a controllers de uma view, basta definir uma interface que estenda ControllerAccessor e definir métodos para acessar cada controller. Cada método é usado
para acessar um controller e deve obedecer as seguinte regras:
O retorno do método deve ser compatível o tipo da controller referenciada.
Não deve possuir parâmetros.
Para fazer a referência com a um controller, podem ser utilizadas duas abordagens:
1) O nome do controller deve coincidir com o identificador do Controller definido na anotação org.cruxframework.crux.core.client.controller.Controller.
2) A anotação org.cruxframework.crux.core.client.screen.views.Target pode ser utilizada na declaração do Controller, informando qual é o o identificador do real Controller definido na
anotação org.cruxframework.crux.core.client.controller.Controller.
5.6. Error Handler
O Crux permite que o desenvolvedor registre um handler para tratar exceções não capturadas no lado do cliente. Por padrão, já existe um tratador simples registrado, cujo comportamente se resume a
mostrar uma janela de alerta na página corrente do navegador, informando detalhes do erro.
5.6.1. Interfaces ErrorHandler e ValidationErrorHandler
Para sobrescrever o tratador de exceções padrão, basta criar uma classe que implemente a interface org.cruxframework.crux.core.client.errors.ErrorHandler e adicioná-la como regra de rebind
no arquivo de configuração do módulo (arquivo .gwt.xml).
Exemplo 5.27. Error Handler
1 <!-- Specify the default implementation to ErrorHandler.-->
2 <replace-with class="<nome_completo_da_classe_de_error_handler>">
3 <when-type-assignable class="org.cruxframework.crux.core.client.errors.ErrorHandler" />
4 </replace-with>
É possível também sobrescrever o tratador de exceções de validação (os mecanismos de validação estão detalhados na próxima seção). Porém, a classe deve implementar a interface
org.cruxframework.crux.core.client.errors.ValidationErrorHandler. A diferença é que o tratatador de exceções de validação só é invocado quando ocorre uma exceção no método de validação.
Deve ser registrado da seguinte forma:
Exemplo 5.28. Validação do Error Handler
1 <!-- Specify the default implementation to ValidationErrorHandler.-->
2 <replace-with class="<nome_completo_da_classe_de_validation_error_handler>">
3 <when-type-assignable class="org.cruxframework.crux.core.client.errors.ValidationErrorHandler" />
4 </replace-with>
O programador ainda tem a opção de invocar diretamente o tratador de exceções padrão, através da seguinte chamada:
Exemplo 5.29. Usando Error Handler na Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller("errorHandlerSample")
public class ErrorHandlerSample
{
@Expose
public void onLoad()
{
try
{
// CÓDIGO QUE POSSA LANÇAR EXCEÇÃO
} catch (Exception e)
{
Crux.getErrorHandler().handleError(e);
}
}
}
5.7. Validação
O Crux suporta a declaração de método de validação para os tratamentos de eventos da Controller. O método de validação é chamado antes do método de tratamento. Se ele for executado sem
problemas, o método de tratamento então é executado. Se o método de validação lançar alguma exceção a execução é finalizada e é exibida a mensagem de retorno para o usuário através do Crux
Error Handler.
Para criar um método de validação para o tratamento de um evento, basta usar a anotação @Validate no método de tratamento do evento, informando qual é o método de validação a ser usado. Veja o
exemplo a seguir:
Exemplo 5.30. Uso da notação @Validate (ControllerValidation.java)
1 @Controller("clientHandler")
2 public class ControllerValidation {
3
@Validate("myValidationMethod")
4
@Expose
5
public void onClose(CloseEvent<Window> event) {
6
// code here
7
}
8
9
protected void myValidationMethod(CloseEvent<Window> event) throws ValidateException {
10
// code here
11
}
12 }
Se não for passado parâmetro para a anotação @Validate, o Crux irá procurar por um método com o nome: "validate" + "NomeMétodoTratamento". Veja o exemplo:
Exemplo 5.31. Exemplo sem anotação @Validate (ControllerValidation.java)
1 @Controller("clientHandler")
2 public class ControllerValidation {
3
@Validate
4
@Expose
5
public void onClose(CloseEvent<Window> event) {
6
// code here
7
}
8
9
protected void validateOnClose() throws ValidateException {
10
// code here
11
}
12 }
Repare nos exemplos acima que os métodos de validação de eventos podem receber um parâmetro do tipo do evento sendo tratado pelo método validado, caso seja necessário acessar alguma
propriedade do evento durante a validação.
5.8. Chamada de Serviços RPC
Pode-se usar o suporte nativo do GWT a chamadas RPC e JSON para se comunicar com o servidor. O Crux, no entanto, acrescenta algumas funcionalidades ao mecanismo original de RPC do GWT
para tornar este processo mais fácil.
Com o Crux, é possível utilizar um Front Controller no lado do servidor. O uso deste Front Controller permite que se faça apenas um único mapeamento no arquivo web.xml da aplicação. Em outras
palavras, não é necessário adicionar uma nova declaração de servlet no módulo GWT (nem no web.xml) para cada novo serviço declarado.
Outra melhoria é que o uso do Front Controller libera o programador de precisar definir um EntryPoint para cada serviço. O exemplo a seguir mostra como uma chamada RPC pode ser feita com o
Crux.
Exemplo 5.32. Chamada RPC Sincrona (GreetingService.java)
1 public interface GreetingService extends RemoteService {
2
String getHelloMessage(String name);
3 }
Repare que a interface de serviço não utiliza a anotação RemoteServiceRelativePath, como seria esperado para uma interface de chamada de serviço feita com o GWT de forma convencional. O Crux
assumirá que ele deve mapear as chamadas da interface de serviço para o Front Controller. Caso a anotação esteja presente na declaração da interface, o Crux a utilizará da forma convencional.
Exemplo 5.33. Chamada RPC Assíncrona (GreetingServiceAsync.java)
1 public interface GreetingServiceAsync {
2
void getHelloMessage(String name, AsyncCallback<String> callback);
3 }
A interface assíncrona deve ser criada da mesma forma como em chamadas RPC com o GWT puro.
Exemplo 5.34. RPC na Controller (MyController.java)
1 @Controller("myController")
2 public class MyController {
3
@Inject
4
private GreetingServiceAsync service;
5
6
public void setService(GreetingServiceAsync service) {
7
this.service = service;
8
}
9
10
@Expose
11
public void sayHello() {
12
service.getHelloMessage("Thiago", new AsyncCallbackAdapter<String>(){
13
@Override
14
public void onComplete(String result){
15
Window.alert(result);
16
}
17
});
18
}
19
20
@Expose
21
public void sayHello2() {
22
// You can instantiate the service proxy with GWT.create too.
23
GreetingServiceAsync service = GWT.create(GreetingService.class);
24
service.getHelloMessage("Thiago", new AsyncCallbackAdapter<String>(){
25
@Override
26
public void onComplete(String result){
27
Window.alert(result);
28
}
29
});
30
}
31 }
A controller realiza a chamada de forma similar ao mecanismo convencional do GWT. Pode-se observar que foi utilizada a classe do Crux AsyncCallbackAdapter como callback para a operação
solicitada. Este adapter implementa um tratador de erros padrão para a operação que apenas delega para o tratador padrão de erros registrado junto ao Crux qualquer erro recebido.
A seção Serviços com GWT-RPC demonstra como implementar, no servidor, a classe de serviço que irá atender a estas requisições feitas por este código apresentado aqui.
5.9. Chamada de Serviços REST
O Crux oferece suporte a chamadas de serviços REST de forma simples e poderosa. É possível criar proxies capazes de abstrair a comunicação com o servidor de maneira análoga ao mecanismo de
RPC apresentado na seção Chamada de Serviços RPC.
Esta seção mostra como chamar um serviço REST a partir do lado cliente da aplicação. A seção Serviços REST mostra como esses serviços podem ser criados no servidor pelo Crux.
Como exemplo, veja o seguinte serviço REST definido no lado server da aplicação:
Exemplo 5.35. REST no Server (MyRestService.java)
1
2
3
4
5
6
7
8
9
@RestService("myService")
@Path("test")
public class MyRestService {
@GET
@Path("hello/{userName}")
public String sayHello(@PathParam("userName") String userName) {
return "Hello " + userName + "!";
}
}
Repare que todas as informações necessárias para o mapeamento do serviço REST estão definidas nesta classe, como path, tipo de método a ser utilizado, etc. Este mapeamento é melhor detalhado
na seção Serviços REST.
O serviço declarado pode ser chamado pelo lado cliente da aplicação através do proxy REST, definido pela interface:
Exemplo 5.36. REST no cliente (MyRestClient.java)
1 @TargetRestService("myService")
2 public interface MyRestClient extends RestProxy {
3
void sayHello(String userName, Callback<String> callback);
4 }
Esta interface informa através da anotação @TargetRestService o nome de serviço que será utilizado para mapear a classe alvo da invocação. Repare que o valor passado aqui deve coincidir com o
valor declarado na classe de serviço no servidor, na anotação @RestService.
Observe que a interface cliente, usada para chamada do serviço, deve declarar métodos que possuam assinaturas compatíveis com as assinaturas dos métodos de serviço definidos na classe do
servidor. Para que as assinaturas sejam compatíveis, é necessário que:
Os nomes dos métodos sejam iguais.
Os métodos devem possuir listas de parâmetros compatíveis. Além dos parâmetros a serem passados para o método da classe de serviço no servidor, o método da interface cliente deve
adicionar um parâmetro à sua lista de parâmetros do tipo org.cruxframework.crux.core.client.rest.Callback<T>, onde T deve ser compatível com o tipo de retorno do método da classe de
serviço no servidor.
Os parâmetros passados pela interface cliente para a classe no servidor devem ser de tipos compatíveis para serialização.
O tipo de retorno dos métodos definidos na interface cliente deve ser void.
Para utilizar a interface proxy em seu código, basta obter uma instância desta proxy através da anotação @Inject ou de uma chamada ao método GWT.create, como pode ser observado no exemplo
abaixo:
Exemplo 5.37. REST proxy (MyController.java)
1 @Controller("myController")
2 public class MyController {
3
@Inject
4
private MyRestClient service;
5
6
public void setService(MyRestClient service) {
7
this.service = service;
8
}
9
10
@Expose
11
public void sayHello() {
12
service.sayHello("Thiago", new Callback<String>(this){
13
@Override
14
public void onSuccess(String result){
15
Window.alert(result);
16
}
17
@Override
18
public void onError(Exception e){
19
Window.alert(e.getMessage());
20
}
21
});
22
}
23
24
@Expose
25
public void sayHello2() {
26
// You can instantiate the service proxy with GWT.create too.
27
MyRestClient service = GWT.create(MyRestClient.class);
28
service.sayHello("Thiago", new Callback<String>(this){
29
@Override
30
public void onSuccess(String result){
31
Window.alert(result);
32
}
33
@Override
34
public void onError(Exception e){
35
Window.alert(e.getMessage());
36
}
37
});
38
}
39 }
5.9.1. Chamadas a serviços externos
No Crux é possível criar um Proxy REST utilizando um serviço REST externo. Isto permite que sua aplicação se comunique com serviços REST de terceiros de maneira simples e prática.
Para utilizar esta funcionalidade, são basicamente três passos:
1. A interface que define o Proxy Rest deverá ser anotada com @TargetEndPoint que deverá conter a URL do servidor externo
2. Ainda na interface do Proxy, deverá ser colocada a anotação @Path que irá definir o caminho onde está localizado o serviço REST no servidor informado anteriormente.
3. Os métodos definidos no Proxy deverão conter a anotação de tipo de requisição (GET,POST,PUT etc) e também a anotação @Path que indicará o endpoint que aquele método deverá invocar.
Veja o exemplo do código completo contendo os passos citados acima:
Exemplo 5.38. Acessando um serviço REST externo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import
import
import
import
import
org.cruxframework.crux.core.client.rest.Callback;
org.cruxframework.crux.core.client.rest.RestProxy;
org.cruxframework.crux.core.client.rest.RestProxy.TargetEndPoint;
org.cruxframework.crux.core.shared.rest.annotation.GET;
org.cruxframework.crux.core.shared.rest.annotation.Path;
@TargetEndPoint("http://www.examplecrux.com:1874")
@Path("rest")
public interface IRestService extends RestProxy {
@GET
@Path("test/")
void getTest(Callback<String> callback);
}
Considerando o código acima, quando o método getTest() for chamado, o Crux fará uma chamada REST para o endereço 'http://www.examplecrux.com:1874/rest/test'
Lembrando que ao utilizar a anotação @TargetEndPoint não será mais necessário definir no servidor a implementação do seu serviço REST, pois a interface apenas é suficiente para realizar as
chamadas, e para de fato efetuar as chamadas, basta instanciar um objeto do tipo do Proxy e fazer a chamada ao método.
5.10. Animações de tela
O Crux permite criar aniamações de componentes e telas de forma nativa e também permite estender os recursos nela já existentes. Essas funcionalidas são baseadas nos recursos CSS Transtion e
CSS Animation, ambos da especificação CSS3.
O CSS transitions permite que as mudanças nos valores das propriedades CSS ocorram suavemente sobre uma duração especificada.
Com CSS animation, é possível criar animações que podem substituir imagens animadas, animações em Flash e JavaScript em muitas páginas web.
A classe org.cruxframework.crux.core.client.css.transition.Transition permite criar transições, baseadas no CSS Transtions, de forma bem simples. Utilize os métodos translateX() e
setHeight() para fazer transições de posição. Utilize os métodos fadeOut() e fadeIn() para transições de opacidade. Abaixo, um exemplo do uso desses recursos.
Exemplo 5.39. Exemplo Transição
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller("mainController")
public class MainController
{
@Expose
public void translateX()
{
Button button = (Button)View.of(this).getWidget("btnTranslate");
Transition.translateX(button, 100, 5000, null);
}
@Expose
public void fadeOut()
{
Button button = (Button)View.of(this).getWidget("btnFadeOut");
Transition.fadeOut(button, 5000, null);
}
}
A classe org.cruxframework.crux.core.client.css.animation.StandardAnimation permite criar animações baseadas no CSS Animations. Para animar componentes, cria uma instância desssa
classe com a animação desejada como parâmetro, e invoque o método animate() passando como parâmetro a widget que queria animar. Abaixo um exemplo de uso desse recurso
Exemplo 5.40. Exemplo Animação
1
2
3
4
5
6
7
8
9
10
11
@Controller("mainController")
public class MainController
{
@Expose
public void animate()
{
Button button = (Button)View.of(this).getWidget("btnExecute");
StandardAnimation animation = new StandardAnimation(StandardAnimation.Type.slideInDown);
animation.animate(button);
}
}
Capítulo 6. Camada de Modelo
Este capítulo mostra como o Crux pode auxiliar na codificação da camada de Modelo da aplicação.
6.1. Serviços com GWT-RPC
Como mostrado na seção Chamada de Serviços RPC, o Crux suporta o mecanismo de comunicação RPC do GWT com algumas funcionalidades adicionais.
Do lado do servidor, a principal diferença entre o GWT convencional e o Crux é que não é necessário que a classe de implementação do serviço herde RemoteServiceServlet. A classe de
implementação é um POJO simples e precisa implementar apenas a interface do serviço. Para uma interface de serviço definida, no lado cliente da aplicação, como:
Exemplo 6.1. Interface RPC
1 public interface GreetingService extends RemoteService {
2
String getHelloMessage(String name);
3 }
Pode-se definir uma classe de implementação da seguinte forma:
Exemplo 6.2. Implementando serviço RPC (GreetingServiceImpl.java)
1 public class GreetingServiceImpl implements GreetingService {
2
public String getHelloMessage(String name){
3
return "Server says: Hello, " + name + "!'";
4
}
5 }
Para determinar qual a implementação deve ser usada para uma determinada interface de serviço, o Crux realiza uma busca por classes que implementem esta interface e utiliza a primeira encontrada.
Este comportamento pode ser modificado, definindo-se uma Factory para os serviços, como mostrado abaixo:
Exemplo 6.3. Definindo factory de serviço (MyServiceFactory.java)
1 public class MyServiceFactory implements ServiceFactory {
2 private WebApplicationContext wac;
3
4 public Object getService(String serviceName) {
5
return wac.getBean(Class.forName(serviceName)); // ommiting exception handling.
6 }
7
8 public void initialize(ServletContext context) {
9
wac = WebApplicationContextUtils.getWebApplicationContext(context);
10 }
11 }
A classe exibida acima poderia ser utilizada em uma aplicação para construir os objetos de implementação dos serviços utilizando o framework Spring. Agora, basta informar ao Crux que o mesmo
deve utilizar esta implementação de factory. Para isto, basta configurar a propriedade serviceFactory no arquivo de configurações Crux.properties com o nome da classe factory definida acima.
Caso uma classe de serviço necessite ter acesso aos objetos HttpServletRequest, HttpServletResponse ou HttpSession associados à requisição corrente, pode-se utilizar as interfaces
RequestAware, ResponseAware ou SessionAware, como demonstrado no exemplo abaixo:
Exemplo 6.4. Acesso ao Request em uma classe de serviço
1 public class GreetingServiceImpl implements GreetingService, RequestAware {
2
private HttpServletRequest request;
3
4
public void setRequest(HttpServletRequest request) {
5
this.request = request;
6
}
7
8
public String getHelloMessage(String name){
9
return "Server says: Hello, " + name + "! Your request came from IP: " +
10
request.getRemoteAddr();
11
}
12 }
Outro ponto importante a ser observado é que o Crux propaga automaticamente a informação do Locale do cliente para os seus serviços, de forma que as classes de mensagens internacionalizadas no
servidor já sabem determinar o locale informado pelo cliente em uma chamada RPC. Consulte a seção Internacionalização para mais informações sobre internacionalização no lado servidor do Crux.
6.2. Serviços REST
O Crux suporta a criação de serviços REST através de uma API inspirada na JSR 311. O Crux implementa um subconjunto desta JSR e adiciona algumas funcionalidades extras, conforme pode ser
verificado nesta seção.
6.2.1. Criando Serviços REST
Para expor serviços REST com o Crux, é preciso configurar o servlet de tratamento de requisições REST no arquivo web.xml da aplicação. Usando archetypes essa configuração já é feita. Pode ser
visto em detalhe na seção Instalando Crux Manualmente.
Para se definir uma classe de serviços REST junto ao Crux, basta criar uma classe java simples e anotá-la com as anotações @RestService e @Path, conforme ilustrado no exemplo abaixo:
Exemplo 6.5. Classe de Serviço REST (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
11
12
@RestService("personService")
@Path("person")
public class PeopleService {
@GET
@Path("{id}")
public Person getPerson(@PathParam("id")Long id) {
// retrieve the person and return...
return person;
}
}
A anotação @RestService é usada para informar ao Crux que esta é uma classe de serviço REST e que a mesma poderá ser referenciada pelo nome "personService" por clientes Crux que desejem
acessar algum serviço desta classe.
A anotação @Path na classe acima informa que todos os serviços associados a métodos desta classe estarão associados a uma URL que terá como prefixo a String "person". A mesma anotação
colocada no método getPerson(Long), informa que este método responderá por um serviço exposto em uma URL associada a "/person/{id}". A URL http://myhost/rest/person/1234, por exemplo,
poderia ser usada para chamar o serviço, informando o id 1234 como parâmetro.
Os métodos de serviço de uma classe REST, devem obedecer à seguintes regras:
Os métodos devem ter visibilidade pública.
Os métodos devem estar anotados com uma das anotações: GET, PUT, POST ou DELETE.
Dados dois métodos que tratem a mesma operação (ex. GET), estes devem estar associados a paths distintos.
As anotações GET, PUT, POST ou DELETE são utilizadas para indicar o tipo de operação HTTP que será tratada em cada método da classe REST.
A forma como os parâmetros são mapeados na URL do serviço é definida por anotações associadas aos parâmetros. O exemplo anterior associa o parâmetro id do método à parte final da URL
requisitada. A seguir estão descritas as anotações que podem ser usadas e o resultado prático que elas produzem no mapeamento do serviço.
6.2.2. Tipos de chamadas REST
Nesta seção, são apresentadas as notações que correspondem aos tipos de chamdas REST: @GET, @PUT, @DELETE e @POST.
@GET
A anotação @GET deve ser usada em um método de serviço REST para informar que o mesmo deverá tratar a operação HTTP GET.
Esta operação deve ser usada para leitura de dados. O Crux não permite que se mapeie uma operação GET que receba parâmetros no corpo da requisição. Todos os parâmetros da operação devem
ser fornecidos como parâmetros @PathParam, @QueryParam, @HeaderParam ou @CookieParam.
É possível, também, configurar o cache para as operações GET, através de propriedades da anotação, mostradas na tabela abaixo:
Tabela 6.1. Atributos da anotação GET
Propriedade
Valor Padrão
Descrição
cacheTime
GET.NEVER (-1)
O número de segundos, a partir do momento atual, que a resposta pode ser mantida em cache pelo cliente.
noTransform
false
Isto implica que o cache ou proxy não deve alterar qualquer aspecto do corpo da entidade que é especificado por estes cabeçalhos, incluindo o próprio valor
do corpo da entidade.
mustRevalidate false
Quando for true, nenhum cache (de um cliente ou de um proxy) deve usar o valor armazenado no cache após o mesmo se tornar obsoleto sem primeiro
revalidar com o servidor de origem a validade do dado.
proxyRevalidate false
Idêntico ao mustRevalidate, mas aplica-se apenas para proxies de cache compartilhado.
cacheControl
Se cacheTime é zero ou um número negativo, então assume-se que nenhum cache deve ser usado. Se cacheTime é um número positivo, então esta
propriedade informa como esse cache deve trabalhar. PUBLIC Indica que a resposta pode ser armazenada em cache por qualquer cache. PRIVATE Indica
CacheControl.PUBLIC que a totalidade ou parte da mensagem de resposta é destinado a um único usuário e não deve ser armazenada em cache por um cache compartilhado.
NO_CACHE significa que um cache não deve usar a resposta para satisfazer um pedido subsequente sem revalidação bem sucedida com o servidor de
origem.
@PUT e @POST
As anotações @PUT e @POST devem ser usadas em métodos de serviço REST para informar que os mesmos deverão tratar as operação HTTP PUT e POST, respectivamente.
Estas operações devem ser usadas para escrita de dados. Operações PUT são idempotentes, o que significa que podem ser executadas, repetidamente, sem efeitos colaterais no servidor, ao
contrário das operações POST, que produzem efeitos distintos, se executadas repetidas vezes.
Tabela 6.2. Atributos das anotações PUT e POST
Propriedade
Valor Padrão
Descrição
validatePreviousState StateValidationModel.NO_VALIDATE Se esta validação de estado está habilitada, o Crux adiciona um cabeçalho HTTP If-Match para garantir que a operação só será
executada se o cliente mantém o estado atual do recurso que está sendo atualizado.
@DELETE
A anotação @DELETE deve ser usada em métodos de serviço REST para informar que os mesmos deverão tratar as operação HTTP DELETE.
Esta operação deve ser usada para remoção de dados.
6.2.3. Opções de Parâmetros
Nesta seção, são apresentadas as notações que correspondem às opções de passagem de parâmetros para cada tipo de chamada REST. A tabela a seguir mostra uma lista com todos os
parâmentros e um resumo de como é feito o mapeamento de parâmentro de cada, em seguida está detalhado cada um deles.
Tabela 6.3. Opções de Parâmetros
Parâmetros
Mepeamento
@PathParam
Mapeamento de parâmentros como parte da URL.
@QueryParam
Mapeamento de parâmentros como uma query da URL.
@HeaderParam
Mapeamento de parâmentros como cabeçalho da requisição.
@CookieParam
Mapeamento de parâmentros como cookie na requisição.
@FormParam
Mapeamento de parâmentros como parâmetro de formulário.
Parâmetros no corpo da requisição
Parâmetros que não possuem nenhum tipo de anotação, são mapeados em notação JSON, no corpo da requisição.
@PathParam
A anotação @PathParam é utilizada para mapear um parâmetro em uma parte do path da URL. O parâmetro precisa estar associado a uma parte específica do path, delimitado na anotação @Path pelos
caracteres "{" e "}".
Os parâmetros associados ao path, não podem ter tipos compostos ou listas de valores. Apenas tipos simples podem ser usados com este mapeamento, como:
Primitivos e wrappers (Integer, Double, etc).
Strings.
Dates.
enums
BigDecimal e BigInteger
Não são suportados para este mapeamento objetos complexos, arrays, listas ou outras coleções.
O exemplo Classe de Serviço REST (PeopleService.java) mostra um mapeamento utilizando @PathParam.
@QueryParam
A anotação @QueryParam é utilizada para mapear um parâmetro como um parâmetro de query na URL. Por exemplo: http://myhost/rest/person?start=5&pageSize=20.
Não são suportados para este mapeamento nenhum tipo de lista de valores, como arrays, listas ou outras coleções.
O exemplo abaixo mostra um mapeamento que poderia ser feito para a URL http://myhost/rest/person?start=5&pageSize=20:
Exemplo 6.6. Mapeamento de parâmetros do tipo query (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
11
@RestService("personService")
@Path("person")
public class PeopleService {
@GET
public List<Person> listPeople(@QueryParam("start")int start, @QueryParam("pageSize")int pageSize) {
// retrieve the list of people ...
return list;
}
}
Um objeto complexo pode ser utilizado para agrupar vários parâmetros da URL, como mostrado no exemplo abaixo e que poderia ser mapeado para a URL http://myhost/rest/person?
person.start=5&person.pageSize=20:
Exemplo 6.7. Mapeamento de parâmetros do tipo query (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestService("personService")
@Path("person")
public class PeopleService {
@GET
public List<Person> listPeople(@QueryParam("person")PersonQuery queryParams) {
// retrieve the list of people ...
return list;
}
public static class PersonQuery
{
private int start;
private int pageSize;
// getters and setters
}
}
Caso se informe uma String vazia no mapeamento do parâmetro queryParams do exemplo acima, a URL associada seria idêntica a do exemplo anterior (http://myhost/rest/person?
start=5&pageSize=20).
@HeaderParam
A anotação @HeaderParam é utilizada para mapear um parâmetro como um cabeçalho da requisição.
Não são suportados para este mapeamento nenhum tipo de lista de valores, como arrays, listas ou outras coleções.
O exemplo abaixo mostra um mapeamento que poderia ser feito para a URL http://myhost/rest/person/1234 e que passasse um parâmetro via cabeçalho da requisição, onde "1234" é mapeado
pelo @PathParam({"id"}):
Exemplo 6.8. Mapeamento de parâmetros no cabeçalho (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
@RestService("personService")
@Path("person")
public class PeopleService {
@PUT
@Path("{id}")
public void savePerson(@HeaderParam("Xtoken")String protectionToken,
@PathParam("id") Long id) {
// save the person...
}
}
Repare que um objeto complexo pode ser utilizado para agrupar vários parâmetros, como mostrado no exemplo abaixo, onde a class PersonHeader é usado para agrupar String Xtoken e String
anotherHeader.
Exemplo 6.9. Mapeamento de parâmetros no cabeçalho usando objeto complexo (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestService("personService")
@Path("person")
public class PeopleService {
@PUT
@Path("{id}")
public List<Person> savePerson(@HeaderParam("person")PersonHeader header,
@PathParam("id") Long id) {
// retrieve the list of people ...
return list;
}
public static class PersonHeader
{
private String Xtoken;
private String anotherHeader;
// getters and setters
}
}
@CookieParam
A anotação @CookieParam é utilizada para mapear um parâmetro como um cookie na requisição.
Não são suportados para este mapeamento nenhum tipo de lista de valores, como arrays, listas ou outras coleções.
O exemplo abaixo mostra um mapeamento que poderia ser feito para a URL http://myhost/rest/person/1234 e que passasse um parâmetro via cookie da requisição, onde "1234" é mapeado pelo
@PathParam({"id"}):
Exemplo 6.10. Mapeamento de parâmetros via cookie (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
@RestService("personService")
@Path("person")
public class PeopleService {
@PUT
@Path("{id}")
public void savePerson(@CookieParam("myCookie")String myCookie,
@PathParam("id") Long id) {
// save the person...
}
}
Repare que um objeto complexo pode ser utilizado para agrupar vários parâmetros, como mostra o exemplo a seguir, onde a classe PersonCookie é usada para agrupar String myCookie e String
anotherProperty.
Exemplo 6.11. Mapeamento de parâmetros via cookie usando objeto complexo (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestService("personService")
@Path("person")
public class PeopleService {
@PUT
@Path("{id}")
public void savePerson(@CookieParam("person")PersonCookie cookie,
@PathParam("id") Long id) {
// save the person...
}
public static class PersonCookie
{
private String myCookie;
private String anotherProperty;
// getters and setters
}
}
@FormParam
A anotação @FormParam é utilizada para mapear um parâmetro como um parâmetro de formulário.
Não são suportados para este mapeamento nenhum tipo de lista de valores, como arrays, listas ou outras coleções.
O exemplo abaixo mostra um mapeamento que poderia ser feito para a URL http://myhost/rest/person/1234 e que passasse um parâmetro de formulário, no corpo da requisição, onde "1234" é
mapeado pelo @PathParam({"id"}):
Exemplo 6.12. Mapeamento de parâmetros de formulário (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
@RestService("personService")
@Path("person")
public class PeopleService {
@POST
@Path("{id}")
public void savePerson(@FormParam("passwd")String passwd,
@PathParam("id") Long id) {
// save the person...
}
}
Repare que um objeto complexo pode ser utilizado para agrupar vários parâmetros, como mostra o exemplo a seguir, onde a classe PersonForm é usada para agrupar String user e String passwd.
Exemplo 6.13. Mapeamento de parâmetros de formulário (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestService("personService")
@Path("person")
public class PeopleService {
@PUT
@Path("{id}")
public void savePerson(@FormParam("person")PersonForm form,
@PathParam("id") Long id) {
// save the person...
}
public static class PersonForm
{
private String user;
private String passwd;
// getters and setters
}
}
Parâmetros no corpo da requisição
Parâmetros que não possuem nenhum tipo de anotação, são mapeados em notação JSON, no corpo da requisição.
Este mapeamento suporta todos os tipos, incluindo tipos de lista de valores, como arrays, listas ou outras coleções.
O exemplo abaixo mostra um mapeamento que poderia ser feito para a URL http://myhost/rest/person/1234 e que passasse um objeto inteiro como parâmetro no corpo da requisição:
Exemplo 6.14. Mapeamento de parâmetros no corpo da requisição (PeopleService.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestService("personService")
@Path("person")
public class PeopleService {
@PUT
@Path("{id}")
public void savePerson(@PathParam("id") Long id, Person person) {
// save the person
}
public static class Person
{
private String login;
private String passwd;
private String name;
private Address address;
// getters and setters
}
}
Este tipo de mapeamento de parâmetro não pode ser feito para operações GET.
6.2.4. JSONP e CORS
JSONP
Devido a uma política de restrição chamada "política de mesma origem (Same Origin Policy)", que impedia que um domínio www.exemploA.com trocasse informações com um domínio
www.exemploB.com, surgiu-se a necessidade de se criar uma maneira de trocar informações entre diferentes domínios, sem nenhuma restrição.
Esta política possuí uma única exceção: a tag <script> do html. Com base nessa exceção, surgiu então o JSONP, que nada mais é do que uma "extensão" do JSON convencional.
Suponha-se que existe um serviço que retorne o seguinte JSON:
Exemplo 6.15. Exemplo de objeto JSON:
{"Nome":"João","idade":32}.
Se a requisição a este JSON fosse feita (sem utilizar JSONP) de um servidor fora do domínio de origem do serviço, a chamada seria barrada. Então como resolver o problema?Utilizando JSONP.
Em uma chamada a um servidor configurado com JSONP, o resultado da chamada acima não seria um JSON, e sim um código javascript que invoca uma função usando o JSON requisitado como
parâmetro. Segue o exemplo do que seria o retorno da mesma chamada utilizando-se JSONP:
Exemplo 6.16. Exemplo de retorno JSONP:
jsonpCallback({"Nome":"João","idade":32});
Observe que o objeto JSON está encapsulado dentro de um código javascript que invoca o método "jsonpCallback" (este parâmetro é customizável).
Esta chamada não será barrada pela política de mesma origem pois não se trata de JSON, e sim de código javascript executável, e conforme citado anteriormente, scripts são uma exceção à regra.
Mas esta é a utilização de JSONP em uma página web convencional. A seguir será mostrado como utilizar JSONP com o Crux Framework.
O Crux oferece suporte a JSONP através da anotação @UseJsonP e torna transparente todo o funcionamento citado anteriormente.
A anotação pode ser utilizada na classe da implementação do serviço REST, em um método específico desta classe ou em ambos, sendo que a anotação do método tem prioridade.
Basicamente, precisa-se de três passos para se utilizar JSONP com o Crux. São eles:
1. Adicionar a anotação @UseJsonP à interface do serviço REST.
Exemplo 6.17. Anotando interface do serviço REST
1
2
3
4
5
6
7
8
9
10
11
12
import
import
import
import
org.cruxframework.crux.core.client.rest.Callback;
org.cruxframework.crux.core.client.rest.RestProxy;
org.cruxframework.crux.core.client.rest.RestProxy.TargetRestService;
org.cruxframework.crux.core.client.rest.RestProxy.UseJsonP;
@TargetRestService("restService")
@UseJsonP
public interface IRestService extends RestProxy
{
void getHello(String nome, Callback<String> callback);
}
2. Adicionar a anotação @JsonPSupport na implementação do serviço REST ou aos métodos desejados.
Exemplo 6.18. Anotando a implementação do serviço REST
1
2
3
4
5
6
7
8
9
10
11
12
@RestService("restService")
@Path("rest")
@JsonPSupport
public class RestService
{
@GET
@Path("hello/{nome}")
public String getHello(@PathParam("nome")String nome)
{
return "Hello " + nome;
}
}
3. O último passo se resume a fazer uma chamada normal ao serviço REST.A seção Chamada de Serviços REST mostra como efetuar chamadas REST.
CORS
Uma outra alternativa para efetuar chamadas entre domínios diferentes é utilizar CORS.
CORS (Cross Origin Resource Sharing) é um mecanismo, que assim como JSONP, permite a troca de informações entre diferentes domínios. Pode ser considerado uma alternativa moderna ao uso
de JSONP, pois este último só oferece suporte à requisições do tipo GET, enquanto o CORS suporta outros tipos de requisições HTTP, além de ser mais fácil de ser usado e mais seguro. A
desvantagem do CORS fica por conta da falta de suporte dos browser mais antigos.
O funcionamento do CORS se dá ao acrescentar um novo HEADER do protocolo HTTP que determina a permissão de chamadas cross-domain. No Crux isto fica transparente e o suporte ao CORS se
dá através da anotação @CorsSupport. Esta interface possui os seguintes parâmetros de configuração:
Tabela 6.4. Parâmetros de configuração da anotação @CorsSupport :
Propriedade
Descrição
Valor default
allowOrigin
Array de Strings contendo os nomes dos domínios os quais terão acesso a este serviço. Por default, nenhum acesso de um domínio externo é permitido. É
permitida a utilização do wildcard "*" para configuração. Exemplo: "*.net, cruxframework.*, *"
Array de strings
vazio
allowOriginConfigFile Nome do arquivo de configuração que contém as Strings com os nomes dos domínios permitidos. O arquivo deverá estar no classpath e deverá conter apenas Vazio
um domínio por linha. A utilização do wildcard "*" é permitida.
allowCredentials
Por padrão, cookies não são incluídos em requisições CORS. Este parâmetro é utilizado para incluir cookies nas requisições
false
exposeHeaders
Requisições CORS podem acessar apenas os seguintes HEADERS: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified and Pragma.
Este parâmetro serve para configurar os HEADERS a serem expostos além dos padrões
Array de strings
vazio
maxAge
Tempo em segundos em que o browser irá manter o Cache do cabeçalho requistado em pré-flighted requests
Vazio. Campo
obrigatório
A anotação pode ser utilizada na classe da implementação do serviço REST, em um método específico desta classe ou em ambos, sendo que a anotação do método tem prioridade.
Exemplo 6.19. Configurando suporte ao CORS em um serviço REST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@org.cruxframework.crux.core.server.rest.annotation.RestService("restService")
@Path("rest")
@CorsSupport(allowOrigin={ "*.cruxframework.com"}, maxAge=15)
public class RestService{
@GET
@Path("hello/{nome}")
public String getHello(@PathParam("nome")String nome)
{
return "Hello " + nome;
}
}
6.2.5. JsonSubtypes
Esta seção mostra as ferramentas do Crux para tratar os casos possíveis durante a serialização de DTOs.
Serialização
Durante a serialização de objetos nas chamadas REST podemos ter heranças de objetos, o que nos permite termos um tipo Foo mais genérico utilizado em um serviço (no lado servidor) e passarmos
como parâmetro em um proxy (lado cliente) seu tipo Bar, mais especializado. Para isto, devemos configurar no objeto mais genérico (a ser serializado) as anotações @JsonSubtypes e
@JsonSubtypes.Type. Veja o exemplo a seguir:
Exemplo 6.20. FooDTO.java e BarDTO.java
1
2
3
4
5
6
7
8
@JsonSubTypes({
@JsonSubTypes.Type(value = BarDTO.class)
})
public class FooDTO implements Serializable
{
private static final long serialVersionUID = 1L;
protected Long id;
}
1 public class BarDTO extends FooDTO
2 {
3 protected String data;
4 }
JsonProperty
O Crux obedecerá o padrão Java de nomenclatura de variáveis durante a serialização das propriedades de um DTO, ou seja, se em um determinado DTO tivermos a propriedade age o Crux procurará
pelo método getAge() para o acesso desta propriedade. Neste tipo de regra, há uma exceção para aquelas propriedades que, por natureza, não seguem o padrão Java como, por exemplo, URL.
Quando o Crux procurar pelo get correspondente, ele tentará encontrar getUrl(), mas nem sempre este é o desejo do programador. Quando isto acontecer, podemos utilizar a notação @JsonProperty,
como exemplificada abaixo:
Exemplo 6.21. Exemplo de utilização da anotação @JsonProperty
1 public class BarDTO extends FooDTO
2 {
3 private String URL;
4
5 @JsonProperty("url")
6 public String getURL()
7 {
8
return url;
9 }
10
11 @JsonProperty("url")
12 public void setURL(String url)
13 {
14
return this.url = url;
15 }
16 }
6.2.6. Serialização de exceção do REST
As classes que respondem às requisições REST residem no servidor, ou seja, não são convertidas em código javascript. Os métodos dessas classes normalmente são responsáveis por tratar lógica
de negócios da aplicação. Nesse cenário, o utilizador do Crux pode sentir a necessidade de lançar exceções de negócio ao tratar uma requisição REST. O problema é que a resposta de uma
requisição REST é enviada para um Controller, classes do pacote client e que são convertidades em javascript. Para que a exceção lançada no servidor seja capturada no cliente, é necessário
serializá-la e adicioná-la na resposta HTTP. Isso pode ser feito automaticamente pelo Crux. Basta a classe de exceção de negócio a ser lançada estender da classe
org.cruxframework.crux.core.shared.rest.RestException
Exemplo 6.22. RestException
1 import org.cruxframework.crux.core.shared.rest.RestException;
2
3 public class BusinessException extends RestException {
4
private static final long serialVersionUID = 1L;
5 }
Feito isso, a exceção lançada é automaticamente serializada (desde que a classe de exceção seja completamente serializável) e atachada na resposta HTTP, a qual é retornada com código 403.
Exemplo 6.23. Serviço REST
1
2
3
4
5
6
7
8
@RestService("usersRestService")
@Path("users")
public class UsersRestService {
@GET
public List<User> get() throws BusinessException {
...
}
}
Essa exceção é repassada como parâmetro para o objeto de callback no método org.cruxframework.crux.core.client.rest.Callback.onError(Exception e)
Exemplo 6.24. Controller
1 @Controller("userController")
2 public class UserController {
3 @Inject
4 public UserRestProxy proxy;
5
6 public void showUsers() {
7
...
8
proxy.get(new Callback<List<User>>() {
9
...
10
@Override
11
public void onError(Exception e) {
12
if (e instanceof BusinessException) {
13
BusinessException be = (BusinessException) e;
14
}
15
}
16
});
17
...
18 }
19 }
6.3. ResourceStateHandler (Cache)
Crux possui suas próprias implementações de tratamento de Cache para chamadas REST, são elas: NoClusteredResourceStateHandler e ClusteredResourceStateHandler, ambas implementam a
interface ResourceStateHandler. O controle de cache é feito através de um atributo do Header do protocolo HTTP chamado ETAG (Entity tag). Esta ETAG é gerada a partir da data da última alteração
de um resource requisitado e serve para que o tratador de Cache identifique se deve ou não enviar uma nova cópia do resource acessado.
Para utilizar uma das duas implementações, basta adicionar ao arquivo crux.properties o atributo restServiceResourceStateHandler com o nome da classe da implementação desejada.
Exemplo 6.25. Configurações de Cache (Crux.properties)
1
2
3
4
5
6
7
8
9
10
#Define qual será a classe responsável por processar as dependências do classpath.
classPathResolver=org.cruxframework.crux.core.server.classpath.ClassPathResolverImpl
#Indica que a estratégia de busca dos recursos deve se basear em um ambiente de cluster.
restServiceResourceStateHandler=org.cruxframework.crux.core.server.rest.state.ClusteredResourceStateHandler
#Indica se os prefixos de URLs de REST devem ser baseados no Host ao invés Módulo.
enableRestHostPageBaseURL=false
NoClusteredResourceStateHandler:
Se trata de uma implementação básica da interface ResourceStateHandler e foi desenvolvida para rodar apenas em ambientes não clusterizados. Aqui os resources são armazenados na memória
local do servidor onde está configurado.
Para adicionar configurações específicas desta implementação, deve-se criar um arquivo chamado NoClusteredCacheConfig.properties e adicionar a propriedade maxNumberOfEntries com o valor
máximo de resources que se deseja manter em memória.
ClusteredResourceStateHandler:
Esta é uma implementação da interface ResourceStateHandler desenvolvida para ambientes clusterizados e pode ser utilizada como base para os mais poderosos sistemas de cache existentes,
como infinispan, EhCache, OsCache e qualquer outro sistema de cache. Esta implementação é baseada em org.jgroups.blocks.ReplCache , portanto a biblioteca jgroups.jar deverá ser adicionada
ao classpath da aplicação.
Para adicionar configurações específicas desta implementação, deve-se criar o arquivo ClusteredCacheConfig.properties. Os seguintes atributos estão disponíveis:
Tabela 6.5. Atributos de configuração do Crux para ClusteredResourceStateHandler:
Propriedade
Descrição
channelConfigPropertyFile
Nome do arquivo de configuração do JGroups channel.
rpcTimeout
Tempo máximo de espera para chamadas RPC do ReplCache
useL1Cache
Habiltar cache L1 (true or false)
l1ReapingInterval
Se o cache L1 estiver habilitado, este é o intervalo para rodar a thread que limpa valores expirados.
l1MaxNumberOfEntries
Se o cache L1 estiver habilitado, este será o valor máximo de entradas
l2ReapingInterval
Intervalo para rodar a thread que limpa valores expirados no cache L2
l1MaxNumberOfEntries
Número máximo de entradas no cache L2
clusterName
Nome do cluster utilizado por este cache
replCount
Número de nodos no cluster onde as informações serão replicadas
Criando uma implementação de tratamento de cache customizada
Caso os tratadores de cache existentes não sejam suficientes, é possível ainda criar uma implementação customizada. Para isto, basta criar uma classe que implemente a interface
ResourceStateHandler e implementar os métodos do contrato conforme a necessidade.
Para utilizar uma implementação, basta configurar o nome da nova classe na propriedade restServiceResourceStateHandler do arquivo crux.properties
6.4. Internacionalização
O Crux fornece um mecanismo para suportar internacionalização no lado servidor da aplicação, de forma similar ao mecanismo do GWT utilizado no lado cliente.
Pode-se criar interfaces e utilizar a anotação @org.cruxframework.crux.core.i18n.DefaultServerMessage nos métodos para informar valores associados a cada chave de internacionalização (os
nomes dos métodos da interface). Veja o seguinte exemplo abaixo:
Exemplo 6.26. Interface de mensagens para internacionalização
1 public interface MyMessages {
2
@DefaultServerMessage("My server message: {0}.")
3
String myServerMessage(String message);
4 }
Para se obter uma instância da interface de internacionalização definida, basta utilizar o métodoMessagesFactory.getMessages(<interfaceClass>).
Exemplo 6.27. Internacionalização do lado servidor
1 public MyServerClass
2 {
3
private static MyMessages messages = MessagesFactory.getMessages(MyMessages.class);
4
5
public void method()
6
{
7
System.out.println(messages.myServerMessage("test"));
8
}
9 }
Pode-se definir, agora, arquivos de propriedades contendo as mensagens a serem exibidas para outras localidades. Os arquivos devem ter o nome igual ao da interface de internacionalização, com
um sufixo identificando a localidade e devem estar visíveis no classpath da aplicação. Veja o exemplo abaixo:
Exemplo 6.28. MyMessages_pt_BR.properties
1
myServerMessage=Minha mensagem no servidor: {0}.
Pode-se, também, consultar diretamente a localização de um cliente da aplicação através do método: LocaleResolverInitializer.getLocaleResolver().getUserLocale() .
Estes mecanismos mostrados aqui podem ser utilizados em qualquer classe de serviço do Crux (REST ou RPC). O Crux identifica a localização do usuário em seu FrontController, antes de delegar o
controle para as classes de serviço da aplicação.
No entanto, caso seja necessário acessar uma mensagem internacionalizada em classes chamadas em um filtro (antes da execução do FrontController), é necessário configurar o filtro
org.cruxframework.crux.core.i18n.I18NFilter no arquivo web.xml da aplicação.
6.5. Banco de Dados
O Crux oferece suporte para criação de um banco de dados local no cliente da aplicação (no navegador). Este banco pode ser utilizado para ler e gravar informações diretamente na máquina do
usuário, mesmo quando este se encontra desconectado da rede.
O banco de dados segue uma estrutura similar à do IndexedDB (http://www.w3.org/TR/IndexedDB/) e requer suporte do navegador a uma das especificações: IndexedDB ou WebSQL.
6.5.1. Criando um banco de dados no cliente
Para definir uma nova base de dados no cliente, basta criar uma interface que estenda a interface Database e anotá-la com a anotação @DatabaseDef, para declarar a sua estrutura de dados. Veja o
exemplo abaixo:
Exemplo 6.29. Definindo uma base de dados
1 @DatabaseDef(name = "CruxCompanyDatabase", version = 1,
2
defaultErrorHandler = MyErrorHandler.class,
3
objectStores = { @ObjectStoreDef(targetClass = Person.class) })
4 public interface MyDatabase extends Database {
5 }
Repare que, através da anotação @DatabaseDef, o usuário informa toda a estrutura do seu banco, assim como outras características como tratadores de erros, versão do banco etc. A interface
Database inclui os métodos necessários para interagir com o banco. Veja o exemplo abaixo:
Exemplo 6.30. Usando um banco de dados
1 @Controller("myController")
2 public class MyController{
3 @Inject
4 private MyDatabase myDatabase;
5 // setters ommited
6
7 public void onLoad()
8 {
9
myDatabase.open(new DatabaseCallback(){
10
@override
11
public void onSuccess(){
12
Window.alert("database opened");
13
retrievePerson(123);
14
}
15
});
16 }
17
18 private void retrievePerson(Integer id)
19 {
20
myDatabase.get(id, Person.STORE_NAME, new DatabaseRetrieveCallback<Person>(){
21
public void onSuccess(Person result){
22
Window.alert("Person name: "+person.getName());
23
}
24
});
25
}
26 }
6.5.2. Acessando informações do Banco de Dados
Todos os acessos ao banco de dados são feitos de forma assíncrona. Isto significa que as operações realizadas sempre receberão um parâmetro de callback para que se possa tratar os resultados
obtidos pela operação realizada.
Cada tipo de operação possui uma classe de callback apropriada. Veja no exemplo abaixo algumas operações:
Exemplo 6.31. Callbacks em operações no banco de dados
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import org.cruxframework.crux.core.client.db.Transaction;
@Controller("myController")
public class MyController{
@Inject
private MyDatabase myDatabase;
// setters ommited
public void onLoad() {
myDatabase.open(new DatabaseCallback(){
@override
public void onSuccess(){
Window.alert("database opened");
loadPerson(123);
}
});
}
private void loadPerson(Integer id) {
myDatabase.get(id, Person.STORE_NAME, new DatabaseRetrieveCallback<Person>(){
public void onSuccess(Person result){
Window.alert("Person name: "+person.getName());
}
});
}
private void savePerson(Person person) {
Transaction transaction = myDatabase.getTransaction(new String[]{Person.STORE_NAME},
Transaction.Mode.readWrite, new TransactionCallback(){
public void onComplete(){
Window.alert("Transaction Completed!");
}
public void onAbort() {
Window.alert("Transaction aborted!");
}
public void onError(String message) {
Window.alert("Error ["+message+"]. Transaction rolled back!");
}
});
ObjectStore<Integer, Person> personStore = transaction.getObjectStore<Integer, Person>;
personStore.put(person);
}
}
Cada classe de callback possui, em geral, um método de sucesso para a operação e um método de erro. O método de sucesso tem a sua implementação como obrigatória para as suas subclasses. O
método de erro, delega o erro para o tratador associado ao banco de dados, mas pode ser sobrescrito pela classe de callback, caso a mesma necessite realizar alguma ação personalizada, como
mostrado no exemplo acima.
O exemplo nos mostra, também, que apesar de a maioria das classes de callback possuirem um método de sucesso e um de erro para a operação solicitada, existem algumas classes que possuem
outros métodos, como o TransactioCallback, visto que uma transação pode ter como resultado um terceiro estado (de ser abortada pelo usuário.)
6.5.3. Controle de Erros
É possível configurar um tratador padrão de erros para o banco de dados do Crux. Todos as operações feitas para o banco de dados são assíncronas e recebem um callback de tratamento dos
resultados. Estas classes de callback possuem sempre um método para tratamento de erros.
Os métodos de tratamento de erro não precisam ser declarados na classe de callback da operação, pois elas possuem uma implementação padrão que delega o erro recebido para o tratador de erros
padrão associado ao banco. Desta forma, todo erro do banco será direcionado para o tratador padrão, a menos que a classe de callback fornecida para a operação que gerou o erro possua um
método de tratamento de erro próprio.
Para criar um tratador de erros para o banco, basta implmentar a interface DatabaseErrorHandler. e para associar o tratador ao banco, basta referenciar a classe criada na anotação @DatabaseDef,
conforme mostrado no exemplo abaixo:
Exemplo 6.32. Tratador de erro
1 @DatabaseDef(name = "CruxCompanyDatabase", version = 1,
2
defaultErrorHandler = MyErrorHandler.class,
3
objectStores = {
4
@ObjectStoreDef(targetClass = Person.class),
5
})
6 public interface MyDatabase extends Database {
7 }
1 public class MyErrorHandler implements DatabaseErrorHandler {
2
3
public void onError(String message) {
4
Crux.getErrorHandler().handleError(message);
5
}
6
7
public void onError(String message, Throwable t) {
8
Crux.getErrorHandler().handleError(message, t);
9
}
10 }
6.5.4. Transações
O banco de dados do Crux suporta transações, que podem ser criadas a partir do método Database.getTransaction(). Ao se criar uma transação, é preciso informar a lista de objetos do banco que
poderão ser acessados ou alterados pela transação, além do tipo da transação (somente leitura ou leitura e escrita).
Através do objeto Transaction, obtido pelo método descrito acima, é possível obter uma referência para os demais objetos armazenados no banco. Desta forma, qualquer interação com o banco de
dados, deve começar por se obter uma transação. A interface Database, no entanto, fornece os métodos put, get e delete que manipulam objetos java diretamente no banco. Estes métodos criam uma
transação implicitamente para realizar estas operações.
O exemplo Callbacks em operações no banco de dados mostra uma transação sendo criada e utilizada para escrita de um objeto no banco de dados.
6.5.5. ObjectStores
ObjectStores são as estruturas utilizadas para armazenar objetos no banco de dados do Crux. Elas se assemelham à tabelas em um banco de dados tradicional. No entanto, o banco de dados do Crux
não é relacional. É um banco NoSQL, orientado a documentos. Para maiores informações sobre a estrutura do banco do Crux, consulte a especificação do IndexedDB.
As ObjectStores são representadas no Crux pela interface ObjectStore<K,V>, onde K é o tipo da chave usada para referenciar os objetos e V é o tipo do objeto a ser adicionado no banco.
Pode-se definir um ObjectStore de duas formas distintas, mostradas nas seções a seguir:
Anotações no objeto de dados
A anotação @Store pode ser utilizada para definir um ObjectStore capaz de armazenar objetos do tipo anotado. Por exemplo:
Exemplo 6.33. Anotação @Store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import org.cruxframework.crux.core.client.db.Transaction;
@Store(Person.STORE_NAME)
public class Person{
public static final String STORE_NAME = "person";
private Integer id;
private String name;
private Date birth;
@Key
public Integer getId() {
return id
}
public void setId(Integer id) {
this.id = id;
}
@Indexed
public String getName() {
return name
}
public void setName(String name) {
this.name = name;
}
public Date getBirth() {
return birth
}
public void setBirth(Date birth) {
this.birth = birth;
}
}
Repare que o campo id no exemplo acima está marcado como chave (@Key) para identificar os objetos dentro da ObjectStore e um índice foi definido para a coluna name. A seção XXX detalha o uso
de índices no banco e a tabela abaixo mostra mais detalhes sobre a chave de uma ObjectStore.
Tabela 6.6. Atributos da anotação KEY
Propriedade
Valor
Descrição
Padrão
order
-1
autoIncrement false
Quando mais de um campo é anotado como chave (@Key), é preciso definir a ordem em que eles são usados dentro de uma chave composta. Deve-se informar valores para
cada componente da chave composta de modo a definir uma ordem de prioridade para montagem do índice. Os campos mais restritivos devem ser colocados com uma
ordem menor, para uma melhor performance.
Caso a valor seja true, ao se inserir um novo registro com o campo identificador nulo, o Crux irá gerar uma chave auto incrementável para identificar o objeto. Chaves
compostas não podem ser auto-incrementáveis.
A ObjectStore definida no exemplo acima deve ser importada na definição do banco de dados, conforme mostrado no exemplo Definindo uma base de dados.
Metadado na interface do banco
É possível, também, adicionar informações sobre a estrutura do banco de dados, diretamente na anotação @DatabaseDef, presente na interface que define o banco de dados. Observe o exemplo
abaixo:
Exemplo 6.34. DatabaseDef
1 @DatabaseDef(name = "CruxCompanyDatabase", version = 1,
2
defaultErrorHandler = MyErrorHandler.class,
3
objectStores = {
4
@ObjectStoreDef(targetClass = Person.class),
5
@ObjectStoreDef(name="Client", targetClass = Person.class)
6
@ObjectStoreDef(name="MyObjectStore", keyPath={"prop1", "prop2"},
7
indexes={@IndexDef(name="myIndex", keyPath={"prop3"}, unique=true)})
8
})
9 public interface MyDatabase extends Database {
10 }
Repare que existem três declarações de ObjectStores mapeadas no exemplo.
A primeira informa apenas uma classe alvo para o mapeamento (Person.class), que pode ser vista no exemplo Anotação @Store. O nome da ObjectStore, as chaves e os índices serão todos
configurados a partir da anotações contidas na classe.
A segunda informa a mesma classe alvo, mas informa um novo nome para o ObjectStore. Desta forma, será criado uma nova store com a mesma estrutura da primeira declaração, mas com um
novo nome ("Client").
A terceira informa todas as configurações na própria anotação (nome, conjunto de chaves e índices).
Outro ponto importante a ser observado é que, caso a declaração da ObjectStore defina uma classe alvo para o mapeamento (targetClass), o Crux irá criar um objeto de acesso associado ao tipo da
classe informada (no caso, um ObjectStore<Integer, Person>). Caso não tenhamos tipos para a chave e para o objeto definidos de forma explícita, o Crux utiizará o tipo JsArrayMixed para chave (um
array javascript nativo) e o tipo JavaScriptObject como objeto a ser inserido no banco (qualquer objeto javascript nativo).
Informações sobre chaves compostas
É possível definir chaves compostas para objetos ou índices no banco de dados do Crux. As chaves compostas podem ser definidas via anotação na classe de mapeamento para o objeto do banco ou
via metadado na anotação de declaração do banco.
Veja o exemplo abaixo:
Exemplo 6.35. Chaves compostas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.cruxframework.crux.core.client.db.Transaction;
@Store(Person.STORE_NAME)
public class Person{
public static final String STORE_NAME = "person";
@Key(order=0)
private Integer id;
@Key(order=1)
private String name;
private Date birth;
public Integer getId() {
return id
}
public void setId(Integer id) {
this.id = id;
}
@Indexed
public String getName() {
return name
}
public void setName(String name) {
this.name = name;
}
public Date getBirth() {
return birth
}
public void setBirth(Date birth) {
this.birth = birth;
}
}
1 @DatabaseDef(name = "CruxCompanyDatabase", version = 1,
2
defaultErrorHandler = MyErrorHandler.class,
3
objectStores = {
4
@ObjectStoreDef(name="MyObjectStore", keyPath={"prop1", "prop2"})
5
})
6 public interface MyDatabase extends Database {
7 }
Repare que a chave composta definida no exemplo acima via anotação possui um campo order. A ordem dos componentes de uma chave composta precisa ser definida e influencia na performance
de operações de busca. Os campos da chave que forem mais específicos devem vir primeiro, para um melhor desempenho.
Quando utilizamos chaves compostas, devemos utilizar um array de objetos (Object[]) como tipo da chave para acessar objetos na ObjectStore. Veja o exemplo abaixo:
Exemplo 6.36. Usando Chaves compostas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.cruxframework.crux.core.client.db.Transaction;
@Controller("myController")
public class MyController{
@Inject
private MyDatabase myDatabase;
// setters ommited
public void onLoad() {
myDatabase.open(new DatabaseCallback(){
@override
public void onSuccess(){
Window.alert("database opened");
loadPerson(123, "Thiago");
}
});
}
private void loadPerson(Integer id, String name) {
myDatabase.get(new Object[]{id, name}, Person.STORE_NAME, new DatabaseRetrieveCallback<Person>(){
public void onSuccess(Person result){
Window.alert("Person name: "+person.getName());
}
});
}
}
6.5.6. Cursores
Os cursores são objetos utilizados para leitura de dados obtidos por operações de consulta ao banco. Um cursor pode ser obtido a partir de uma consulta feita a um ObjectStore ou a um índice do
banco. Veja os exemplos abaixo:
Exemplo 6.37. Cursores
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import org.cruxframework.crux.core.client.db.Cursor;
import org.cruxframework.crux.core.client.db.DatabaseCursorCallback;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import org.cruxframework.crux.core.client.db.Cursor;
import org.cruxframework.crux.core.client.db.DatabaseCursorCallback;
import org.cruxframework.crux.core.client.db.KeyRange;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import org.cruxframework.crux.core.client.db.Cursor;
import org.cruxframework.crux.core.client.db.DatabaseCursorCallback;
import org.cruxframework.crux.core.client.db.KeyRange;
@Controller("myController")
public class MyController{
@Inject
private MyDatabase myDatabase;
// setters ommited
public void onLoad()
{
myDatabase.open(new DatabaseCallback(){
@override
public void onSuccess(){
searchPeople();
}
});
}
private void searchPeople()
{
Transaction transaction = myDatabase.getTransaction(new String[]{Person.STORE_NAME},
Transaction.Mode.readWrite);
ObjectStore<Integer, Person> personStore = transaction.getObjectStore<Integer, Person>;
personStore.openCursor(new DatabaseCursorCallback<Integer, Person>(){
public void onSuccess(Cursor<Integer, Person> cursor) {
if (cursor != null && cursor.hasValue()) {
Person person = cursor.getValue();
Integer key = cursor.getKey();
Window.alert("Found person ["+person.getName()+"]. Person ID ["+key+"]");
cursor.continueCursor();
}
}
});
}
}
@Controller("myController")
public class MyController{
@Inject
private MyDatabase myDatabase;
// setters ommited
public void onLoad()
{
myDatabase.open(new DatabaseCallback(){
@override
public void onSuccess(){
searchPeople();
}
});
}
private void searchPeople()
{
Transaction transaction = myDatabase.getTransaction(new String[]{Person.STORE_NAME},
Transaction.Mode.readWrite);
ObjectStore<Integer, Person> personStore = transaction.getObjectStore<Integer, Person>;
KeyRange<Integer> keyRange = personStore.getKeyRangeFactory().lowerBound(123);
personStore.openCursor(keyRange, new DatabaseCursorCallback<Integer, Person>(){
public void onSuccess(Cursor<Integer, Person> cursor) {
if (cursor != null && cursor.hasValue()) {
Person person = cursor.getValue();
Integer key = cursor.getKey();
Window.alert("Found person ["+person.getName()+"]. Person ID ["+key+"]");
cursor.continueCursor();
}
}
});
}
}
@Controller("myController")
public class MyController{
@Inject
private MyDatabase myDatabase;
// setters ommited
public void onLoad()
{
myDatabase.open(new DatabaseCallback(){
@override
public void onSuccess(){
searchPeople();
}
});
}
private void searchPeople()
{
Transaction transaction = myDatabase.getTransaction(new String[]{Person.STORE_NAME},
Transaction.Mode.readWrite);
ObjectStore<Integer, Person> personStore = transaction.getObjectStore<Integer, Person>;
KeyRange<Integer> keyRange = personStore.getKeyRangeFactory().only(123);
personStore.openCursor(keyRange, CursorDirection.next, new DatabaseCursorCallback<Integer, Person>(){
public void onSuccess(Cursor<Integer, Person> cursor) {
if (cursor != null && cursor.hasValue()) {
Person person = cursor.getValue();
Integer key = cursor.getKey();
Window.alert("Found person ["+person.getName()+"]. Person ID ["+key+"]");
cursor.continueCursor();
}
}
});
}
}
Os exemplos acima mostram como realizar buscas em uma ObjectStore e utilizar um cursor para leitura dos dados. Um cursor de uma busca que tenha retornado elementos, aponta para um objeto
dentro no banco. Os métodos getKey() e getValue() podem ser usados para recuperar a chave e o valor do objeto no banco, referenciados pelo cursor.
Pode-se, então, navegar pelo cursor para recuperar os demais objetos retornados pela busca. O método continueCursor() solicita ao cursor que avance para o próximo elemento. Quando o cursor
avança de posição, o método onSuccess() do objeto de callback fornecido é novamente chamado, agora com o cursor posicionado na nova configuração. Os métodos advance(count) e
continueCursor(Key) também podem ser usados para navegar entre os elementos do cursor.
É possível, também, utilizar um cursor para atualizar um valor no banco. O método update(value) atualiza o registro apontado pelo cursor com o valor informado.
Quando se solicita a abertura de um cursor, é ainda possível fornecer um filtro para os valores e determinar a direção que o cursor irá percorrer o conjunto de elementos retornados. Observe no exemplo
acima o uso da classe KeyRange. A mesma possibilita que informemos uma faixa de valores para serem usados na busca por valores. O exemplo nos mostra, também, como informar o sentido de
navegação do cursor.
6.5.7. Índices
O Crux suporta índices para propriedades de elementos armazenados em suas ObjectStores, para agilizar a recuperação e consulta de dados no banco. Assim como as ObjectStores, os índices
podem ser definidos por meio da anotação @Index junto a propriedades do objeto de dados, ou declarada através da anotação @IndexDef, conforme ilustrado nos exemplos abaixo:
Exemplo 6.38. Índices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import org.cruxframework.crux.core.client.db.Transaction;
@Store(value=Person.STORE_NAME, indexes={@IndexDef(name="birth", keyPath={"birth"}, unique=false)})
public class Person{
public static final String STORE_NAME = "person";
@Key
private Integer id;
private String name;
private Date birth;
public Integer getId() {
return id
}
public void setId(Integer id) {
this.id = id;
}
@Indexed(unique=true)
public String getName() {
return name
}
public void setName(String name) {
this.name = name;
}
public Date getBirth() {
return birth
}
public void setBirth(Date birth) {
this.birth = birth;
}
}
1 @DatabaseDef(name = "CruxCompanyDatabase", version = 1,
2
defaultErrorHandler = MyErrorHandler.class,
3
objectStores = {
4
@ObjectStoreDef(targetClass = Person.class,
5
indexes={@IndexDef(name="nameBirth", keyPath={"name,birth"}, unique=true)})
6
})
7 public interface MyDatabase extends Database {
8 }
O primeiro exemplo define dois índices para a ObjectStore"Person". Um dos índices é definido via anotação @IndexDef e o outro via anotação @Indexed, colocada no método de acesso da
propriedade "name".
O segundo exemplo mostra um terceiro índice, declarado diretamente no metadado do banco, utilizando a anotação @IndefDef. Repare, também, que este terceiro índice criado envolve mais de uma
propriedade ("name" e "birth").
Os índices são representados no Crux pela classe Index<K,I,V>, onde K é o tipo da chave dos objetos no banco, I é o tipo da chave do ínidice (o tipo da propriedade indexada) e V é o tipo do objeto
referenciado pelo índice. Veja o exemplo abaixo:
Exemplo 6.39. Usando Índices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import org.cruxframework.crux.core.client.db.Cursor;
import org.cruxframework.crux.core.client.db.DatabaseCursorCallback;
import org.cruxframework.crux.core.client.db.KeyRange;
@Controller("myController")
public class MyController{
@Inject
private MyDatabase myDatabase;
// setters ommited
public void onLoad()
{
myDatabase.open(new DatabaseCallback(){
@override
public void onSuccess(){
searchPeople();
}
});
}
private void searchPeople()
{
Transaction transaction = myDatabase.getTransaction(new String[]{Person.STORE_NAME},
Transaction.Mode.readWrite);
ObjectStore<Integer, Person> personStore = transaction.getObjectStore<Integer, Person>;
Index<Integer, String, Person> nameIndex = personStore.getIndex("name");
Index<Integer, Date, Person> birthIndex = personStore.getIndex("birth");
Index<Integer, Object[], Person> nameBirthIndex = personStore.getIndex("nameBirthIndex");
nameIndex.get("Thiago", new DatabaseRetrieveCallback<Person>(){
public void onSuccess(Person person) {
Window.alert("Found person ["+person.getName()+"].");
}
});
KeyRange<Date> keyRange = birthIndex.getKeyRangeFactory().lowerBound(new Date());
birthIndex.openCursor(keyRange, CursorDirection.next, new DatabaseCursorCallback<Date, Person>(){
public void onSuccess(Cursor<Date, Person> cursor) {
if (cursor.hasValue()) {
Person person = cursor.getValue();
Date key = cursor.getKey();
Window.alert("Found person ["+person.getName()+"]. Index Key ["+key+"]");
cursor.continueCursor();
}
}
});
KeyRange<Object[]> keyRange = nameBirthIndex.getKeyRangeFactory().lowerBound(
new Object[]{"Thiago", new Date()});
nameBirthIndex.openKeyCursor(keyRange, CursorDirection.next, new DatabaseCursorCallback<Object[], Integer>(){
public void onSuccess(Cursor<Object[], Integer> cursor) {
if (cursor.hasValue()) {
Integer personKey = cursor.getValue();
Object[] indexKey = cursor.getKey();
Window.alert("Found person Key ["+personKey+"]. Index Key ["+key[0]+","+key[1]+"]");
cursor.continueCursor();
}
}
});
}
}
Os exemplos acima ilustram algumas situações de busca usando os índices anteriormente definidos. Repare nos métodos get(String, callback), openCursor(KeyRange, callback) e
openKeyCursor(Keyrange, callback) mostrados no exemplo. Eles nos mostram como acessar um elemento pelo índice, realizar uma consulta de valores com um filtro e realizar uma consulta aos
índices dos objetos.
Outro ponto importante a ser observado é que índices com chaves compostas funcionam de maneira similar às ObjectStores. Basta usar o tipo Object[] como tipo da chave do índice.
6.6. Application cache (Aplicação offline)
HTML5 introduziu um concetio de cache de aplicação. Isso significa que é possível utilizar uma aplicação web mesmo não estando conectado com a internet. Além disso, o cache permite que as
aplicações web respondam mais rápido por não acessar o servidor de aplicação a todo momento e, consequentemente, diminui a carga do servidor. O recurso de cache é suportado pelos
navegadores mais recentes, como Internet Explorer 10, Firefox, Chrome, Safari e Opera (versões mais atuais).
6.6.1. Conceitos básicos
Para ativar o recurso de cache numa página HTML, basta incluir o atributo manifest na tag html do documento:
Exemplo 6.40. Conceitos básicos
1
2
3
4
<!DOCTYPE HTML>
<html manifest="main.appcache">
...
</html>
Todas as páginas com esse atributo serão cacheadaa quando visitadas pelo usuário. Se o atributo não for especificado, a página não será cacheada (a menos que a página esteja explicitamente
especificada no arquivo manifest, como veremos a seguir).
6.6.2. Arquivo MANIFEST
O arquivo MANIFEST é um simples arquivo de texto que indica ao navegador quais recursos devem (ou nunca devem) ser mantidos em cache. Este arquivo possui 3 seções:
CACHE MANIFEST: arquivos listados nessa seção serão mantidos em cache depois que forem baixados pela primeira vez
NETWORK: arquivos listados nessa seção requerem uma conexão com o servidor e nunca serão mantidos em cache
FALLBACK: arquivos listados nessa seção especificam páginas de contorno quando a página requisitada não estiver acessível
CACHE MANIFEST
A primeira linha do arquivo, CACHE MANIFEST, é obrigatória:
Exemplo 6.41. CACHE MANIFEST
1
2
3
4
CACHE MANIFEST
/theme.css
/logo.gif
/main.js
O arquivo manifest acima lista 3 recursos: um arquivo .CSS, uma imagem .GIF e um arquivo JavaScript. Quando o arquivo manifest é carregado, o navegador fará o download desses 3 arquivos do
diretório raiz do web site. A partir de então, mesmo que o usuário não esteja conectado à internet, o recurso ainda estará disponível.
NETWORK
A seção NETWORK abaixo especifica que o arquivo "login.asp" nunca será mantido em cache, e nunca ficará disponível quando a aplicação estiver operando no modo offline.
Exemplo 6.42. NETWORK
1 NETWORK:
2 login.asp
Um asterisco pode ser utilizado para indicar que todos os recursos necessitam de conexão com a internet.
Exemplo 6.43. Coringa para sessão NETWORK
1 NETWORK:
2 *
FALLBACK
A seção FALLBACK abaixoe especifica que "offline.html" será utilizada pelo navegador no lugar de qualquer arquivo no diretório /html/ caso não exista conexão com a internet.
Exemplo 6.44. FALLBACK
1 FALLBACK:
2 /html/ /offline.html
A extensão recomendada para os arquivos manifest é ".appcache".
6.6.3. Atualização do cache
Uma vez que uma aplicação está em cache, ela permanecerá cacheada até que uma das coisas aconteça:
O usuário limpe o cache do navegador
O arquivo manifest seja modificado (veja a dica abaixo)
O cache da aplicação seja programaticamente modificado
Exemplo 6.45. Exemplo com timestamp
1
2
3
4
5
6
7
8
9
10
11
CACHE MANIFEST
# 2012-02-21 v1.0.0
/theme.css
/logo.gif
/main.js
NETWORK:
login.asp
FALLBACK:
/html/ /offline.html
Linhas começando com "#" são consideradas comentários, mas podem servir para outro propósito. Quando um recurso em cache é atualizado no servidor, uma alteração em comentário forçará o
cache dos navegadores a serem atualizados. Utilizar um mecanismo de timestamp ou número de versão num comentário do arquivo manifest é uma boa dica.
6.6.4. Suporte Crux para o appcache
O Crux possui suporte para esse poderoso novo recurso do HTML5. Para ativá-lo, basta importar o módulo CruxOffline no arquivo ".gwt.xml"" do projeto, criar um arquivo com extensão ".offline.xml" (que
servirá como screen no modo offline) e adicionar o filtro org.cruxframework.crux.core.server.offline.AppcacheFilter no arquivo web.xml da aplicação. O Crux fará o controle de todo processo
descrito acima.
Exemplo 6.46. Habilitando Appcache
1 <module>
2
3 <!-- Crux
4 <inherits
5 <inherits
6 <inherits
7 <inherits
8 ...
dependencies -->
name='org.cruxframework.crux.core.Crux' />
name='org.cruxframework.crux.gwt.CruxGWTWidgets' />
name='org.cruxframework.crux.widgets.CruxWidgets' />
name='org.cruxframework.crux.core.CruxOffline'/>
Abaixo, um exemplo de um arquivo *.offline.xml:
Exemplo 6.47. Screen Offline
1 <offlineScreen xmlns="http://www.cruxframework.org/offline" moduleName="sample" screenId="index.html" >
2
3 </offlineScreen>
Abaixo, a configuração do arquivo web.xml:
Exemplo 6.48. Configurando filtro
1
2
3
4
5
6
7
8
9
10
11
12
13
...
<filter>
<filter-name>appCacheFilter</filter-name>
<filter-class>org.cruxframework.crux.core.server.offline.AppcacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>appCacheFilter</filter-name>
<url-pattern>*.appcache</url-pattern>
</filter-mapping>
...
O Crux gerará os arquivos ".appcache"" e mapeará neles todos os recursos gerados pela compilação do GWT (para cada cada permutação). Além disso, as páginas de acesso já estarão
referenciando o arquivo gerado. E os arquivos manifest serão "carimbados" com o timestamp de quando foram gerados, facilitando a atualização do cache.
6.6.5. A Classe Network
A classe org.cruxframework.crux.core.clientoffline.NetworkNetwork do Crux permite que o programador registre um tratador de eventos de conexão. Dessa forma, ele será informado,
assíncronamente, se a conexão com servidor caiu ou foi restabelecida e, a partir disso, ajustar o estado da aplicação de forma conveniente. Para registrar o tratador de eventos, deve ser criada uma
classe que implemente a interface org.cruxframework.crux.core.clientoffline.NetworkEvent.Handler e adicioná-lo como ouvinte de eventos de conexão por invocar o método
Network.addNetworkHandler, passando como parâmetro uma instância do handler criado.
Exemplo 6.49. Classe Network AppCache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Controller("mainController")
public class MainController
{
@Expose
public void onLoad()
{
Network.get().addNetworkHandler(new NetworkEvent.Handler()
{
@Override
public void onNetworkChanged(NetworkEvent event)
{
// CÓDIGO PARA GERENCIAR EVENTOS DE CONEXÃO
}
});
}
}
6.6.6. Observações
Seja cuidadoso com o seu cache. Uma vez que um arquivo esteja em cache, o navegador continuará exibindo a versão armazenada mesmo se o mesmo for alterado no servidor. Para assegurar que os
navegadores atualizarão o cache, é necessário alterar o arquivo manifest. É importante lembrar também que navegadores diferentes podem ter capacidades diferentes para a quantidade de dados em
cache (alguns navegadores tem limite de 5MB por site).
Capítulo 7. Segurança
Neste capítulo são tratados problemas de segurança comuns de aplicações web e o que o Crux oferece de solução.
7.1. Segurança no Crux
A arquitetura do Crux mantém o front controller no lado do cliente. Este código, por definição, não deve conter regras de negócio e deve servir apenas para o controle da camada de visão, com regras
simples como navegação entre Views, validações comuns de campos etc.
Para ilustrar melhor este modelo, será dado um caso real de exemplo: uma interface desabilita o botão de excluir se o usuário corrente não tem o perfil de administrador. A operação de habilitar ou não
um botão é feita pelo Controller do lado do cliente. Um invasor, pode alterando o código do cliente, habilitar o botão de excluir. Quando a operação de exclusão é enviada ao servidor onde os dados
são persistidos, ele irá verificar que essa é uma operação não autorizada e irá bloquear a exclusão.
Assim, um usuário mal intencionado poderia até modificar o comportamento da interface, mas no momento que o servidor fosse acessado este validará os dados e garantirá a segurança da aplicação.
Uma exceção a esta regra é quando a aplicação a ser desenvolvida necessita manter certas regras de negócio no cliente, como por exemplo quando há a utilização do recurso de banco de dados
offline. Então, deve-se tomar cuidado com o que efetivamente deve ser exposto ao usuário e o que ficará contido no servidor.
Modificar e observar os dados da interface ainda pode ser uma tarefa difícil, uma vez que o código javascript gerado pelo compilador pode ser ofuscado. Mesmo assim, todos sabemos que garantir
que regras de negócio não estejam alocadas no cliente e ofuscar o código visível ainda não garantem um sistema seguro. Há diversas maneiras de se acessar o conteúdo do servidor de formas mal
intencionadas e um dos papéis do Crux é combater estes ataques oferecendo mecanismos de defesa eficientes, transparentes e customizáveis.
7.2. Proteção contra Ataques
Nesta seção será falado mais sobre os possíveis ataques à aplicação e os mecanismos de defesa que estão disponíveis no Crux.
7.2.1. Ataques de XSS
O Crux e o GWT possuem mecanismos de proteção contra XSS. As escritas de valores em componentes de telas (widgets) são feitas, preferencialmente, através da propriedade innerText e não via
escrita de HTML (innerHTML ou outros métodos), o que evita o ataque. Ataques XSS através de escritas na innerText só seriam possíveis na tag script do DOM, o que não é a base de nenhum
elemento de tela. (Referência: OWASP - DOM based XSS Prevention Cheat Sheet).
O GWT possui um método para filtrar as entradas que ainda sim precisem ser feitas diretamente via HTML (Input Filtering), através da classe SafeHTMLBuilder. Todos os componentes de tela do Crux e
do GWT escrevem dados usando uma das duas abordagens acima. Desta forma, aplicações feitas com o Crux estariam vulneráveis apenas se elas escrevessem informações diretamente no DOM,
com códigos nativos ou recursos similares, ao invés de usar as widgets do Crux para isso, ou se elas escrevessem novos componentes que não se previnam destes ataques.
7.2.2. Ataques de CSRF
O Crux fornece dois mecanismos para proteção contra ataques de CSRF. Um mecanismo consiste em anotar os métodos considerados sensíveis (e que seriam potenciais alvos de ataques de CSRF)
com a anotação @UseSynchronizerToken. O Crux, então, implementa o padrão SynchronizerToken para proteger estas requisições. (Referência: OWASP - Cross-Site Request Forgery (CSRF)
Prevention Cheat Sheet).
O segundo mecanismo está disponível apenas para os clientes de serviços REST do Crux. Ele utiliza uma token de segurança como cabeçalho da requisição. Este é um mecanismo mais simples de
proteção, mas que tem como vantagem a performance. Por ele não adicionar overhead absolutamente nenhum às requisições, ele é sempre habilitado para as operações de escrita via REST.
Obviamente, pode-se optar por reforçar a segurança adicionando mecanismos extras de proteção, como outros tokens de sincronização.
Desta forma, as aplicações feitas em Crux possuem essas duas ferramentas prontas para proteger as suas operações contra CSRF. Ainda sim elas podem estar vulneráveis caso não configurem
nenhum dos dois mecanismos para as operações que elas expõem (operações RPC simples sem token de sincronização). É preciso verificar a implementação das aplicações e verificar se estão
configurando esses tokens para as operações sensíveis.
7.2.3. Ataques de Clickjackings
Este é um ataque que pode ser combatido com uma configuração nos servidores da aplicação. Pode-se configurar os servidores para devolver os arquivos da aplicação (os .HTML e os .JS) para
serem servidos com um cabeçalho na resposta: X-Frames-Options, com o valor "SAMEORIGIN".
No entanto, esta configuração não impede o ataque em todos os navegadores. Existem navegadores que não suportam este cabeçalho. Por isso, o Crux vai incluir um mecanismo adicional de
segurança baseado em javascript. Existe já um Issue aberto para isso (298) que está previsto para ser entregue na versão 5.1 do framework.
7.2.4. Outros tipos de ataques
Existem diversos outros tipos de ataques. Estes, em geral, não são tratados pelo framework, mas sim pela boa codificação da aplicação e também pela utilização correta de recursos de infraestrutura.
Como exemplos, podem ser citados a utilização de firewalls para bloquear acessos indevidos em portas do servidor, a utilização de HTTPS para previnir ataques do tipo "man in the middle", utilização
de captchas para bloquear ataques DOS etc.
Capítulo 8. Ferramentas do Crux
Neste capítulo é apresentado o compilador do Crux, com seus parâmetros de execução e algumas ferramentas de apoio do Crux Framework.
8.1. Compilador do Crux
O compilador do Crux e do GWT têm a função primária de gerar todos os arquivos html e javascript otimizados que serão processados pelo navegador. O input do compilador do Crux são os arquivos
da aplicação: as Views, Controllers, Templates, Accessors, DataSources, etc. Após a execução do compilador do Crux, serão gerados artefatos que servirão de input para o compilador do GWT. Este,
por sua vez, otimiza ainda mais o código e gera os arquivos html e javascript finais.
O compilador do Crux é invocado a partir de uma classe Java org.cruxframework.crux.tools.compile.CruxCompiler que pode ser executada através de uma tarefa Maven, já definida no POM da
aplicação. Para simplificar o trabalho de invocar esta tarefa, o arquivo POM da aplicação já foi configurado de forma a invocarmos somente a diretiva install do Maven. Após invocá-la, será gerado o
WAR final (caso a aplicação seja um container) ou o JAR final (caso a aplicação seja um módulo). Este arquivo gerado está localizado na raiz da pasta target da aplicação.
8.1.1. Parâmetros de compilação
Tabela 8.1. Parâmetros de compilação do Crux
Parâmetro
Descrição
Valores
aceitos*
Valor padrão*
outputDir
A pasta de output onde os arquivos compilados serão inseridos
-
${project.build.directory}/${project.artifactId} sim
sourceDir
A pasta source do projeto
-
${project.basedir}/src/main/java
webDir
A pasta web root da aplicação
-
${project.build.directory}/${project.artifactId} sim
classpathDir
A pasta relativo ao classpath. O Crux a usará para processar seus recursos, como a localização
das Views, Templates, etc.
-
O classpath da aplicação
sim
resourcesDir
A pasta de recursos estáticos da aplicação
-
${basedir}/src/main/resources
não
pagesOutputDir
A pasta para onde os arquivos gerados serão criados
-
${project.build.directory}/${project.artifactId} sim
scanAllowedPackages
Uma lista de pacotes (separados por vírgula) que serão escaneados para achar Controles,
Módulos, etc. Esta informação também pode ser informada no arquivo Crux.properties.
-
-
não
scanIgnoredPackages
Uma lista de pacotes (separados por vírgula) que não serão escaneados para achar Controles,
Módulos, etc. Esta informação também pode ser informada no arquivo Crux.properties.
-
-
sim
outputCharset
O charset dos arquivos de output
-
UTF-8
sim
pageFileExtension
A extensão das páginas geradas
-
html
não
indentPages
Se true, as páginas serão identadas
true | false true
não
keepPagesGeneratedFiles
Se false, as páginas geradas serão removidas após a compilação
true | false true
não
doNotPreCompileJavaSource Faz com que o compilador ignore a pré compilação Java
true | false false
não
help
Exibe a tela de ajuda
-
-
não
h
Exibe a tela de ajuda
-
-
não
Obrigatório
sim
* Alguns valores das colunas seguintes contém o uso de cifrão e chaves, que é uma notação da gramática do Maven. Para maiores informações, consulte Documentação do Maven.
Tabela 8.2. Parâmetros de compilação do GWT
Parâmetro
Descrição
Valores aceitos
Valor padrão
Obrigatório
logLevel
O nível de detalhes de log
ERROR | WARN | INFO | TRACE | DEBUG |
SPAM | ALL
-
não
workDir
O diretório de trabalho do compilador para uso interno (deve ser um local com permissão de
escrita)
-
Pasta temp do
sistema
não
gen
Opção para debug: faz com que os arquivos transientes, gerados pelo DeferredBinding sejam
salvos em uma pasta
-
-
não
style
O nível de detalhamento do javascript gerado
OBFUSCATED | PRETTY | DETAILED
OBFUSCATED
não
ea
Opção para debug: faz com que a saída do compilador verifique por asserts no código
true | false
false
não
validateOnly
Valida o código fonte, mas não o compila
true | false
false
não
draftCompile
Habilita uma compilação mais rápida, mas menos otimizada
true | false
false
não
compileReport Cria um relatório de compilação para informar detalhes da compilação
pasta do sistema
-
não
localWorkers
O número de processadores que serão utilizados durante a compilação
1...N
-
não
war
O diretório para onde o WAR será gerado
pasta do sistema
war
não
extra
O diretório para onde os arquivos adicionais (não relacionados ao deploy) serão escritos
pasta do sistema
-
não
Além destes parâmetros, ainda podem ser passados parâmetros da JVM como, por exemplo:
Tabela 8.3. Parâmetros opcionais para JVM
Parâmetro
Descrição
Valores
Xss
JVM: Ajusta o tamanho da pilha de threads (controla a StackOverflowError)
-
org.mortbay.naming.InitialContextFactory GWT: A propriedade de sistema que define a fábrica de contexto inicial utilizada na configuração de inicialização
-
Xms
JVM: Especifica a memória inicial alocada para a JVM
-
Xmx
JVM: Especifica o máximo de memória alocável para a JVM
-
Xdebug
GWT: Parâmetro que informa se o Jetty deve subir em modo de Debug ou não
true |
false
DCrux.dev
Crux: Informa para o compilador se o ambiente é de desenvolvimento ou produção
true |
false
XX:MaxPermSize
JVM: Informa o máximo de memória de conteúdo permanente que será utilizado pela VM, como classes, métodos, objetos de internacionalização e objetos similares.
8.2. Gerador de Schema
A ferramenta de geração de schemas é responsável por gerar catálogos de componentes do GWT, Crux, Widgets, como também componentes e templates criados pelo próprio usuário. Estes
catálogos são arquivos XSDs que são importantes não somente como meta modelo de cada componente, mas principalmente pela possibilidade de utilização da função de auto complete na IDE, que
é muito útil para o desenvolvedor.
O gerador de schema é invocado a partir de uma classe Java org.cruxframework.crux.tools.schema.SchemaGenerator que pode ser executada através de uma tarefa Maven, já definida no POM da
aplicação. Para simplificar o trabalho de invocar esta tarefa, existe a diretiva de execução Generate Schemas.launch disponível na raiz da projeto. Os arquivos gerados pela execução desta diretiva
estão na pasta xsd localizada na raiz do projeto. Veja o exemplo abaixo.
Figura 8.1. Exemplo de arquivos gerados
Para que a função de auto complete esteja disponível, adicionar o catálogo crux-catalog.xml nas configurações da IDE utilizada. É importante lembrar que é uma boa prática regerar este catálogo a
cada novo template ou componente criado pelo usuário.
8.3. Code Server
O CodeServer (ou Servidor de Código) é uma ferramenta para invocar o SuperDevMode do GWT. O SuperDevMode permite eliminar a utilização do plugin instalado no navegador, que é responsável
por compilar o código Java em Javascript. Esta tarefa agora é delegada para o servidor de código, que vem cada vez mais sendo otimizado para realizar este trabalho rapidamente.
O CodeServer é invocado a partir de uma classe Java org.cruxframework.crux.tools.codeserver.CodeServer que pode ser executada através de uma tarefa Maven, já definida no POM da
aplicação. Para simplificar o trabalho de invocar esta tarefa, o arquivo POM da aplicação já foi configurado de forma a invocarmos somente a diretiva Start CodeServer.launch. Após invocá-la, o
servidor de códigos será iniciado.
Para habilitar o CodeServer, entre no arquivo de configuração do módulo (_.gwt.xml) e habilite as seguintes linhas:
Exemplo 8.1. Configurações para se habilitar o CodeServer (SuperDevMode)
1
2
3
4
5
6
7
8
9
<!-- To enable super dev mode -->
<add-linker name="xsiframe"/>
<set-configuration-property name="devModeRedirectEnabled" value="true"/>
<set-property name="compiler.useSourceMaps" value="true" />
<set-property name="user.agent" value="<<DesiredUserAgent>>" />
<set-property name="device.features"
value="<< largeDisplayMouse || largeDisplayTouch ||
largeDisplayArrows || smallDisplayTouch ||
smallDisplayArrows >>"</set-property>
Em seguida, visite o endereço http://localhost:9876/ . Ao entrar, copie os links Dev Mode On e Dev Mode Off. Em seguida, acesse uma página do sistema que utilize um dos módulos listados na
página. Por fim, acesse o link copiado Dev Mode On. Dica: caso o navegador utilizado não aceite salvar os links nos favoritos (que é um passo sugerido na própria página do SuperDevMode), abra
uma aba com a aplicação e outra aba com a página central do SuperDevMode. Quando precisar ativá-lo, selecione a aba do SuperDevMode, copie o link, volte para a aba da aplicação, cole e o
acione na barra de URL.
Pronto, neste momento você já estará utilizando o SuperDevMode.
8.4. Crawling Tools
O CrawlingTools é uma ferramenta para gerar o conteúdo estático de páginas que utilizem muito processamento AJAX, importante para que estas páginas sejam indexadas e apareçam nos resultados
dos crawlers da web (como o próprio Google). Aplicações AJAX necessitam de um tratamento especial para a geração deste conteúdo. Isto se dá através do conceito de snapshots, que são fotos
estáticas tiradas das páginas quando cada conteúdo dinâmico é acrescentado/removido da página. Para mais informações sobre crawlers consulte o link
https://developers.google.com/webmasters/ajax-crawling/
O CrawlingTools é invocado a partir de uma classe Java org.cruxframework.crux.tools.crawling.CrawlingTool que pode ser executada através de uma tarefa Maven, já definida no POM da
aplicação. Para simplificar o trabalho de invocar esta tarefa, o arquivo POM da aplicação já foi configurado de forma a invocarmos somente a diretiva CrawlingTool.launch. Mas antes de mais nada, é
necessário parametrizar o projeto para a ferramenta de Crawling funcionar corretamente.
Abaixo estão os passos necessários para tornar a aplicação Crawlable:
1. Antes de mais nada, deve-se utilizar um view container que dê suporte a Crawling. Qualquer view container que estender
org.cruxframework.crux.core.client.screen.views.CrawlableViewContainer atenderá aos requisitos necessários.
No view container existe um atributo chamado historyPrefix . Este atributo é um nome que compõe a URL e tem como objetivo permitir a navegabilidade entre as páginas da aplicação. Por
padrão, seu valor é view, mas este pode ser alterado para um nome customizado.
2. O segundo passo consiste em criar o arquivo com a lista de URLs que deverão ter Snapshots criados
Para que o Crux saiba de quais páginas ele deverá criar snapshots, será necessário criar um arquivo contendo os nomes das páginas. O arquivo pode conter qualquer extensão, mas o conteúdo
interno deve seguir o seguinte padrão:
[nome_página.extensão]:[history_prefix=nome_da_view].
Exemplo:
Em uma aplicação chamada de myApp, tem-se uma página chamada de cadastro.html e o historyPrefix do view container foi mantido com o valor default. Neste caso a URL do sistema ficaria
assim:
"http://endereçodoservidor:8080/myApp/cadastro.html!#view=viewCadastro"
obs: Note que, conforme a referência citada no início deste tópico (https://developers.google.com/webmasters/ajax-crawling/), o token "!#" indica que esta aplicação aceita ser indexada por um
crawler.
Com base nesta URL, a string que deverá ir para o arquivo de URLs será:
"cadastro.html:view=viewCadastro"
O arquivo deverá conter uma URL por linha.
3. Após criar o arquivo, é preciso que alterar as seguintes configurações no arquivo POM.xml do projeto:
Tabela 8.4. Parâmetros utilizados no CrawlingTools
Parâmetro
Descrição
Obrigatório
Valores aceitos
outputDir
Indica o diretório para onde os índices serão gerados
true
-
applicationBaseURL | urls
Indica o diretório onde se encontram os .html gerados da aplicação
true
-
javascriptTime
Indica o timeout que será esperado para que a página execute as requisições e carregue completamente
true
-
Parâmetro
Descrição
Obrigatório
Valores aceitos
stopOnErrors
Indica se a geração dos índices deve ser interrompida ou não, caso um erro seja encontrado
false
true | false
4. Após concluir a parametrização acima, o gerador de snapshots já estará pronto para rodar. Execute o arquivo Generate CrawlingSnapshots.launch.
Após a execução do arquivo, verifique os Snapshots gerados na pasta que foi previamente configurada.
5. O último passo é mapear o filtro de Crawling no arquivo web.xml
Abra o arquivo web.xml da aplicação e inclua as seguintes linhas no final do arquivo:
1
2
3
4
5
6
7
8
9
10
11
12
<filter>
<display-name>CrawlingFilter</display-name>
<filter-name>CrawlingFilter</filter-name>
<filter-class>org.cruxframework.crux.core.server.crawling.CrawlingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CrawlingFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
6. Conclusão
Se todos os passos até aqui tiverem sido executados com sucesso, a aplicação agora será Crawlable, ou seja, motores de busca conseguirão localizar e indexar o sistema em pesquisas feitas
na internet.
Capítulo 9. Componentes Crux
Neste capítulo é abordado o mecanismo de criação de componente (também chamado Widget) do Crux, explicando o seu funcionamento e os conceitos envolvidos. Será apresentado o passo-a-passo
para criar um componente, dotá-lo de suporte declarativo e usá-lo no XHTML.
O Crux provê um mecanismo declarativo de construção de telas. Todos os componentes disponibilizados podem ser inseridos na interface gráfica através de TAGs especiais incorporadas no XHTML.
9.1. O que é um Componente?
De modo simplificado, pode-se dizer que um componente é simplesmente um elemento XHTML, que pode conter outros elementos, ao qual é associado um código de controle. Esse código é o
responsável por fazer com que esse elemento possua um comportamento mais elaborado, provendo algum tipo de funcionalidade adicional e permitindo com que um código Java possa manipulá-lo.
Por exemplo, um Button (com.google.gwt.user.client.ui) é simplesmente um elemento <BUTTON>, nativo do HTML. Esse elemento é então associado a um código base do GWT, que permite que ele seja
acessado, via Java, e possa ter sua aparência e comportamentos alterados conforme desejado no sistema. Pode-se associar a ele um tratador de eventos de clique usando o método
Button.addClickHandler(ClickHandler). Assim, quando o evento for executado o código associado realiza a operação desejada.
9.1.1. Criar novos Componentes
Existem duas formas de se criar novas widgets com o Crux.
1. A primeira (xDevice), demonstrada na seção seguinte, é provida pelo Crux e se assemelha ao mecanismo de UIBinder, porém é mais simples e mais poderosa.
2. A segunda forma é a convencional, provida pelo próprio GWT, estendendo-se direta ou indiretamente a classes Composite ou Widget (com.google.gwt.user.client.ui). Esta forma não será
documentada aqui, uma vez que já é documentada pelo GWT.
Para ambas as formas, caso se queira declarar estes componentes em um arquivo XHTML, devemos utilizar o recurso de Fábrica de Widgets.
O Crux não transforma TAGs XHTML em widgets no momento da execução do programa, mas sim quando o programa é compilado. Durante o processo de compilação, o Crux lê as TAGs do XHTML
e, para cada TAG, ele encontra uma classe que sabe como interpreta-la e gerar o código fonte imperativo correspondente. Ou seja, cada TAG está associada a uma classe que sabe transformar o
código:
1
<gwt:button visible="false"/>
em um código assim:
1
Button b = new Button(); b.setVisible(false);
No Crux, são chamadas de Fábricas de Widgets as classes que sabem fazer a tradução das TAGs XHTML. Como pode-se observar, traduzir, nesse caso, significa ler código XML e gerar código Java.
Este processo será descrito adiante.
9.2. Criar widgets xDevice
O Crux possui um mecanismo com o qual pode-se criar widgets de forma simples e rápida. Sucintamente, cria-se um arquivo de extensão .xdevice.xml, são inseridos nesse arquivo as tags HTML e
os componentes desejados e, finalmente, cria-se um Controller que tratará os eventos disparados por esses componentes.
Para ilustrar esse processo, vamos criar uma widget fictícia e bastante simples, que chamaremos de SearchField. Será composta com um campo de texto, destinado a receber uma consulta do usuário
e um botão, que pode ser clicado para executar a busca. O efeito final esperado é algo assim:
Figura 9.1. Componente SearchField
9.2.1. Criando o SearchField
O Crux possui um mecanismo com o qual podemos criar widgets de forma simples e rápida. Isso é feito em 3 passos:
1. Cria-se uma interface que contenha os métodos que sua widget deve possuir.
No componente de exemplo com apenas 1 método específico (getSearchString), que é usado para obter o texto digitado pelo usuário. Existe também um método para adicionar um
ClickHandler, herdado da interface HasClickHandlers. Os demais métodos são herdados da interface DeviceAdaptive e contemplam todo o restante que uma widget pode fazer, tais como
getElement, setVisible, etc. Herdar essa interface é obrigatório.
A anotação @Template é usada para informar, através do atributo name, qual arquivo será usado para definir a estrutura estática do componente, como demonstrado na próxima seção.
Exemplo 9.1. Interface do SearchField
1
2
3
4
5
6
7
@Templates({
@Template(name="searchField", device=Device.all)
})
public interface SearchField extends DeviceAdaptive, HasClickHandlers
{
String getSearchString();
}
2. Cria-se um arquivo de extensão .xdevice.xml, que é responsável por definir a estrutura estática interna do componente. Este arquivo deve obrigatoriamente estar no mesmo pacote da interface
correspondente ao componente.
Exemplo 9.2. Exemplo estrutura interna (searchField.xdevice.xml)
1 <x:xdevice
2 xmlns:x="http://www.cruxframework.org/xdevice"
3 xmlns:gwt="http://www.cruxframework.org/crux/gwt"
4 useController="searchFieldController">
5
6 <gwt:textBox id="searchFieldInputText" style="display:inline; min-width:200px"/>
7
8 <gwt:button id="searchFieldButton" style="display:inline; min-width:70px"
9
text="Buscar" onClick="searchFieldController.buscar"/>
10
11 </x:xdevice>
O componente SearchField é composto simplesmente por um campo texto e um botão. Observe que os eventos de clique do botão são tratados pelo método buscar de um controller chamado
searchFieldController. Esse controller é exibido a seguir.
3. Cria-se um Controller, que será responsável pelo comportamento do componente. Esse controller implementa a interface que definimos no passo 1. Além disso, ele estende a classe
DeviceAdaptiveController. Essa herança é obrigatória.
Exemplo 9.3. Exemplo Controller (SearchFieldController.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Controller("searchFieldController")
public class SearchFieldController extends DeviceAdaptiveController implements SearchField
{
private TextBox inputText;
private ClickHandler handler;
@Override
public String getSearchString()
{
return inputText.getText();
}
@Expose
public void buscar(ClickEvent evt)
{
handler.onClick(evt);
}
@Override
protected void init()
{
inputText = getChildWidget("searchFieldInputText");
}
@Override
public HandlerRegistration addClickHandler(ClickHandler handler)
{
this.handler = handler;
return null;
}
}
9.2.2. Usando o SearchField
Está pronto o componente, mas como usá-lo? Programaticamente, pode-se usar a anotação @Inject para criar uma instância do SearchField e, em seguida, adicioná-lo à tela. Além disso, para
interceptar o evento de clique do botão, basta adicionar um ClickHandler.
Exemplo 9.4. Exemplo Controller (MyController.java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Controller("myController")
public class MyController
{
@Inject
public SearchField searchField;
@Expose
public void onLoad()
{
searchField.addClickHandler(new ClickHandler()
{
@Override
public void onClick(ClickEvent event)
{
String msg = "A consulta digitada pelo usuário foi '";
msg += searchField.getSearchString();
msg += "'";
Window.alert(msg);
}
});
FlowPanel panel = (FlowPanel) Screen.get("meuPainel");
panel.add(searchField);
}
public void setSearchField(SearchField searchField)
{
this.searchField = searchField;
}
}
Figura 9.2. Evento de clique do SearchField
Para usar esse componente declarativamente, ou seja, inserir um <searchField> no XHTML tela, o mecanismo de Fábrica de Widgets deve ser utilizado. Na próxima seção será tratada essa questão.
9.3. Fábrica de Widgets
A engine declarativa do Crux utiliza os Generators para criar as Widgets baseadas em informações contidas nos arquivos xHTML (Views, Templates, Screens, etc.) do Crux.
Para registrar uma Widget customizada, você deve criar utilizar o mecanismo de Fábrica de Widgets.
Veja o exemplo abaixo:
Exemplo 9.5. Registro de uma Widget
1
2
3
4
5
6
7
8
9
10
11
12
@DeclarativeFactory(id="myWidget", library="myLibrary", targetWidget=MyWidget.class)
@TagAttributes({
@TagAttribute("myWidgetAttribute")
})
public class MyWidgetCreator extends WidgetCreator<WidgetCreatorContext>
{
@Override
public WidgetCreatorContext instantiateContext()
{
return new WidgetCreatorContext();
}
}
Note que a classe acima utiliza a anotação @DeclarativeFactory e estende a classe abstrata WidgetCreator. Estas duas condições são necessárias para qualquer fábrica de Widgets do Crux.
Através da notação @DeclarativeFactory, é especificada o nome da biblioteca (atributo library) onde a Widget desejada será registrada. Além disso, também é especificado o próprio nome
associado da Widget (atributo id). A classe ou interface correspondente ao Widget deve ser informado no atributo targetWidget.
Antes de aprofundarmos no código acima e vermos mais exemplos, vamos ver como as Widgets registradas podem ser utilizadas:
Exemplo 9.6. Utilização de Widget registrada
1 <html xmlns="http://www.w3.org/1999/xhtml"
2
xmlns:mylib="http://www.cruxframework.org/crux/myLibrary">
3
<head>
4
<script language="javascript" src="cruxtest/cruxtest.nocache.js"></script>
5
</head>
6
<body>
7
<mylib:myWidget id="myWidgetID" myWidgetAttribute="attributeValue"/>
8
</body>
9 </html>
Note que o namespace chamado http://www.cruxframework.org/crux/myLibrary é utilizado na página acima. Um arquivo XSD define este namespace é criado utilizando o Gerador de Schema.
O exemplo mostrado assume que a Widget MyWidget contém uma propriedade pública String chamada myWidgetAttribute que estará associada à tag myWidgetAttribute no arquivo xHTML.
A classe WidgetCreator é a classe básica para qualquer fábrica de Widgets do Crux. Suas subclasses podem utilizar anotações para descrever atributos, eventos e eventuais filhos que a Widget
contiver. Estas anotações são utilizadas para dois propósitos:
1. Amarrar automaticamente a informação declarada nos xHTMLs com as Widgets criadas pela engine do Crux.
2. Informar à engine do Crux quais atributos esta fábrica pode conter. Esta informação é utilizada para gerar um arquivo XSD que pode ser utilizado para permitir o autocomplete para
desenvolvedores (veja maiores informações em Gerador de Schema).
Esta classe e seu conteúdo serão descritos nas seções seguintes:
9.3.1. Anotações de Fábricas
O Crux provê anotações para auxiliar o processamento dos atributos, eventos e filhos.
Processando atributos
Para processar atributos, podem ser utilizadas as anotações @TagAttributes e @TagAttribute, assim como no próximo exemplo:
Exemplo 9.7. Utilização das anotações @TagAttribute
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@DeclarativeFactory(id="myWidget", library="myLibrary", targetWidget=MyWidget.class)
@TagAttributes({
@TagAttribute("myAttribute"),
@TagAttribute(value="theRequiredAttribute", required=true),
@TagAttribute(value="intAttribute", type=Integer.class),
@TagAttribute(value="otherAttribute", property="widgetProperty"),
@TagAttribute(value="nested", property="element.widgetProperty"),
@TagAttribute(value="style", processor=StyleAttributeProcessor.class)
})
public class MyWidgetCreator extends WidgetCreator<WidgetCreatorContext>{
@Override
public WidgetCreatorContext instantiateContext()
{
return new WidgetCreatorContext();
}
}
O exemplo anterior assume que o Widget para o qual a fábrica está sendo criada possui os setters para os atributos declarados, por exemplo:
Exemplo 9.8. Setters para os atributos declarados na @TagAttributes
1 public class MyWidget extends Widget{
2
public void setMyAttribute(String myAttribute){...}
3
public void setTheRequiredAttribute(String theRequiredAttribute){...}
4
public void setIntAttribute(int intAttribute){...}
5
public void setWidgetProperty(int intAttribute){...}
6
public Element getElement(){...}// Where "Element" has the property "widgetProperty"
7
...
8 }
A anotação @TagAttributes possui uma propriedade value do tipo @TagAttribute. Cada @TagAttribute é utilizado para informar um atributo da Widget.
No exemplo anterior, todas as amarrações entre as declarações nos arquivos xHTMLs e as Widgets são realizadas automaticamente pela engine do Crux.
A anotação @TagAttribute aceita as seguintes propriedades:
Tabela 9.1. Propriedades da @TagAttribute
Nome
Tipo
Obrigatório? Valor padrão
Descrição
value
String
sim
-
O nome do atributo.
defaultValue String
não
""
O valor default a ser utilizado no XSD. Esta propriedade não seta efetivamente o valor padrão no componente, apenas informa no XSD
este valor.
type
Class
não
String
O tipo do atributo.
required
boolean não
false
Informa que o atributo é obrigatório.
supportsI18N boolean não
false
Informa que o atributo suporta mensagens i18n.
property
String
não
O nome do atributo Se informado, altera o código gerado para acrescentar código para popular a propriedade correspondente na Widget (a Widget deve ter o
(value property)
método setter correspondente). O Crux permite propriedades aninhadas, como property1.property2
processor
Class
não
-
Uma subclasse de AttributeProcessor. Utilizada para gerar código para processar alguma informação ao ler um atributo.
xsdIgnore
boolean não
false
Se true, faz com que o SchemaGenerator ignore este atributo no XSD.
Os tipos suportados por um atributo são:
1.
2.
3.
4.
qualquer tipo primitivo ou um encapsulador de um primitivo;
String;
Date;
qualquer tipo enum.
Todos os tipos enums serão mapeados para simpleTypes no XSD gerado.
O código gerado pelo Crux tratará todas as conversões necessárias e os tipos de mensagem i18n.
Quando for especificado um processador para um atributo, podem ser utilizadas as anotações @TagAttributesDeclaration e @TagAttributeDeclaration, assim como no exemplo seguinte:
Exemplo 9.9. Exemplo de registro de uma Widget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@DeclarativeFactory(id="myWidget", library="myLibrary", targetWidget=MyWidget.class)
@TagAttributesDeclaration({
@TagAttributeDeclaration("myAttribute")
})
public class MyWidgetCreator extends WidgetCreator<WidgetCreatorContext>{
@Override
public void processAttributes(SourcePrinter out, WidgetCreatorContext context)
throws CruxGeneratorException
{
super.processAttributes(context);
MyWidget widget = context.getWidget();
String attr = context.readWidgetAttribute("myAttribute");
if (!StringUtils.isEmpty(attr))
{
widget.setMyAttribute(attr);
}
}
}
As anotações @TagAttributesDeclaration e and @TagAttributeDeclaration são similares às anotações @TagAttributes e @TagAttribute.
Entretanto, eles somente declaram atributos (necessários pelo SchemaGenerator), não geram nenhum processamento de forma automática.
Quando utilizarmos a anotação @TagAttributesDeclaration o código para o processamento deve ser escrito também. Veja abaixo.
Outra maneira de especificar como processar um atributo é utilizar um AttributeProcessor, como:
Exemplo 9.10. Exemplo de processamento de atributos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@DeclarativeFactory(id="myWidget", library="myLibrary", targetWidget=MyWidget.class)
@TagAttributes({
@TagAttribute(value="animation",
type=MyWidgetCreator.Animations.class,
processor=MyWidgetCreator.AnimationAttributeProcessor.class)
})
public class MyWidgetCreator extends WidgetCreator<WidgetCreatorContext>{
public static enum Animations{slide, reveal}
public static class AnimationAttributeProcessor extends AttributeProcessor<WidgetCreatorContext>
{
public AnimationAttributeProcessor(WidgetCreator<?> widgetCreator){
super(widgetCreator);
}
@Override
public void processAttribute(SourcePrinter out, WidgetCreatorContext context,
String attributeValue){
Animations animation = Animations.valueOf(attributeValue);
switch (animation){
case slide: out.println(context.getWidget()+".setAnimation("+
SlideAnimation.class.getCanonicalName()+".create());");
break;
case reveal: out.println(context.getWidget()+".setAnimation("+
RevealAnimation.class.getCanonicalName()+".create());");
break;
}
}
}
}
O processador deve estender a classe abstrata org.cruxframework.crux.core.rebind.screen.widget.AttributeProcessor.
Processando Eventos
Para processar eventos, podem ser utilizadas as anotações @TagEvents e @TagEvent, assim como no seguinte exemplo:
Exemplo 9.11. Exemplo de uso das anotações @TagEvents e @TagEvent
1
2
3
4
5
6
7
8
9
10
11
@DeclarativeFactory(id="myWidget", library="myLibrary", targetWidget=MyWidget.class)
@TagEvents({
@TagEvent(ClickEvtBind.class)
})
public class MyWidgetCreator extends WidgetCreator<WidgetCreatorContext>{
@Override
public WidgetCreatorContext instantiateContext()
{
return new WidgetCreatorContext();
}
}
A anotação @TagEvent possui a propriedade valor do tipo Class<? extends EvtProcessor> .
O processador de eventos deve estender a classe abstrata org.cruxframework.crux.core.rebind.screen.widget.EvtProcessor. Veja o exemplo adiante:
Exemplo 9.12. Exemplo de processamento de eventos
1 public class ClickEvtBind extends EvtProcessor{
2
private static final String EVENT_NAME = "onClick";
3
4
public ClickEvtBind(WidgetCreator<?> widgetCreator){
5
super(widgetCreator);
6
}
7
8
public String getEventName(){
9
return EVENT_NAME;
10
}
11
12
public Class<?> getEventClass(){
13
return ClickEvent.class;
14
}
15
16
public Class<?> getEventHandlerClass(){
17
return ClickHandler.class;
18
}
19 }
EvtBinders são classes que automaticamente podem amarrar declarações de eventos a Widgets. O Crux oferece EvtBinders para todos os eventos do GWT e para alguns eventos do próprio Crux. A
tabela seguinte mostra alguns dos EvtBinders do GWT:
Tabela 9.2. EvtBinders
Tipo do Evento
AttachEvtBind
Widget
onAttach
AttachEvtBind
Widget
onBeforeSelection
BeforeSelectionEvtBind
HasBeforeSelectionHandlers
onBlur
BlurEvtBind
HasBlurHandlers
onChange
ChangeEvtBind
HasChangeHandlers
onClick
ClickEvtBind
HasClickHandlers
onFocus
FocusEvtBind
HasFocusHandlers
onHighlight
HighlightEvtBind
HasHighlightHandlers
onKeyDown
KeyDownEvtBind
HasKeyDownHandlers
onKeyPress
KeyPressEvtBind
HasKeyPressHandlers
onKeyUp
KeyUpEvtBind
HasKeyUpHandlers
onError
LoadErrorEvtBind
HasErrorHandlers
onLoad
LoadEvtBind
HasLoadHandlers
onMouseDown
MouseDownEvtBind
HasMouseDownHandlers
onMouseMove
MouseMoveEvtBind
HasMouseMoveHandlers
onMouseOut
MouseOutEvtBind
HasMouseOutHandlers
onMouseOver
MouseOverEvtBind
HasMouseOverHandlers
onMouseUp
MouseUpEvtBind
HasMouseUpHandlers
onMouseWheel
MouseWheelEvtBind
HasMouseWheelHandlers
onOpen
OpenEvtBind
HasOpenHandlers
onScroll
ScrollEvtBind
HasScrollHandlers
onSelection
SelectionEvtBind
HasSelectionHandlers
onShowRange
ShowRangeEvtBind
HasShowRangeHandlers
onChange
ValueChangeEvtBind
HasValueChangeHandlers
A anotação @TagEventDeclaration possui um atributo do tipo String. Este atributo simplesmente informa ao Crux o nome do evento.
Quando utilizada a anotação @TagEventsDeclaration, deve ser escrito também o código para o seu processamento.
Processando Filhos
As Widgets podem ter filhos declarados também. Para informar para a engine do Crux as anotações @TagChildren e @TagChild devem ser utilizadas.
O código seguinte mostra um exemplo completo, declarando uma Widget com filhos e utilizando um contexto customizado:
Exemplo 9.13. Exemplo de Widget com filhos e com contexto customizado
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class MyWidgetContext extends WidgetCreatorContext{
int index = 0;
}
@DeclarativeFactory(id="myWidget", library="myLibrary", targetWidget=MyWidget.class)
@TagChildren({
@TagChild(MyWidgetCreator.ItemProcessor.class)
})
public class MyWidgetCreator extends WidgetCreator<MyWidgetContext>{
@TagConstraints(tagName="item", minOccurs="0", maxOccurs="unbounded")
@TagAttributesDeclaration({
@TagAttributeDeclaration("value"),
@TagAttributeDeclaration(value="label", supportsI18N=true),
@TagAttributeDeclaration(value="selected", type=Boolean.class)
})
public static class ItemProcessor extends WidgetChildProcessor<MyWidgetContext>{
@Override
public void processChildren(SourcePrinter out, MyWidgetContext context)
throws CruxGeneratorException
{
String label = context.readChildProperty("label");
String value = context.readChildProperty("value");
if(label != null && label.length() > 0){
label = getWidgetCreator().getDeclaredMessage(label);
}
if (value == null || value.length() == 0){
value = label;
}else{
value = EscapeUtils.quote(value);
}
out.println(context.getWidget()+".insertItem("+label+", "+value+", "+context.index+");");
String selected = context.readChildProperty("selected");
if (selected != null && selected.length() > 0){
out.println(context.getWidget()+".setItemSelected("+context.index+", "+
Boolean.parseBoolean(selected)+");");
}
context.index += 1;
}
}
@Override
public WidgetCreatorContext instantiateContext() {
return new WidgetCreatorContext();
}
}
Este exemplo cria uma fábrica que consegue traduzir o seguinte código:
Exemplo 9.14. Exemplo de código interpretável pela fábrica recém criada
1 <html xmlns="http://www.w3.org/1999/xhtml"
2
xmlns:mylib="http://www.cruxframework.org/crux/myLibrary" >
3
<head>
4
<script language="javascript" src="cruxtest/cruxtest.nocache.js"></script>
5
</head>
6
<body>
7
<mylib:myWidget id="myWidgetID" >
8
<myLib:item label="#{myMessageBundle.label1}" value="value1" />
9
<myLib:item label="#{myMessageBundle.label2}" value="value2" />
10
<myLib:item label="#{myMessageBundle.label3}" value="value3" selected="true"/>
11
<myLib:item label="#{myMessageBundle.label4}" value="value4" />
12
<myLib:item label="#{myMessageBundle.label5}" value="value5" />
13
</mylib:myWidget>
14
</body>
15 </html>
Observe que podemos inserir atributos e eventos para as tags filhas, para isso devem ser utilizadas as anotações @TagAttributesDeclaration e @TagEventsDeclaration. Ainda, podem ser inseridos
outros filhos reutilizando a anotação @TagChildren e a classe de processamento correspondente.
Uma classe de processamento filha deve estender a classe WidgetChildProcessor, ou uma de suas subclasses.
9.3.2. Métodos de Fábricas
O método WidgetCreator.createWidget(...) é chamado para a efetiva criação da Widget. Ele processa os atributos, eventos e filhos e executa os seguintes passos:
1.
2.
3.
4.
5.
Chama o método void
Chama o método void
Chama o método void
Chama o método void
Chama o método void
instantiateWidget(SourcePrinter out, C context)
processAttributes(SourcePrinter out, C context)
processEvents(SourcePrinter out, C context)
processChildren(SourcePrinter out, C context)
postProcess(SourcePrinter out, C context)
Estes métodos podem ser utilizados para especificar e processar informações sobre os atributos, eventos e filhos da Widget. Todos esses métodos recebem um parâmetro do tipo C extends
WidgetCreatorContext.
A classe básica WidgetCreatorContext possui os seguintes métodos públicos:
Tabela 9.3. Métodos da WidgetCreatorContext
Método
Descrição
getWidget
Retorna o nome da variável que contém a Widget a ser criada
getWidgetElement
Retorna o elemento metadado da Widget (um objeto JSONObject)
getWidgetId
Retorna o identificador da Widget
readWidgetProperty(String propertyName)
Lê a valor da propriedade propertyName do metadado da Widget
readChildProperty(String key)
Lê a propriedade key do metadado da Widget filha corrente sendo processada
O WidgetCreatorContext pode ser estendido para adicionar propriedades e métodos customizáveis no contexto. Os métodos da classe WidgetCreator podem ser sobrescritos para adicionar eventuais
comportamentos específicos.
Método instantiateWidget
O método instantiateWidget pode ser sobrescrito para eventuais customizações na instanciação de objetos. Revisitando a Widget declarada no início da seção Fábrica de Widgets, se precisarmos
adicionar atributos para serem instanciados, podemos escrever algo assim:
Exemplo 9.15. Exemplo de atributos para instanciação de uma Widget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@DeclarativeFactory(id="myWidget", library="myLibrary", targetWidget=MyWidget.class)
@TagAttributesDeclaration({
@TagAttributeDeclaration(value="theRequiredAttribute", required=true)
})
public class MyWidgetCreator extends WidgetCreator<WidgetCreatorContext>{
@Override
public void instantiateWidget(SourcePrinter out, WidgetCreatorContext context)
throws CruxGeneratorException{
String className = getWidgetClassName();
String theRequiredAttribute = context.readWidgetProperty("theRequiredAttribute");
out.println("final "+className + " " + context.getWidget()+" = new "+
className+"("+EscapeUtils.quote(theRequiredAttribute)+");");
}
...
}
Este método imprime a instanciação da Widget. A variável representando o nome da Widget é passada para a instantiateWidget no método context.getWidget(). É importante observar que a
Widget deve ser criada e atribuída a uma variável final, como mostrado nos exemplos anteriores.
O propósito deste método é somente instanciar a Widget. O melhor lugar para tratar a extração dos atributos, eventos e filhos não é aqui. Outros métodos devem ser utilizados (como mostrado abaixo)
para tirar vantagem de todos os benefícios que a engine oferece.
Processamento de eventos e atributos
Os métodos processAttributes e processEvents são utilizados para processar atributos e eventos que não podem ser tratados de forma automática pelo Crux, mas sim através de anotações nas
fábricas.
Quando estes métodos são sobrescritos, dependendo de qual método for escolhido, um dos métodos deve ser chamado: super.processAttributes() ou super.processEvents().
Processamento de filhos
O método processChildren é utilizado para adicionar um comportamento customizado para o processamento dos filhos. Quando este método é sobrescrito, não há a necessidade de chamar o método
super.processChildren (como é necessário para atributos e eventos). A lógica de processamento dos filhos não é herdada de super classes (mas atributos e eventos são).
Pós Processamento
O método de pós processamento pode ser sobrescrito para adicionar lógica customizada após o processamento dos atributos, eventos e filhos.
9.3.3. Herança de Fábricas
As anotações para processamento de atributos e eventos presentes nas superclasses e interfaces das fábricas de Widgets também são consideradas pela engine do Crux.
Observe o seguinte exemplo:
Exemplo 9.16. Exemplo de herança nas fábricas de Widgets (1)
1
2
3
4
5
6
@TagAttributes({
@TagAttribute("name")
})
public interface HasNameFactory<C extends WidgetCreatorContext>
{
}
Exemplo 9.17. Exemplo de herança nas fábricas de Widgets (2)
1
2
3
4
5
6
7
8
9
@DeclarativeFactory(id="myWidget", library="myLibrary", targetWidget=MyWidget.class)
@TagAttributes({
@TagAttribute("myWidgetAttribute")
})
public class MyWidgetCreator extends WidgetCreator<WidgetCreatorContext>
implements HasNameFactory<WidgetCreatorContext>
{
...
}
A fábrica mostrada anteriormente aceitará e automaticamente amarrará os atributos "myWidgetAttribute" e "name" na MyWidget.
As bibliotecas do Crux utilizam o suporte a herança para facilitar o desenvolvimento das fábricas de Widgets. As bibliotecas GWTWidgets têm interfaces que amarram atributos e eventos para a
maioria das interfaces das Widgets. Simplesmente implementando algumas dessas interfaces é suficiente para oferecer à fábrica sendo construída a habilidade de amarrar vários atributos e eventos.
Algumas das interfaces providas são:
Tabela 9.4. Métodos da interfaces
Interface
Attributes
Events
HasAllFocusHandlersFactory
-
onFocus, onBlur
HasAllKeyHandlersFactory
-
onKeyUp, onKeyPress, onKeyDown
HasAllMouseHandlersFactory
-
onMouseUp, onMouseDown, onMouseOver, onMouseOut, onMouseMove, onMouseWheel
HasAnimationFactory
animationEnabled
-
HasBeforeSelectionHandlersFactory
-
onBeforeSelection
HasChangeHandlersFactory
-
onChange
HasClickHandlersFactory
-
onClick
HasCloseHandlersFactory
-
onClose
HasDirectionFactory
direction
-
HasHighlightHandlersFactory
-
onHighlight
HasNameFactory
name
-
HasOpenHandlersFactory
-
onOpen
HasScrollHandlersFactory
-
onScroll
HasSelectionHandlersFactory
-
onSelection
HasShowRangeHandlersFactory
-
onShowRange
HasTextFactory
text
-
HasValueChangeHandlersFactory
-
onChange
HasWordWrapFactory
wordWrap
-
Capítulo 10. Restrições
Neste capítulo são tratadas as restrições (limitações) do Crux Framework e de seus componentes.
10.1. Reflexão e Generics no Crux
Reflexão e Generics no Java, muitas vezes, são conceitos problemáticos. Basicamente, o problema é que parte da informação provida pelo mecanismo de generics do Java existe em tempo de
compilação, mas não está disponível em runtime.
O impacto prático disso para o Crux é que nem sempre é possível recuperar a informação de tipo corretamente a partir de uma classe genérica.
Ao criarmos serviços REST no Crux usando generics, precisamos ter em mente que existe a situação de Generics Suportado, e a situação de Generics Não Suportado. Não conseguiríamos
recuperar a informação de tipo genérica de um método na classe Foo.
Exemplo 10.1. Generics Suportado
1 Class Bar extends Foo<String>
Exemplo 10.2. Generics Não Suportado
1 Class Bar<T> extends Foo<T>
2
3 Class A extends Bar<String>
Vale ressaltar que isso não é uma restrição apenas do Crux, mas da linguagem java. Ela não oferece forma de fazer essa leitura. Apenas para efeito ilustrativo, o GWT implementou uma biblioteca
particular de reflexão para usar em seus generators, que analisa os arquivos .java e não os .class (por isso é obrigatório enviar o source em uma aplicação GWT), para poder contornar esse problema.
A partir apenas do .class é impossível obter informações completas de tipos.
Glossary
GWT
O Google Web Toolkit é um framework livre criado pela Google em maio de 2006. Ele está licenciado sob a Licença Apache versão 2.0.2. Documentação do GWT
IoC
Inversion of Control (IoC) é um padrão de desenvolvimento onde o controle das chamadas não é determinada pelo programador, diferentemente do modelo de programação tradicional.
Archetypes
Os archetypes são estruturas de projetos pré-configuradas, que aceitam customizações via parâmetros e têm a responsabilidade de montar um projeto completo, pronto para o uso.
Apêndice A. Biblioteca de Componentes
Documentação das bibliotecas de componentes do Crux:
Crux Widgets.
Crux Smart Faces.
Apêndice B. Javadoc
O JavaDoc do Crux Framework está disponínel para consulta web em:
Javadoc Crux Dev.
Javadoc Crux Runtime.
Javadoc Crux Smart-Faces.
Javadoc Crux Widgets.
Apêndice C. Envio de Contribuição
Para os usuários que quiserem enviar contribuições para o Crux seguir as seguintes regras:
O código gerado deve seguir o padrão: Code Conventions for the Java Programming Language
A contribuição deve ser enviada como um patch do SVN no google code. Para gerar um patch usar uma das três opções a seguir.
Pela linha de comando
Usando o Eclipse
Com Tortoise no Windows

Documentos relacionados