Facelets case study: Ricettario Docente: Gabriele Lombardi [email protected] Sommario SLIDE CONTENUTO Scopo Descrizione dello scopo del mini-progetto. Architettura Come utilizzare JEE per questo progetto. Modello dei dati Un domain-model mappato con JPA. Business layer Funzionalità ed esposizione con WS. Interfaccia web Componenti facelet e utilizzo di primefaces. Scopo Realizzare una piccola applicazione web che: – permetta di creare/editare/eliminare/cercare delle ricette di cucina memorizzate in un database; – sia realizzata secondo il modello three-tier; – abbia un’interfaccia web MVC modulare; – offra le proprie funzionalità anche via web-service. Strumenti utilizzati (in GlassFish): – – – – JPA per la persistenza; EJB per il business-tier; Facelet con componenti custom per l’app web; JAX-WS per i web-services; Architettura Business tier JavaDB Endpoint JAX-WS EJB EJBcon con logica logicadidi business business Persistence provider Data tier Modello dei dati come JavaBeans TipoPiatto Ricetta Injection Injection ORM con JPA Controller Controllerdidi pagina paginaJSF JSF Espressioni EL Pagina PaginaJSF JSF con consintassi sintassi Facelet Facelet Ingrediente Controller Controllerdidi componente componente Componente Componente Espressioni EL composita composita Facelet Facelet Presentation tier Modello dei dati Utilizziamo dei «normali» JavaBeans; mappiamo la struttura dati sul DB con JPA; mappiamo la struttura dati sull’XML con JAX-B. @MappedSuperclass public class DomainObject implements Serializable { @Id @GeneratedValue private Long id; … } @Entity @XmlAccessorType(XmlAccessType.FIELD) public class Recipe extends DomainObject { @Column(nullable = false, length = 64) @XmlAttribute(required = true) private String name; @Lob @Column(nullable = false) private String description; @Entity public class Ingredient extends DomainObject{ @Column(nullable = false, length = 64) private String name; @Enumerated private PlateType plateType; @OneToMany(cascade = CascadeType.ALL) @XmlElementWrapper(name = "ingredients") @XmlElement(name = "ingredient") private List<Ingredient> ingredients; @Lob private String comment; @Column(nullable = false, length = 16) private String quantity; … } … } Modello dei dati Configurazione di JPA in persistence.xml Configurazione del DataSource JNDI: – in GlassFish (o con file apposito di configurazione). <persistence version="2.0" xmlns=“…" xmlns:xsi=“…" xsi:schemaLocation=“... …"> <persistence-unit name="RicettarioCorePU" transaction-type="JTA"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <jta-data-source>jdbc/Ricettario</jta-data-source> <mapping-file>com/gab/tests/j2ee/ricettario/core/entities/queries.xml</mapping-file> <properties> <property name="eclipselink.ddl-generation" value="create-tables"/> </properties> </persistence-unit> </persistence> Connection pool Risorsa JNDI Business layer Definire le query in un file XML: – in persistence.xml abbiamo detto: – <mapping-file>…/queries.xml</mapping-file> <?xml version="1.0" encoding="UTF-8"?> <entity-mappings xmlns=“…“ xmlns:xsi=“…“ xsi:schemaLocation=“… …" version="1.0"> <package>com.gab.tests.j2ee.ricettario.core.entities</package> <named-query name="Recipe.getAll"> <query>SELECT r FROM Recipe r</query> </named-query> <named-query name="Recipe.getNameLike"> <query> SELECT r FROM Recipe r WHERE lower(r.name) LIKE lower(:namePart) </query> </named-query> Tutte le ricette Ricette con like sul nome (parametro) <named-query name="Recipe.getIngredientLike"> <query> SELECT r FROM Recipe r, IN(r.ingredients) i WHERE lower(i.name) LIKE lower(:namePart) </query> </named-query> </entity-mappings> Ricette con like sul nome dell’ingrediente Business layer Implementazione di un EJB: – Stateless ogni richiesta è indipendente dalle altre: – è il tipo di EJB di sessione (con risultato) più efficiente, uno può essere condiviso tra svariati client in thread concorrenti. – annotato con JAX-WS per offrirsi come WebService; – con iniezione per l’accesso alla persistenza; – i metodi sono automaticamente transazionali (JTA). @Local public interface RecipesBean { public Recipe save(Recipe recipe); public List<Recipe> getAll(); public List<Recipe> getNameLike(String namePart); public List<Recipe> getIngredientNameLike(String namePart); public void delete(Recipe recipe); } @Stateless @WebService public class RecipesBeanImpl implements RecipesBean { @PersistenceContext(unitName = "RicettarioCorePU") private EntityManager em; @Override public Recipe save(Recipe recipe) {…} @Override @WebResult(name = "recipe") public List<Recipe> getAll() {…} @Override public void delete(@WebParam(name = "recipe") Recipe recipe) {…} @Override public List<Recipe> getNameLike(String namePart) {…} @Override public List<Recipe> getIngredientNameLike(String namePart) { return em .createNamedQuery("Recipe.getIngredientLike", Recipe.class) .setParameter("namePart", "%" + namePart.trim() + "%").getResultList(); } } Interfaccia Web: la componente Ha un controller proprio: – ne implementa le operazioni e ne mantiene lo stato. Ha una view propria: – definita con sintassi Facelet usando il namespace cc. @FacesComponent("recipeEditor") public class RecipeEditor extends UIPanel implements NamingContainer { private Recipe recipe; public Recipe getRecipe() { if (recipe == null) { recipe = (Recipe)getValueExpression("value") .getValue(FacesContext.getCurrentInstance() .getELContext()); } return recipe; } @Override public String getFamily() { return UINamingContainer.COMPONENT_FAMILY; } … // Operazioni implementate qui } <cc:interface componentType="recipeEditor"> <cc:attribute name="value" required="true" type="com.gab.tests.j2ee.ricettario.core.entities.Recipe"/> </cc:interface> <!-- IMPLEMENTATION --> <cc:implementation> … <p:commandButton icon="ui-icon-closethick" title="Elimina“ immediate="true" process="@this" update="@form" onclick="if (!confirm('Sicuro?')) { return false; }" action="#{cc.removeIngredient(ingredient)}"/> </p:column> </p:dataTable> <h:outputText value="Descrizione:"/> <p:inputTextarea value="#{cc.recipe.description}"/> </h:panelGrid> </cc:implementation> Interfaccia Web: Il controller di pagina La pagina: – è una facelet «normale» che utilizza la componente; – il controller da accesso allo strato di business. @ManagedBean(name = "recipes") @ViewScoped public class RecipesController implements Serializable { // Iniezione dell’EJB con lo strato di business. @EJB private RecipesBean rb; // Attributi ed operazioni qui che usano l’EJB. } © 2013 - ECOLE Interfaccia Web