Desenvolvimento Java para Web Osvaldo Pinali Doederlein Visionnaire Introdução à Plataforma J2EE 1.4 Agenda Containers Padrões JCP Servidores de Aplicação J2EE e Containers http://java.sun.com/j2ee/ Container: Conceito fundamental do J2EE Aplicação executa num “casulo” que assume ou facilita diversas responsabilidades Administração / Ciclo de vida Conectividade (SGBD, Messaging, HTTP, SOAP etc.) Funcionalidades: Persistência, Segurança... APIs O Container também implementa APIs J2EE APIs J2SE: stand-alone (bibliotecas simples) APIs J2EE: maior dependência de serviços XML-RPC: exige server HTTP JMS: exige middleware de mensagens Connector: exige bridges com outros sistemas EJB: exige servidor RMI/IIOP APIs do J2EE 1.4: Diversos JavaMail Envio e recepção de e-mail por POP, IMAP, SMTP JavaBeans Activation Framework Ativação de funcionalidades via tipos MIME JNDI (Java Naming and Directory) LDAP, NDS, DNS, NIS, CosNaming; serviços J2EE JAAS (Autenthication & Authorization Service) Autorização e Autenticação; PAM APIs do J2EE 1.4: Middleware JMS (Java Message Service) Comunicação assíncrona robusta JTA (Java Transaction API) Transações de alto nível JCA (J2EE Connector Architecture) Integração com sistemas não-J2EE JDBC (Java Database Connectivity) J2SE + DataSources, transações XA APIs do J2EE 1.4: Web Servlets Programação Web dinâmica JSP (Java Sever Pages) Idem, mais visual JSTL (Java Standard Template Library) Idem, mais estruturado Alternativas/Complementos: Struts, Spring... Futuro (J2SE 5.0): JSF (JavaServerFaces) APIs do J2EE 1.4: XML e Web Services JAXP (Java API for XML Processing) DOM, SAX, XSLT SAAJ (SOAP with Attachments API for Java) Web Services: SOAP (modelo documento) JAX-RPC (Java API for XML-based RPC) Web Services: SOAP (modelo RPC), WSDL JAXR (Java API for XML Registries) Web Services: UDDI / ebXML APIs do J2EE 1.4: EJB EJB (Enterprise Java Beans) Componentes Distribuídos; Session; RMI/IIOP EJB BMP/CMP/CMR Entity; Persistência Automática (mapeamento O/R) EJB MDB (Message-Driven Beans) Facilidade de alto nível para JMS e Web Services Transações, Segurança Facilidades declarativas Padrões do JCP J2EE 1.4 (JSR-151 + 15 JSRs, jcp.org) Padroniza quase tudo APIs Schemas, Deployment Comportamentos (ciclos de vida, etc.) Não cobre: Ferramentas, Metodologias QoS Integração (ex.: suporte a SGBDs e middlewares) http://java.sun.com/reference/blueprints/ Tomcat 5.5 Apache + Sun; RI de JSRs de Servlet, JSP, etc. Container Web do JBoss, Sun AppServer, … Conectores para Apache e IIS http://jakarta.apache.org/tomcat/ JBoss 4.0 JBoss Group Open Source & Comercial Arquitetura: Microkernel (JMX), AOP http://www.jboss.com/products/jbossas/ Design Patterns para J2EE Agenda Design Patterns Singleton Façade Factory DAO MVC Design Patterns Design Patterns (GoF, 1995) Pattern = design reutilizável; problema+solução Modelos; ex.: Intenção, Motivação, Aplicabilidade, Estrutura, Implementação, Implicações, Categoria... Instanciável, mas não reusável, como código Linguagem de Patterns: Coleção de patterns relacionados, com modelo comum http://java.sun.com/blueprints/patterns/ Singleton: Motivação Garantir que uma classe tenha instância única Sem abrir mão do suporte à herança (como static) Implementação robusta de entidades que precisam ser únicas; ex: configuração Alternativa OO às variáveis globais Facilita a inicialização “lazy” Singleton: Estrutura Singleton -singletonInstance ... «constructor» -Singleton( ) «misc» +getInstance( ) ... class Configuracao { private static Configuracao singleton; private final Properties props; private Configuracao (Properties props) { this.props = props; } public static Configuracao getInstance () { if (singleton == null) { properties props = // ... Lê properties de um arquivo singleton = new Configuracao(props); } return singleton; } public getProperties () { return props; } } Properties props = Configuracao.getInstance().getProperties(); Façade: Motivação Interface única e simples para um sistema Simplifica API, reduz curva de aprendizado Reduz dependências e acoplamento Se o modelo interno do sistema evoluir, basta mudar a implementação da Façade; clientes são preservados Conduz a modelos em camadas (layers) Ex: Cliente (JSP) Façade (Session Bean) Negócio Façade: Estrutura Cliente 1 Usa * Façade Façade (Session / J2EE) public class SistemaPagamentos { public void alteraSalario (String matricula, double salario) { Funcionario func = CadastroFuncs.getInstance().find(matricula); if (func != null && func.isAtivo()) func.setSalario(salario); } // ... Outros métodos da Façade } // Cliente não precisa conhecer Funcionario, CadastroFuncs, etc. SistemaPagamentos sp = // ... sp.alteraSalario("93764622", 4350.00); Factory: Motivação Cria objetos sem saber suas classes concretas Encapsula decisões sobre a criação de objeto Melhoram OO (construtores não são polimórficos!) Variante: Factory Method Útil com herança, delega criação à subclasse Evita instanceof para criar objetos relacionados Variante: Abstract Factory Suporta um conjunto de entidades relacionadas Suporta implementações "plugáveis", ex.: JDBC, XML Factory Method: Estrutura public interface Cliente { public Conta abreConta (); } public class ClienteSimples implements Cliente { public Conta abreConta () { return new ContaComum(this); } public class ClientePremier implements Cliente { public Conta abreConta () { return new ContaEspecial(this); } Cliente cli = // ... obtém da database ou outra fonte Conta ct = cli.abreConta(); // cria conta do tipo correto Abstract Factory: Estrutura // Utiliza fábrica abstrata de engine XML (Xerces, Crimson, etc.) SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setValidating(true); spf.setNamespaceAware(true); // Utiliza fábrica abstrata de parser (com ou sem validação, etc.) SAXParser parser = spf.newSAXParser(); XMLReader reader = parser.getXMLReader(); reader.setContentHandler(this); reader.parse(config); DAO: Motivação Desacoplar o acesso a dados dos clientes Ocultar a complexidade do acesso a dados Permitir alternância fácil e dinâmica da implementação de persistência Não é necessário com ferramentas ORM (como Hibernate), mas facilita migração JDBCORM DAO: Estrutura public class Venda { /* nenhum acesso a dados */ } public interface VendaDao { public Venda find (String id) throws SQLException; } public class VendaDaoJDBC implements VendaDao { private DataSource ds; public Venda find (String id) throws SQLException { try { Connection c = ds.getConnection(); Statement stmt = c.createStatement(); ResultSet rs = stmt.executeQuery("SELECT…"); if (rs.next()) return new Venda(id, rs.getString("produto"), …); } finally { if (c != null) try { c.close(); } catch (SQLException e){} } return null; // Objeto não encontrado } MVC-2: Motivação Desacoplar responsabilidades de uma GUI: Model: Camada de Implementação das Regras de Negócio View: Layout dos dados (ex.: telas HTML) Controller: Comportamento da GUI (ex.: consultas, atualizações, tratamento de erros, defaults) Permite variar/evoluir cada um separadamente View editável por não-programadores Back-end pode variar sem afetar GUI MVC-2: Estrutura Requisição Controller Envio de Dados Seleção do Visualizador Resposta View Resposta do Envio de dados Model <html:form action="/LoginSubmit.do"> <table> <tr><td>Login:</td> <td><html:text property="login" size="20"/></td> </tr> <tr><td>Senha:</td> <td><html:password property="password" size="20"/></td> </tr> <tr><td><html:submit>OK</html:submit></td></tr> </table> </html:form> <html:messages id="error" property="invalid"> <script>alert('<bean:write name="error" />');</script> </html:messages> <html:javascript formName="LoginForm"/> View public class LoginAction extends DispatchAction { Controller public ActionForward login (ActionMapping map, ActionForm form, HttpServletRequest req, HttpServletResponse resp) throws Exception { audit(request, PortalAudit.ACCESS); if (isFirstLogin(request)) return mapping.findForward("firstLogin"); else return mapping.findForward("loginOk"); } private boolean isFirstLogin (HttpServletRequest req) {...} // ... Outras ações ... } Logging Agenda Log4J java.util.logging Log4J Biblioteca do projeto Apache Jakarta Substitui System.out.println()... Níveis de log; ativação dinâmica sem mudar código Opções de output (console, arquivo, rede, DB etc.) Formatação, Extensibilidade Logs detalhados, de qualidade; fácil e eficiente Para diagnóstico de problemas (debug ou produção) Para auditoria, etc. http://logging.apache.org/log4j/ Log4J: Exemplo Logger logger = Logger.getLogger("SIST"); logger.debug("x=“ + x); logger.log(Level.DEBUG, "x=“ + x); if (logger.isDebugEnabled()) // Se não estiver habilitado... logger.debug(a+b+c+d+e+f); // ...evita custo de gerar dados Log4J: log4j.properties log4j.logger.SIST = INFO, APP_SIST log4j.appender.APP_SIST = org.apache.log4j.RollingFileAppender log4j.appender.APP_SIST.File = ../logs/SIST.log log4j.appender.APP_SIST.MaxFileSize = 10MB log4j.appender.APP_SIST.MaxBackupIndex = 10 log4j.appender.APP_SIST.layout = org.apache.log4j.PatternLayout log4j.appender.APP_SIST.layout.ConversionPattern = [%p] %d{HH:mm:ss} - %m%n Log4J: log4j.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> <logger name=“SIST"> <level value="INFO"/> </logger> <appender name="APP_SIST" class="org.apache.log4j.RollingFileAppender"> <param name="File" value="../logs/SIST.log"/> <param name="MaxFileSize" value="10MB"/> <param name="MaxBackupIndex" value="10"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%p] %d{HH:mm:ss} - %m%n"/> </layout> </appender> </log4j:configuration> java.util.logging API padrão do J2SE 1.4+ (JSR-47) Semelhante à Log4J, mas não igual/compatível Menos poderosa; Mais leve, simples, eficiente Log4J ou java.util.logging? Log4J não é o padrão, mas ainda é mais popular java.util.logging melhor para componentes reusáveis (não impõe dependência da Log4J para projetos) A Jakarta Commons Logging só piora as coisas http://java.sun.com/j2se/1.5.0/docs/guide/logging/ java.util.logging: Exemplo Logger logger = Logger.getLogger("SIST"); logger.fine("x=“ + x); logger.log(Level.FINE, "x=“ + x); if (logger.isLoggable(Level.FINE)) // Se não estiver habilitado... logger.fine(a+b+c+d+e+f); // ...evita custo de gerar dados java.util.logging: logging.properties handlers= java.util.logging.FileHandler SIST.level = INFO java.util.logging.FileHandler.pattern = ../logs/SIST%u.log java.util.logging.FileHandler.limit = 10000000 java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter JDBC Agenda JDBC API J2EE/DataSources A API JDBC Baseada na ODBC (Microsoft; Open Group CLI) Microsoft: novas APIs (OleDB, ADO, RDO, RDS...) JDBC: evolução compatível + frameworks O/R Características Baixo nível: conexões, statements, cursores, SQL Leve e eficiente Infra-estrutura para todas as outras soluções http://java.sun.com/products/jdbc/ JDBC: Connection Representa sessão com o SGBD, e transação Alto custo de criação. Usar pools! Default: auto-commit, não usar!! Connection conn = null; try { conn = DriverManager.getConnection(url, user, password); // Utiliza a conexão... } catch (SQLException e) { if (conn != null) try { conn.close(); } catch (SQLException e) {} } JDBC: Statement e ResultSet Statement: Query, Update, ou DDL Prefira PreparedStatement sempre! ResultSet: Cursor (resultado de Statement) Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT id,nome FROM FUNC"); while (rs.next()) { System.out.println(rs.getInt(1)); System.out.println(rs.getString("nome")); } stmt.close(); JDBC: PreparedStatement Estende Statement, mais poderoso e eficiente Binding (?), reduz recompilação e formatação de SQL Cache no cliente (driver JDBC) Também é mais seguro (evita "injeção de SQL") PreparedStatement stmt = conn.prepareStatement( "SELECT id,nome FROM FUNC WHERE salario >= ?"); stmt.setDouble(1, 3000); ResultSet rs = stmt.executeQuery(); ... stmt.close(); JDBC: CallableStatement Invocação de stored procedures / functions “Anti-OO”, mas reduz tráfego com SGBD CallableStatement stmt = conn.prepareCall( “{call CALCULA_SALARIO (?,?)}"); stmt.setInt(1, idFuncionario); stmt.registerOutParameter(2, Types.DOUBLE); stmt.executeUpdate(); double salario = stmt.getDouble(2); stmt.close(); Gerenciamento de Recursos Invocação dos métodos close() Connection.close(): fecha todos Statements e blobs Statement.close(): fecha ResultSet corrente Statemet.execute*(): também fecha ResultSet anterior ResultSet.close(): não fecha seus blobs! Regra: fechar só o objeto “raiz local” do método Ex.: Se um método recebe a Connection como parâmetro, basta fechar os Statement criados Evite dividir responsabilidades (um método cria, outro faz o close()) bugs, bugs, bugs!! Opções: Connection TRANSACTION_READ_UNCOMMITTED Permite non-repeatable/phantom reads TRANSACTION_REPEATABLE_READ Permite phantom reads TRANSACTION_SERIALIZABLE Isolamento ACID total DESEMPENHO TRANSACTION_READ_COMMITTED CONFIABILIDADE Permite dirty/non-repeatable/phantom reads Opções: ResultSet FETCH_FORWARD/REVERSE/UNKNOWN Direção da leitura (hints para o driver) TYPE_FORWARD_ONLY/ SCROLL_SENSITIVE/SCROLL_SENSITIVE Leitura randômica ou aleatória DESEMPENHO Somente leitura, ou cursor atualizável PRODUTIVIDADE CONCUR_READ_ONLY/UPDATABLE DataSource Abstração sobre obtenção de conexões J2EE, mas também pode ser usada sem container Suporte a JNDI, pools, transações distribuídas Configuração no container Context ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup("jdbc/Sistema"); ... Connection conn = ds.getConnection(); Testes Unitários Agenda Testes Unitários JUnit Testes Unitários TDD (Test-Driven Development), XP 1 funcionalidade = 1 teste Funcionalidade: método, classe, caso de uso, cenário Escrever teste antes do código a testar Objetivos: melhorar qualidade e produtividade Planejar componentes, do ponto de vista do cliente Proteção contra bugs de regressão Otimizações, refactoring... "fearless programming" http://www.extremeprogramming.org/rules/unittests.html JUnit Framework para testes unitários em Java Kent Beck (XP) + Erich Gamma (Patterns, Eclipse) Simples Extensível Exemplos públicos para examinar... J2SE/TCK (45.000 testes) Eclipse (feito com o JUnit) http://www.junit.org/ public class TestaRaizes extends TestCase { public void testOk () { double[] x = Raizes.calcula(3, 4, 1); // 3x2+4x+1=0 assertTrue(x[0] == -3 && x[1] == -9); System.out.println(x[0] + "," + x[1]); } public void testDeltaNegativo () { try { double[] x = Raizes.calcula(3, 0, 1); // 3x2=-1 fail(); } catch (IllegalArgumentException e) {} } } public class Raizes { public static double[] calcula (double a, double b, double c) { double delta = Math.sqrt(b*b-4*a*c); if (Double.isNaN(delta)) throw new IllegalArgumentException("Delta negativo"); double x1 = (-b+delta)/2*a; double x2 = (-b-delta)/2*a; return new double[]{x1,x2}; } } Servlets Agenda Servlet Inicialização Filtros Redirecionamento Sessões Cookies Servlet Servlet = Classe Java que responde a HTTP Exige um conector HTTP (webserver) Servlet recebe um request Conector cuida de headers, TCP/IP, etc. Base para todo stack Java Web JSP, JSTL, JSF, Struts, Velocity, Spring, etc. http://java.sun.com/products/servlet/ public class AloMundo extends HttpServlet { public void doGet (HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { resp.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head>"); out.println("<title>Alô Mundo</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Alô, Mundo!</h1>"); out.println("</body>"); out.println("</html>"); } } Web.xml: <servlet> <servlet-name>AloMundo</servlet-name> <servlet-class>AloMundo</servlet-class> </servlet> <servlet-mapping> <servlet-name>AloMundo</servlet-name> <url-pattern>/alo</url-pattern> </servlet-mapping> </web-app> Inicialização e Shutdown Servlet.init(), Servlet.destroy() init() na primeira invocação, ou load-on-startup=true ServletContextListener Mecanismo apropriado para ciclo de vida da aplicação como um todo public class MeuListener implements ServletContextListener{ public void contextInitialized (ServletContextEvent sce) { System.out.println("inicializando..."); } public void contextDestroyed (ServletContextEvent sce) { System.out.println("terminando..."); } } Web.xml: <listener> <listener-class>TesteListener</listener-class> </listener> Filtros Interceptação de requests Verificação de desempenho Logging automatizado Controle de segurança, transações, etc. Transformações, ex.: via XSLT Implementação de frameworks dinâmicos, ex.: Struts Implementação do próprio container public class PSSFilter implements Filter { // Omitidos: init(), destroy() public void doFilter (ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { try { chain.doFilter(req, resp); } finally { Catalog.closeConnection(); } } } Web.xml: <filter> <filter-name>PSSFilter</filter-name> <filter-class>com.visionnaire.PSS.servlet.PSSFilter</filter-class> </filter> <filter-mapping> <filter-name>PSSFilter</filter-name><url-pattern>*.jsp</url-pattern> </filter-mapping> Forward e Include Forward: Desvio para outra URL request.getRequestDispatcher("/Header.jsp") .forward(request, response); Include: Cópia do output de uma URL request.getRequestDispatcher("/OK.jsp") .include(request, response); Contexto, Sessões e Cookies ServletContext: Representa a web-app MIME, dispatchers, resources, servlets, atributos, log HttpSession: Permite armazenar informações sobre um cliente, através de várias páginas ID, atributos, criação, último acesso, timeout Cookies ou URL rewriting Cookie: Pacote de dados armazenável no browser Utiliza headers do HTTP Pode ser restrito pelo cliente (número, tamanho) JSP Agenda JSP Taglibs Expression Language JSTL JSP Servlet: Java com HTML, JSP: HTML com Java Semelhante a ASP, PHP, etc. JSP gera uma Servlet Compilado pelo container, em demanda ou deploy Acesso a toda a API de Servlets Diretrizes de página Scriptlets Ações JSP: Sintaxe Comentário Expressão Scriptlet Declaração Diretiva include Diretiva page <%-- comentário --> <%= expressão %> <% statement %> <%! declaração %> <%@include file="url-absoluta"> <%@page atrib="valor" %> language="java", import="pkg" errorPage="url", extends="classe" info="msg", contentType="MIME" buffer="kb|none", isErrorPage, session, autoflush, isThreadSafe JSP: Ações Ação include Ação useBean Ação getProperty Ação forward Ação plugin <jsp:include page="url"> <jsp:useBean atrib="valor"/> id="nome" scope="page|request| session|application" type="class" class="class" beanName="class" <jsp:getProperty name="name" value="valor"/> <jsp:forward page="url"/> <jsp:plugin atrib="valor"/> JSP: Variáveis predefinidas As mesmas que estariam disponíveis na Servlet HttpServletRequest request HttpServletResponse response HttpSession session PrintWriter out <h2>Seu host é: <%= request.getRemoteHost() %></h2> <%@page language="java"%> <%@page import="java.util.Date"/> <jsp:include page="Titulo.html"/> <jsp:include page="Navegacao.jsp"/> <html><head/> <body> <p> Alo, mundo! Agora são <%= new Date() %> </p> </body> </html> Taglibs Declaração e Uso (JSP) <%@taglib prefix="opd" uri="/tags/osvaldo" %> <tt:alo name="Osvaldo"> ... </tt:alo> Configuração (web.xml) <jsp-config> <taglib> <taglib-uri>/tags/osvaldo</taglib-uri> <taglib-location>/WEB-INF/tags/osvaldo.tld</taglib-location> </taglib> </jsp-config> <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd"> <taglib> <tlibversion>1.0.0</tlibversion> <jspversion>1.1</jspversion> <shortname>Taglib Demo</shortname> <tag> <name>alo</name> <tagclass>tags.AloTag</tagclass> <bodycontent>empty</bodycontent> <attribute> <name>nome</name> <required>true</required> <rtexprvalue>true</rtexprvalue> <type>java.lang.String</type> </attribute> </tag> </taglib> public class AloTag extends BodyTagSupport { private String nome; public void setNome (String nome) { this.nome = nome; } public int doEndTag () throws JspException { try { pageContext.getOut().write("Alo, " + nome); } catch (IOException e) {} return SKIP_BODY; } } EL (Expression Language) Parâmetros de tags; reduz uso de scriptlets Sintaxe: ${var} (= var) ${var.x.y} (= var.getX().getY()) Operadores extra; números e strings div, mod, and, or, not, eq, ne, lt, gt, le, ge, empty Ex.: ${(10 mod 4 == 5) and empty x} ${(10 % 4 == 5) && x == null} EL (Expression Language) Variáveis predefinidas pageContext (servletContext, session, request, response) param, paramValues header, headerValues cookie, initParam pageScope, requestScope, sessionScope, applicationScope Sintaxe [ ] para acesso a variáveis Map (get()) ${header["host"]} ${pageScope.departamentos[nomeDepto]} JSTL (JSP Standard TagLib) JSR-52: Biblioteca padrão de tags para JSP Core: Iteração, Condições, Output… FMT: Formatação, Internacionalização SQL: Acesso a dados via JDBC XML: Parsing, Xpath, XSLT Functions: Manipulação de strings <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>Demo de JSTL</title></head> <body> <sql:transaction dataSource=“jdbc/MinhaDatabase"> <sql:query var=“produtos"> SELECT * FROM PRODUTO </sql:query> </sql:transaction> <h2>Componentes de Hardware</h2> <table border="1"> <c:forEach var="row" items="${produtos.rowsByIndex}"> <tr> <c:forEach var="col" items="${row}"> <td><c:out value="${col}"/></td> </c:forEach> </tr> </c:forEach> </table> </body></html> JSTL: Core <c:out value="texto“/> <c:set var="nome" value="valor" scope="page|request|session|application"/> <c:remove var="nome" scope="page|request|session|application"/> <c:catch var="nome"> ... </c:catch> <c:if test="condição" var,scope> ... </c:if> <c:choose> <c:when test="condição">...</c:when> <c:otherwise>...</c:otherwise> </c:choose> JSTL: Core <c:forEach var="nome" items="coleção" varStatus="status" begin="n" end="n" step="n"> <c:forTokens var="nome" items="string" delims="sep" varStatus="status" begin="n" end="n" step="n"> <c:import url="url" charEncoding="encoding" context="contexto" varReader="nome" var,scope> <c:param name="nome" value="valor"/> <c:url value="url" context="contexto" var,scope/> <c:redirect url="url" context="contexto" > <c:param name="nome" value="valor"/> JSTL: Formatação <fmt:setTimeZone value="timeZone"/> <fmt:timeZone var,scope> corpo </fmt:timeZone> <fmt:formatNumber value="valor" currencyCode="code" currencySymbol="symbol" type="number|currency|percent" var,scope groupingUsed="bool" pattern="pattern" maxIntegerDigits="n" minIntegerDigits="n" maxFractionDigits="n" minFractionDigits="n"> <fmt:parseNumber value="valor" parseLocale="locale" type="number|currency|percent" integerOnly="bool" var,scope/> JSTL: Formatação <fmt:formatDate value="data" type="{time|date|both}" dateStyle="{default|short|medium|long|full}" timeStyle="{default|short|medium|long|full}" pattern="pattern" timeZone="zona" var,scope/> <fmt:parseDate value="data" type="{time|date|both}" dateStyle="{default|short|medium|long|full}" timeStyle="{default|short|medium|long|full}" var,scope pattern="pattern" timeZone="zona" parseLocale="locale"/> JSTL: SQL <sql:query sql="query" var="var" dataSource="nome" maxRows="max" startRow="start" var,scope> <sql:param value="valor"/> </sql:query> <sql:update sql="update" dataSource="nome" var,scope> params </sql:update> <sql:dateParam value="valor" type="timestamp|time|date"/> JSTL: SQL <sql:transaction dataSource="nome" isolation="read_committed|read_uncommitted|repeatable_ read|serializable"> ... </sql:tx> <sql:setDataSource dataSource="nome" url="url" driver="classe" user="nome" password="pwd" var,scope/> Mais taglibs... JSTL XML (<x:...>) Internacionalização (<fmt:...>) Funções EL definidas pelo usuário JSR-128: JESI, JSP TagLib for Edge Side Includes JSR-267: JSP TagLib for Web Services Deployment Agenda Pacotes jar, war, ear Deploy em servidores J2EE Pacotes jar, war, ear JAR: J2SE / Classes, Resources /META-INF/ Manifest.MF WAR: J2EE / Páginas (e subdiretórios) /WEB-INF Configurações, TLDs /WEB-INF/classes Classes /WEB-INF/lib JARs /WEB-INF/tags tagfiles EAR: J2EE (EJB) Deploy em servidores J2EE "Hot deploy", copiar WAR/EAR para diretório Tomcat: /webapps JBoss: server/config/deploy DataSources: deploy de .XML Outros (JBoss): RAR (Resource Adapter) SAR (Service) Deploy "explodido" (opcional / debug) Ferramentas GUI de Deploy: overrides Struts Agenda Model (forms) View Controller (actions) Configuração Taglibs Tiles Validação Requisição Controller Envio de Dados Seleção do Visualizador Resposta View Resposta do Envio de dados Model View Página JSP "amarrada" à Struts <html:form action> Action (Controller) Campos Form (View) Controller Herda Action Predefinidas: Tiles, Download, Forward, Include... Implementar execute() Uma ou mais actions: request.getParameter("action") Dispara alguma ação de negócio Determina próxima página (forward) DispatchAction: implementar métodos similares ao execute(), mas com nome igual à da ação Configuração WEB-INF/struts-config.xml Mapeamento MVC, page flow WEB-INF/web.xml Configuração padrão da Struts, ex.: ActionServlet WEB-INF/: Outros arquivos da Struts WEB-INF/lib: Binários da Struts WEB-INF/.struts-config.mex: Layout visual para o struts-config.xml (MyEclipse) public class ContatoForm extends ActionForm { private String nome="", sobrenome="", email="", fone="", warning=""; public String getNome () { return nome; } public void setNome (String nome) { this.nome = nome; } // Omitido: outros getters/setters public String valida () { if (nome == null || nome.length() == 0) return "nome"; if (sobrenome == null || sobrenome.length() == 0) return "sobrenome"; if (email == null || email.length() == 0 || email.indexOf('@') == -1) return "email"; if (fone == null || fone.length() == 0) return "fone"; return null; } } public class MessageBean { private String message = ""; public String getMessage () { return message; } public void setMessage (String message) { this.message = message; } } public class AssinaAction extends Action { public ActionForward execute (ActionMapping map, ActionForm aForm, HttpServletRequest req, HttpServletResponse resp) throws Exception { ContatoForm form = (ContatoForm)aForm; String campoRuim = form.valida(); if (campoRuim != null) { warn(req, campoRuim); return map.findForward("valor-errado"); } else return map.findForward("sucesso"); } protected void warn (HttpServletRequest req, String msg) { MessageBean msgBean = new MessageBean(); msgBean.setMessage(msg); req.setAttribute("messageBean", msgBean); } } assina.jsp: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <html><head><title>Assinatura</title></head> <body><h1 align="center">Assinatura</h1> Digite seus dados de contato para receber nossa Newsletter!<p/> <center> <html:form action="/assina"> <table> <tr><td>Nome:</td><td><html:text property="nome"/></td></tr> <tr><td>Sobrenome:</td><td><html:text property="sobrenome"/></td></tr> <tr><td>Email:</td><td><html:text property="email"/></td></tr> <tr><td>Fone:</td><td><html:text property="fone"/></td></tr> </table> <html:submit value="Assine!"/> </html:form> </center> </body></html> confirma.jsp: (Usando taglib Struts Beans – NÃO PROGRAMAR ASSIM!!!) <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <html><head><title>Confirmação</title></head> <body><center><h1>Confirmação</h1> Muito obrigado, prepare seu Inbox para nosso spam! <table> <tr><td>Nome:</td> <td><bean:write name="contatoForm" property="nome"/></td></tr> <tr><td>Sobrenome:</td> <td><bean:write name="contatoForm" property="sobrenome"/></td></tr> <tr><td>Email:</td> <td><bean:write name="contatoForm" property="email"/></td></tr> <tr><td>Fone:</td> <td><bean:write name="contatoForm" property="fone"/></td></tr> </table> </center> </body></html> confirma.jsp: (Usando taglib JSTL Core – FAZER DESSA MANEIRA!!!) <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html><head><title>Confirmação</title></head> <body><center><h1>Confirmação</h1> Muito obrigado, prepare seu Inbox para nosso spam! <table> <tr><td>Nome:</td> <td><c:out value="${contatoForm.nome}"/></td></tr> <tr><td>Sobrenome:</td> <td><c:out value="${contatoForm.sobrenome}"/></td></tr> <tr><td>Email:</td> <td><c:out value="${contatoForm.email}"/></td></tr> <tr><td>Fone:</td> <td><c:out value="${contatoForm.fone}"/></td></tr> </table> </center> </body></html> valor-errado.jsp: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>Valor faltando ou incorreto!</title></head> <body><center> <h2>Valor faltando ou incorreto: <c:out value="${messageBean.message}"/>!</h2> Por favor, <a href="assina.jsp">Tente novamente</a>. </center> </body></html> assina2.jsp: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %> <html><head><title>Assinatura</title></head> <body><h1 ALIGN="CENTER">Assinatura</h1> Digite seus dados de contato para receber nossa Newsletter!<p/><center> <html:form action="/assina2"> <c:if test="${not empty contatoForm.warning}"> Campo faltando ou inválido: <bean:write name="contatoForm" property="warning" filter="false"/> </c:if> <table> <tr><td>Nome:</td><td><html:text property="nome"/></td></tr> <tr><td>Sobrenome:</td><td><html:text property="sobrenome"/></td></tr> <tr><td>Email:</td><td><html:text property="email"/></td></tr> <tr><td>Fone:</td><td><html:text property="fone"/></td></tr> </table> <html:submit value="Assine!"/> </html:form></center></body></html> struts-config.xml: <struts-config> <form-beans> <form-bean name="contatoForm" type="ContatoForm"/> </form-beans> <global-forwards> <forward name="sucesso" path="/confirma.jsp"/> </global-forwards> <action-mappings> <action path="/assina" type="AssinaAction" name="contatoForm" scope="request"> <forward name="valor-errado" path="/valor-errado.jsp"/> </action> <action path="/assina2" type="AssinaAction2" name="contatoForm" scope="request"> <forward name="valor-errado" path="/assina2.jsp"/> </action> </action-mappings> </struts-config> Struts Taglibs Struts inclui várias taglibs: Bean, Logic (maior parte obsoleta; usar JSTL) HTML: Gera elementos, útil para forms http://struts.apache.org/userGuide/struts-html.html Nested: Relacionamentos entre tags (pai/filho) Utilities: Diversos Tiles Tiles Taglib da Struts Permite compor páginas complexas, juntando fragmentos (tiles) reutilizáveis Como <jsp:include>, mas mais flexível e estruturado Facilidade de template Separa estrutura, layout e componentes Permite recombinar estes elementos facilmente http://struts.apache.org/userGuide/dev_tiles.html Tiles: Preparando Configuração do struts-config.xml: (copiar do struts-blank.war) <controller processorClass= "org.apache.struts.tiles.TilesRequestProcessor"/> <plug-in className="org.apache.struts.tiles.TilesPlugin"> <set-property property="definitions-config" value="/WEB-INF/tiles-defs.xml"/> <set-property property="moduleAware" value="true"/> </plug-in> Tiles: Planejando Comece planejando seu layout "master"... Cabeçalho Menu Corpo Rodapé Tiles: Planejando Defina estrutura de página/layout/fragmentos página1 página2 página3 layout Menu Cabeçalho Rodapé Corpo Tiles: Implementando Página (instancia/parametriza o layout) pagina1.jsp: <%@taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:insert page="/layout.jsp" flush="true"> <tiles:put name="titulo" value="Página 1"/> <tiles:put name="cabecalho" value="/cabecalho.html"/> <tiles:put name="menu" value="/menu.jsp"/> <tiles:put name="rodape" value="/rodape.jsp"/> <tiles:put name="corpo" value="/corpo1.jsp"/> </tiles:insert> Tiles: Implementando Layout layout.jsp: <%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <html><head> <title><tiles:getAsString name="titulo"/></title></head> <body><table width="100%"> <tr><td colspan="2"><tiles:insert attribute="cabecalho"/></td></tr> <tr><td width="120"><tiles:insert attribute="menu"/></td> <td><tiles:insert attribute="corpo"/></td></tr> <tr><td colspan="2"><tiles:insert attribute="rodape"/></td></tr> </ table></body></html> Tiles: Implementando os Tiles JSPs, HTMLs ou imagens, normais Exemplo: cabecalho.html <a href="http://www.companhia.com"> <img src="/BannerEsq.gif" align="left" border="0"> </a> <img src="/BannerDir.gif" align="right" border="0"> Tiles: Usando Definitions Especificação declarativa das páginas Suporta herança de páginas JSPs de página reduzidas 1 insert: <%@taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> <tiles:insert definition="pagina2.page" flush="true"/> Ainda mais facilidade para sites dinâmicos JSP pode decidir entre várias definitions Tiles-defs.xml: <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/tiles-config_1_1.dtd"> <tiles-definitions> <definition name=".layoutBase" path="/layout.jsp"> <put name="titulo" value=""/> <put name="cabecalho" value="cabecalho.html"/> <put name="menu" value="menu.jsp"/> <put name="rodape" value="rodape.jsp"/> <put name="corpo" value=""/> </definition> <definition name="pagina1.page" extends=".layoutBase" > <put name="titulo" value="Página 2"/> <put name="corpo" value="corpo2.jsp"/> </definition> </tiles-definitions> pagina1.jsp layout.jsp (titulo="Página 1") cabecalho.html menu.jsp corpo1.jsp rodape.jsp pagina2.jsp layout.jsp (titulo="Página 2") pagina3.jsp layout.jsp (titulo="Página 3") cabecalho.html cabecalho.html rodape.jsp menu.jsp menu.jsp corpo2.jsp corpo3.jsp rodape.jsp Validação Opções de validação Sem Validator: ad-hoc Validação Manual (ou semi-automática) Validação Automática Código de validação, onde? Form (e/ou JavaScript): Só regras encapsuláveis no bean; ex.: formato de string; valores mín/máx Action: Acesso à lógica de negócio, database, etc. http://struts.apache.org/userGuide/dev_validator.html Validação Manual Implementar ActionForm.validate() public ActionErrors validate ( ActionMapping map, HttpServletRequest req) { ActionErrors ae = super.validate(map, req); if (ae == null) ae = new ActionErrors(); if (...) ae.add("nome", new ActionMessage("contatoForm.nome")); if (...) ae.add("email", new ActionMessage("contatoForm.email")); ... return ae; } Detecte todos os erros, não só o primeiro! struts-config.xml: <action path="/assina" type="AssinaAction" name="contatoForm" scope="request" input="/assina.jsp" validate="true"> </action> <message-resources parameter="MessageResources" null="false"/> WEB-INF/classes/MessageResources.properties: contatoForm.nome=Nome não pode ser vazio contatoForm.email=Email deve ter formato "conta@host" assina.jsp: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <html><head><title>Assinatura</title></head> <body><h1 align="center">Assinatura</h1> Digite seus dados de contato para receber nossa Newsletter!<p/> <center> <html:form action="/assina"> <table> <html:errors/> <tr><td>Nome:</td><td><html:text property="nome"/></td></tr> <tr><td>Sobrenome:</td><td><html:text property="sobrenome"/></td></tr> <tr><td>Email:</td><td><html:text property="email"/></td></tr> <tr><td>Fone:</td><td><html:text property="fone"/></td></tr> </table> <html:submit value="Assine!"/> </html:form> </center> </body></html> public class AssinaAction extends Action { public ActionForward execute (ActionMapping map, ActionForm aForm, HttpServletRequest req, HttpServletResponse resp) throws Exception { // Se chegou até aqui, é porque o Struts já // executou a validação com sucesso! return map.findForward("sucesso"); } } Validação Automática Validação declarada em arquivo XML Validação Server-Side (Validator), obrigatória Mais segura, não contornável pelo usuário Validação Client-Side (JavaScript), opcional Mais eficiente, evita request ao servidor e refresh struts-config.xml: <plug-in className="org.apache.struts.validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/validator-rules.xml, /WEB-INF/validation.xml"/> </plug-in> validator-rules.xml, validation.xml: extraia do struts-blank.war WEB-INF/classes/MessageResources.properties: # Mensagens-padrão (copiar do comentário no validator-rules.xml): errors.required={0} é obrigatório. errors.minlength={0} não pode ter menos que {1} caracteres. ... validation.xml: <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1.3//EN" "http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd"> <form-validation> <formset> <form name="contatoForm"> <field property="nome" depends="required"><arg key="contatoForm.nome"/></field> <field property="sobrenome" depends="required"> <arg key="contatoForm.sobrenome"/> </field> <field property="email" depends="required,mask"> <arg key="contatoForm.email"/> <var> <var-name>mask</var-name> <var-value>^([\w]+)(.[\w]+)*@([\w]+)(.[\w]{2,3}){1,2}$</var-value> </var> </field> <field property="fone" depends="required"> <arg key="contatoForm.fone"/> </field> </form> </form-validation> assina.jsp: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %> <html><head><title>Assinatura</title></head> <body><h1 align="center">Assinatura</h1> <p align="center">Digite seus dados de contato para receber nossa Newsletter!</p> <center> <html:form action="/assina" onsubmit="return validateContatoForm(this);"> <table> <tr><td>Nome:</td><td><html:text property="nome"/> <html:errors property="nome"/></td></tr> <tr><td>Sobrenome:</td><td><html:text property="sobrenome"/> <html:errors property="sobrenome"/> </td></tr> <tr><td>Email:</td><td><html:text property="email"/> <html:errors property="email"/> </td></tr> <tr><td>Fone:</td><td><html:text property="fone"/> <html:errors property="fone"/> </td></tr> </table> <html:submit value="Assine!"/> </html:form><html:javascript formName="contatoForm"/></center></body></html> public class ContatoForm extends ValidatorForm { private String nome=""; private String sobrenome=""; private String email=""; private String fone=""; public String getNome () { return nome; } public void setNome (String nome) { this.nome = nome; } public String getSobrenome () { return sobrenome; } public void setSobrenome (String sn) {this.sobrenome = sn;} public String getEMail () { return email; } public void setEMail (String email) { this.email = email; } public String getFone () { return fone; } public void setFone (String nome) { this.fone = fone; } } // Nenhum código de validação! Validação no Servidor Validação no Cliente Validação: Capacidades Validações predefinidas: required, requiredif, validwhen, minlength, maxlength mask, creditCard, email, url integer, float, double, long, short, byte, date intRange, longRange, floatRange, doubleRange Definições globais (constantes) Validadores plugáveis Validação Híbrida (Manual + Automática)