Aquesta documentació segueix una estructura similar a la del framework FAM 2.0 [1], però només reescrivint aquelles parts que són diferents.
La següent taula mostra les principals diferències entre les dos versions:
Framework FAM 3.0 | Framework FAM 4.0 |
---|---|
Eclipse Galileu | Eclipse Mars |
Java 5/6 | Java 7/8 |
Weblogic 10.3.0 | Weblogic 12.1.3 |
Eclipselink 1.0.2 | Eclipselink 2.5.2 |
JSF 1.2 | JSF 2.1 |
Maven 2 | Maven 2 / 3 |
IceFaces 1.8.2 | PrimeFaces 6.1 Bootstrap 3.3.6 PrettyFaces 2.0 QueryDSL 4.0.3 OmniFaces 1.13 |
Si vols començar immediatament, l’article de creació d'un manteniment simple [2] és el teu punt d’entrada. Si pel contrari, vols tenir prèviament un coneixement dels nous aspectes del framework es recomana seguir l’ordre proposat.
New -> Run configurations -> Nou projecte BackOffice
Introduir el codi del projecte.
Seleccionar opcíó d'importació al menu New
Selecció del tipus d'importació: Existing Maven project
Selecció del projecte
La plantilla inclou perfils i drets per defecte (manteniment, consulta, ...), cal configurar els corresponents al nou projecte
Nota: Aquest pas es pot deixar per més endavant però mai es pot omitir.
inici.xhtml
i resources/literals/aplicacio.properties
amb els drets de l’aplicacióxxx-filter.properties
per fer servir aquests ids i posar bé els dretsperfils.properties
per fer servir aquests perfilsEngegar el servidor weblogic local i fer un desplegament local per poder verificar la pàgina de simulació del VUS i la pàgina d'inici de l'aplicació.
L'esquema general d'un projecte és:
fam_backoffice │ ├── src │ │ │ ├── cat.diba.jee.core │ │ │ ├── cat.diba.jee.fam │ │ │ ├── filters │ │ │ │ │ ├── local-filter.properties │ │ │ │ │ ├── dev-filter.properties │ │ │ │ │ ├── int-filter.properties │ │ │ │ │ ├── pre-filter.properties │ │ │ │ │ └── pro-filter.properties │ │ │ └── META-INF │ │ │ └── persistence.xml │ ├── generated-sources │ ├── resources │ ├── resources-test │ ├── scaffolding │ ├── test │ ├── WebRoot │ └── target
La següent taula descriu el significat dels directoris:
Directori | Descripció |
---|---|
src | Codi font de l'aplicació. En els següents apartats s’explica amb més detall. Directoris / paquets principals:
|
generated-sources | Classes generades a partir de les entitats o models pel plugin de QueryDSL. En els següents apartats s’explica amb més detall |
resources i resources-test | Recursos de l'aplicació pels entorns d'execució i de test. Exemple: log4j.properties |
scaffolding | Plantilles per generar el codi mínim per les operacions típiques d’un CRUD (Create Read Update Delete). Veure l'article de scaffolding [3] |
test | Codi font dels test |
WebRoot | Contingut web del projecte: vistes, configuració, ... S’explica a l'apartat vistes |
target | Directori on Maven genera el arxius temporals i el war |
La següent imatge mostra els diferents paquets del core:
Les classes principals són:
cat.diba.core.entity.DibaEntity:
Classe arrel de totes les entitats del projectecat.diba.core.service.DibaService
Serveis generals implementats per gestionar un CRUDcat.diba.core.view.bb.DibaBB
Backing Bean o Managed Bean base per gestionar les vistes JSFcat.diba.core.view.datamodel.DibaDataModel
Model de dades per gestionar les taules amb paginació lazycat.diba.core.view.search.DibaSearchBean
Bean per gestionar les cerques que filtren els resultats de les taulesNo existeix el paquet DAO perquè aquesta funció la realitza, en la majoria dels casos, JPA de forma que és el propi servei el que accedeix a la base de dades mitjançant l'Application Managed Persistence Context.
La següent imatge mostra els paquets específics d'un projecte, en aquest cas del projecte FAM:
A continuació la descripció dels paquets i un exemple dels noms de classes amb l'entitat FamExpedient:
Paquet | Descripció |
---|---|
cat.diba.jee.fam.base | Per defecte dos classes: els missatges de l'aplicació i les constants. |
cat.diba.jee.fam.entity | Entitats JPA Exemple: FamExpedient. Al generar les entitats des de les taules (veure Generació model de dades JPA [4]) totes porten el prefix Fam, però alhora d'anomenar a la resta de classes s'elimina aquest prefix |
cat.diba.jee.fam.service | Serveis de les entitats. Són les classes amb el negoci de l'aplicació. Normalment trobarem un servei per entitat Exemple: ExpedientService |
cat.diba.jee.fam.view.bb | Backed /Managed Bean que gestionen les pàgines JSF. Normalment trobarem un BB per entitat Exemple: ExpedientBB |
cat.diba.jee.fam.view.datamodel | Models de dades de les entitats que permeten connectar-se a les taules de la vista amb paginació lazy i cerca. Normalment trobarem un DataModel per entitat Exemple: ExpedientDataModel |
cat.diba.jee.fam.view.pojo | Paquet opcional. Trobarem aquelles classes que no tenen una relació directe amb la base de dades (no són entitats), però són el resultat de consultes de la base de dades o de càlculs finals |
cat.diba.jee.fam.view.reports | Paquet opcional. Classes relacionades amb la generació d'informes: PDF (ireport), Word (Aspose), Excel (poi), .... Veure els exemples del FAM |
cat.diba.jee.fam.view.search | Classes relacionades amb la cerca de les vistes índex. Normalment trobarem un SearchBean per entitat Exemple: ExpedientSearchBean |
Les entitats hauran de seguir el següent exemple (no es mostra el mapeig JPA de la taula):
@Entity public class FamExpedient implements Serializable, DibaEntity { @Override public Long getId() { return camp amb l'identificador de la fila; } }
QueryDSL [5] é un framework DSL (domain-specific language - llenguatge específic del domini) que permet escriure consultes type-safe molt semblant a SQL i per múltiples formes d'accedir a les dades: JPA, JDO, SQL, MongoDB, ...
Per defecte, l'entorn de desenvolupament està configurat per generar una classe que embolcalla l'entitat i permet aprofitar les característiques de QueryDSL. La nova classe es crea al directori generated-sources
i el mateix paquet, el nom es genera segons el patró NomEntitat
-> QNomEntitat
.
Veiem un exemple de cerca d'expedient amb la versió del framework FAM 2.0 que es pot trobar al DAO:
@Override public Query creaQuery(ExpedientSearchFilter filtre, boolean nomesCount, int initrow, int rows) { LOG.debug("creaQuery(filtre, nomesCount, initrow, rows) - Inici"); String whereClause = " where EXP_ESTAT_ID = ESTAT_ID" + " and EXP_TIPUS_ID = TIPUS_ID" + " and (EXP_BAIXA_DATA > SYSDATE OR EXP_BAIXA_DATA IS NULL)"; if (FormatUtils.checkIdValid(filtre.getFiltreIdEstat())) { whereClause += " and EXP_ESTAT_ID = " + filtre.getFiltreIdEstat(); } if (FormatUtils.checkIdValid(filtre.getFiltreIdTipus())) { whereClause += " and EXP_TIPUS_ID = " + filtre.getFiltreIdTipus(); } if (FormatUtils.checkStringValid(filtre.getFiltreTitol())) { whereClause += " and fam_translate(EXP_TITOL) like '%' || fam_translate('" + filtre.getFiltreTitol() + "') || '%'"; } if (FormatUtils.checkStringValid(filtre.getFiltreGestorCodiSap())) { whereClause += " and fam_translate(EXP_GESTOR_CODI_SAP) like '%' || fam_translate('" + filtre.getFiltreGestorCodiSap() + "') || '%'"; } if (FormatUtils.checkStringValid(filtre.getFiltreNumExpedientSap())) { whereClause += " and fam_translate(EXP_NUM_EXPEDIENT_SAP) like '%' || fam_translate('" + filtre.getFiltreNumExpedientSap() + "') || '%'"; } if (FormatUtils.checkStringValid(filtre.getFiltreInfoXBMQ())) { whereClause += " and fam_translate(EXP_INFO_XBMQ) like '%' || fam_translate('" + filtre.getFiltreInfoXBMQ() + "') || '%'"; } QueryDTO queryDTO = new QueryDTO(); queryDTO.setFrom(" from FAM_EXPEDIENT, FAM_EXPEDIENT_TIPUS, FAM_EXPEDIENT_ESTAT "); queryDTO.setWhere(whereClause); queryDTO.setOrder(filtre.getSqlOrdre()); Query query = createQuery(nomesCount, queryDTO, initrow, rows); LOG.debug("creaQuery(filtre, nomesCount, initrow, rows) - Fi"); return query; }
I ara la mateixa cerca amb QueryDSL que es pot trobar al SearchBean:
public Predicate where() { QFamExpedient expedient = QFamExpedient.famExpedient; // No carregar mai els eliminat lògics BooleanExpression onlyNotDeleted = expedient.expBaixaData.isNull() .or(expedient.expBaixaData.gt(Expressions.currentDate())); BooleanBuilder where = new BooleanBuilder(onlyNotDeleted); if (null != titol) { where = where.and(expedient.expTitol.containsIgnoreCase(titol)); } if (null != expedientSap) { where = where.and(expedient.expNumExpedientSap.containsIgnoreCase(expedientSap)); } if (null != codiSap) { where = where.and(expedient.expGestorCodiSap.containsIgnoreCase(codiSap)); } if (null != estatId) { where = where.and(expedient.famExpedientEstat.estatId.eq(estatId)); } if (null != tipusId) { where = where.and(expedient.famExpedientTipus.tipusId.eq(tipusId)); } if (null != codiXBMQ) { where = where.and(expedient.expInfoXbmq.like(codiXBMQ)); } return where; }
Principals avantatges:
El servei de l'entitat estén el servei DibaService
que ja implementa la major part dels mètodes d'un CRUD. Els dos punts a tenir en compte al definir el servei són:
@TransactionManagement(TransactionManagementType.BEAN)
public EntityPath<DibaEntity> entityPath()
Amb la tècnica de scaffolding [3], aquests dos punts ja es generen automàticament.
@TransactionManagement(TransactionManagementType.BEAN) public class ExpedientService extends DibaService { /** * Constructor per defecte * */ public ExpedientService() { super(FamExpedient.class); } @Override public EntityPath<FamExpedient> entityPath() { return QFamExpedient.famExpedient; } }
La cerca de l'entitat es basa en la classe DibaSearchBean
. Per simplicar el codi d'exemple, en aquesta ocació fem servir l'entitat Zona i un camp de cerca pel nom. Llavors la classe ZonaSearchBean
quedaría així:
@ManagedBean @SessionScoped public class ZonaSearchBean extends DibaSearchBean { private static final long serialVersionUID = 1L; private String name; public String getName() { return name; } @Override public OrderSpecifier<?> orderBy() { return QFamZona.famZona.famZonaNom.asc(); } @Override public OrderSpecifier<?> orderBy(String sortField, SortOrder sortOrder) { OrderSpecifier<?> orderSpecifier = QFamZona.famZona.famZonaNom.asc(); return orderSpecifier; } @Override public void reset() { this.name = null; } public void setName(String name) { this.name = name; } public Predicate where() { QFamZona famZona = QFamZona.famZona; BooleanBuilder where = new BooleanBuilder(); if (null != name) { where = where.and(famZona.famZonaNom.containsIgnoreCase(name)); } return where; } }
És important remarcar l'abast del bean: @SessionScoped
. Això permet mantenir les condicions de cerca al tornar a la pàgina índex: valors dels camps, resultat, pàgina concreta de la taula
Amb la tècnica de scaffolding [3] ja es genera automàticament l'anotació.
El model de dades s'utilitza a les taules i ja incorpora la paginació lazy i la cerca.
public class ExpedientDataModel extends DibaDataModel<FamExpedient> { @Inject private ExpedientService expedientService; @Override public DibaService<FamExpedient> service() { return expedientService; } }
Per defecte, el directori views
tindrà una carpeta per entitat on es podrà trobar les diferents vistes. Normalment seràn dos i una opcional, segons les necessitats de l'aplicació:
Un exemple dels noms de les vistes, continuant amb l'entitat FamExpedient, és la següent:
Sense diàleg | Amb diàleg |
---|---|
WebRoot │ └── views │ └── expedient │ ├── expedientDetall.xhtml │ └── expedientLlista.xhtml |
WebRoot │ └── views │ └── expedient │ ├── expedientDetall.xhtml │ ├── expedientDialog.xhtml │ ├── expedientLlista.xhtml │ └── expedientLlistaForm.xhtml |
Amb la tècnica de scaffolding [3] es genera automàticament l'estructura sense diàleg
Scaffold és bastida en anglés. La tècnica de scaffolding consisteix en generar codi que permet la funcionalitat bàsica d’un model o entitat, normalment un CRUD (Create, Read, Update, Delete). D’aquesta forma s’estalvia temps alhora de generar el codi base, la bastida, d’una entitat, que en el cas de les aplicacions JEE de la Diputació són:
La generació d'aquest codi es basa en plantilles ja establertes de forma que també s'estalvia temps i possibles errors en el disseny de les vistes
La estructura de les plantilles es pot veure a la següent imatge. Cal tenir en compte que la carpeta agd
correspon al projecte on es vol generar el codi, per defecte a la plantilla apareix com a fam.
La configuració està a l'arxiu scaffolding.json
{ "output_directory": ".", "template_location": "scaffolding", "base_package": "cat.diba.jee.agd", "read": false, "only_with_entity_directives": true, "entities": ["ExempleModel"] }
Per generar el codi de les entitats caldrà modificar la cadena entities
amb els noms de les entitats corresponents i adaptar el paquet base al del projecte.
S'executa la classe tools.Scaffolding
i a continuació cal refrescar el projecte a l'Eclipse.
Actualment no genera la navegació de prettyFaces
i cal crear-la manualment a l'arxiu pretty-aplicacio.xml
. Veure l'article navegació entre pàgines [6]
Recordatori del cicle de vida o fases d'una pàgina JSF:
Les tasques de validació (fase 3) es fan abans que l'execució del codi de negoci (fases 4 i 5). En aquelles situacions que no es supera la validació s'obté un millora del rendiment. La validació de formularis es farà aprofitant l'estàndar JSR-303.
La llibreria OmniFaces [8] ofereix eines per estalviar tasques repetitives:
Els missatges d'error es troben a l'arxiu cat.diba.jee.core.faces.Messages_ca.properties
Un factor important és l'actualització de les parts del DOM que ens interessa, ja que la gran majoria seran peticions Ajax. Això s'aconsegueix mitjançant l'atribut update
d'alguns components. Com a norma general sempre s'actualitzarà el component amb l'id messages
que permet la visualització d'avisos (info, error, perill, ...). D'altres exemples d'update
<p:ajax event="dialogReturn" listener="#{ubicacioBB.onAreaChosen}" update="messages areaId areaId_hidden" />
<p:commandLink action="#{ubicacioBB.saveOrUpdate}" id="save" ajax="true" styleClass="btn btn-default" type="submit" update="@form messages">
<i class="fa fa-save" aria-hidden="true" />
#{literalsCore['Helper.Save']}
</p:commandLink>
És el cas més senzill. S'utilitza <o:outputLabel> [10] i l'atribut required="true"
al camp d'entrada. L'avantatge d'aquesta etiqueta és que aprofita el valor del camp referenciat com a valor pels missatges d'error.
Exemple: per defecte, el missatge d'error és form_id:field_i és un camp obligatori
, però amb aquest component l'error queda com (segons l'exemple que apareix desprès) Nom és un camp obligatori.
<form> <o:outputLabel for="nomUbicacio" value="#{literalsAplicacio['Ubicacio.Nom']}" styleClass="control-label col-sm-1 text-left" /> <div class="col-sm-3"> <h:inputText value="#{ubicacioBB.detailEntity.famUbiNom}" id="nomUbicacio" styleClass="form-control" required="true" /> </div> </form> <o:highlight styleClass="has-error" focus="true" />
Aquest cas es presenta quan el camp s'informa a partir d'un dialeg o depen d'un altre camp.
<div class="form-group #{ areaNom.valid ? '' : 'has-error'}"> <o:outputLabel for="areaId_hidden" value="#{literalsAplicacio['Ubicacio.Area']}" styleClass="control-label col-sm-1 text-left" /> <div class="col-sm-3"> <div class="input-group"> <h:inputText id="areaId" styleClass="form-control" value="#{ubicacioBB.detailEntity.famArea != null ? ubicacioBB.detailEntity.famArea.famAreNom : ''}" disabled="true" /> <!-- http://stackoverflow.com/questions/29490141/validation-disabled-pinputte... [13] --> <h:inputHidden id="areaId_hidden" required="true" binding="#{areaNom}" value="#{ubicacioBB.detailEntity.famArea != null ? ubicacioBB.detailEntity.famArea.famAreNom : ''}" /> <div class="input-group-addon"> <p:commandLink actionListener="#{areaBB.chooseArea}" immediate="true" ajax="true"> <i class="fa fa-search" aria-hidden="true" /> <p:ajax event="dialogReturn" listener="#{ubicacioBB.onAreaChosen}" update="messages areaId areaId_hidden" /> </p:commandLink> </div> </div> </div> </div>
PrettyFaces és una llibreria open-source per re-escriure les URL (URL-Rewriting) i fer-les més amigables amb suport millorat per JSF 1.1, 1.2 i 2.0. PrettyFaces resol el problema "URL REST" de forma elegant, i a més incorpora característiques com: accions de càrrega de pàgina, integració perfecte amb la navegació JSF i compatibilitat sense configuració amb d'altres frameworks web.
El primer pas és afegir les dependències al projecte Maven. L'arquetipo del FAM ja les incorpora al pom.xml
<!-- Pretty URL --> <dependency> <groupId>org.ocpsoft.rewrite</groupId> <artifactId>rewrite-servlet</artifactId> <version>2.0.12.Final</version> </dependency> <dependency> <groupId>org.ocpsoft.rewrite</groupId> <artifactId>rewrite-config-prettyfaces</artifactId> <version>${prettyfaces.version}</version> </dependency> <!-- End PrettyFaces -->
A continuació, a l'arxiu web.xml
, cal indicar els fitxers que defineixen les rutes de la nostra aplicació sota el paràmetre com.ocpsoft.pretty.CONFIG_FILES
. Aquí tenim l'exemple del FAM:
<!-- Configuració PretyFaces --> <context-param> <param-name>com.ocpsoft.pretty.CONFIG_FILES</param-name> <param-value>/WEB-INF/pretty-aplicacio.xml,/WEB-INF/pretty-index.xml,/WEB-INF/pretty-demos.xml</param-value> </context-param> <context-param> <param-name>com.ocpsoft.pretty.DEVELOPMENT</param-name> <param-value>true</param-value> </context-param>
Per mostrar com es configura la navegació farem servir com a exemple els següents valors:
EntitatBase
WebRoot/views
aplicacions.diba.cat
fambo
A continuació la taula que defineix la relació entre una petició HTTP i l'acció que es desencadena en el cas d'operacions amb les entitats i la base de dades, bàsicament lo que s'anomena CRUD: Create (crea), Read (llegeix), Update (actualitza) i Destroy (suprimeix).
Verb HTTP | Ruta | BackingBean::mètode | Usat per |
---|---|---|---|
GET | /entitatsBase | entitatBaseBB::index | Retorna la vista on apareixen totes les entitats base |
GET | /entitatsBase/crea | entitatBaseBB::create | Retorna la vista per crear una nova entitat base |
POST | /entitatsBase/crea | entitatBaseBB::saveOrUpdate | Crea (desa) una nova entitat base |
GET | /entitatsBase/:id/edita | entitatBaseBB::edit | Retorna la vista per editar una entitat base que ja existeix |
POST | /entitatsBase/:id/edita | entitatBaseBB::saveOrUpdate | Actualitza (desa) una entitat base concreta |
POST | /entitatsBase/:id/edita | entitatBaseBB::delete | Suprimeix una entitat base concreta |
En JSF, és el BackingBean i el mètode qui determina l'accio a realitzar. Per aquest motiu, una mateixa ruta i verb poden tenir diferents resultats segons el mètode a executar.
Una de les avantages de PrettyFaces
és el fet de poder definir una URL que executa un mètode d'un Backing Bean. Per exemple, en determinades etiquetes JSF no està disponible l'atribut action
o actionListener
. O en d'altres casos, la URL és una etiqueta HTML que no permet afegir cap referència a Java, però amb la definició de la navegació amb PrettyFaces
si s'executarà el mètode desitjat.
La següent taula mostra les accions de navegació definides per FAMEntitatBase, la adreça URL corresponent i la configuració de PrettyFaces
:
Acció de navegació | Ruta (URL) | PrettyFaces |
---|---|---|
Índex de les entitats | /entitatsBase |
<url-mapping id="entitatsBase"> <pattern value="/entitatsBase" /> <view-id value="/views/entitatBase/entitatBaseLlista.xhtml" /> </url-mapping> |
Inicialitza la nova entitat base i retorna la vista HTML | /entitatsBase/crea |
<url-mapping id="newEntitatBase"> <pattern value="/entitatsBase/crea" /> <view-id value="/views/entitatBase/entitatBaseDetall.xhtml" /> <!-- Evita executar l'acció amb submit o Ajax, només amb GET --> <action onPostback="false">#{entitatBaseBB.create}</action> </url-mapping> |
Inicialitza la edició d'una entitat base i retorna la vista HTML | /entitatsBase/{id}/edita |
<url-mapping id="viewEntitatBase"> <pattern value="/entitatsBase/#{id}/edita" /> <view-id value="/views/entitatBase/entitatBaseDetall.xhtml" /> <!-- Evita executar l'acció amb submit o Ajax, només amb GET --> <action onPostback="false">#{entitatBaseBB.edit}</action> </url-mapping> |
Inicialitza la duplicació d'una entitat base i retorna la vista HTML | /entitatsBase/{id}/duplica |
<url-mapping id="duplicaEntitatBase"> <pattern value="/entitatsBase/#{id}/duplica" /> <view-id value="/views/entitatBase/entitatBaseDetall.xhtml" /> <!-- Evita executar l'acció amb submit o Ajax, només amb GET --> <action onPostback="false">#{entitatBaseBB.duplica}</action> </url-mapping> |
Mostra un dialeg. Útil per seleccionar una entitat base des d'una altra pantalla | /entitatsBase/dialog |
<url-mapping id="dialogEntitatBase"> <pattern value="/entitatsBase/dialog"></pattern> <view-id value="/views/entitatBase/entitatBaseDialog.xhtml" /> </url-mapping> |
Quan arriba una petició, el servlet de PrettyFaces
verifica si la URL coincideix amb algun dels patrons configurats. Si és així, llavors primer executa l'acció, si en té d'alguna definida, i a continuació retorna la vista corresponent.
Per generar la URL correcta a les vistes (arxius xhtml
) i els Backing Bean, només cal escriure "pretty:<view_id>"
Exemples:
<h:link outcome="pretty:entitatsBase" value="Índex" />
Enllaç a la pàgina índex des d'un bean:
public String index() { return "pretty:entitatsBase"; }
id
) coincideix amb el nom que s'ha definit a la navegació <pattern value="/entitatsBase/#{id}/edita" /><p:column headerText="Assumpte" sortBy="#{element.famEntAssumpte}"> <h:link outcome="pretty:viewEntitatBase" value="#{element.famEntAssumpte}"> <f:param name="id" value="#{element.id}"></f:param> </h:link> </p:column>
Cal tenir en compte que si no troba cap regla de navegació que coincideixi amb la ruta, ja sigui definida amb PrettyFaces o amb JSF, llavors torna a mostrar la mateixa vista.
El flux normal de navegació és el que mostra la següent imatge:
El menú principal està definit a l'arxiu menuAplicacio.xhtml
i caldria afegir un enllaç com aquest:
<h:link outcome="pretty:entitatsBase">#{literalsAplicacio['Menu.EntitatBase']}</h:link>
<p:column headerText="Assumpte"> <h:link outcome="pretty:viewEntitatBase" value="#{element.famEntAssumpte}"> <f:param name="id" value="#{element.id}"></f:param> </h:link> </p:column>
<p:commandLink action="#{entitatBaseBB.saveOrUpdate}" id="save" ajax="true" styleClass="btn btn-default" type="submit" update="@form messages"> <i class="fa fa-save" aria-hidden="true" /> #{literalsCore['Helper.Save']} </p:commandLink>
<h:link styleClass="btn btn-default" outcome="pretty:entitatsBase"> <i class="fa fa-undo" aria-hidden="true" /> #{literalsCore['Helper.Back']} </h:link>
<p:commandLink action="#{entitatBaseBB.delete}" styleClass="btn btn-danger" immediate="true"> <i class="fa fa-trash" aria-hidden="true" /> #{literalsCore['Helper.Destroy']} <p:confirm header="Confirmació" message="#{literalsCore['Helper.Destroy.Confirm']}" /> </p:commandLink>
El diàleg de confirmació està definit com
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"> <p:commandButton value="#{literalsCore['Helper.Yes']}" type="button" styleClass="ui-confirmdialog-yes btn btn-default" icon="fa fa-check" /> <p:commandButton value="#{literalsCore['Helper.No']}" type="button" styleClass="ui-confirmdialog-no btn btn-default" icon="fa fa-close" /> </p:confirmDialog>
En aquest article s'explica com generar un CRUD (Create Update Read Delete) agafant com a base una entitat de les més simples del projecte FAM Backoffice: FamZona.
Nota: Als noms de les entitats es respecta el prefix FAM de les taules, però a la resta de classes no per facilitar la lectura i la cerca.
Per generar l'entitat a partir de la taula cal seguir l'article Generació model de dades JPA [4]
Aquí s'expliquen els passos bàsics, per veure en detalla com funciona llegir l'article scaffolding [15].
Els canvis es fan a l'arxiu /scaffolding.json
i cal modificar la clau entities
amb el nom de l'entitat ["FamZona"]
i la clau base_package
amb el nom del paquet del projecte.
L'arixu quedaria així:
{
"output_directory": ".",
"template_location": "scaffolding",
"base_package": "cat.diba.jee.fam",
"read": false,
"only_with_entity_directives": true,
"entities": ["Zona"]
}
Nota: com es vol obtenir ZonaService, el nom de l'entitat s'informa sense el prefix Fam. A les plantilles, per solucionar possibles problems de si porta o no prefix, ja està contemplat en cadascun dels casos com es veurà als apartats de revisió del codi generat.
Executar la classe tools.Scaffolding
que està definida al menú per obtenir les classes generades a partir de les plantilles.
En aquest punt ja estan creades les classes i les vistes mínimes per implementar el CRUD de l'entitat FamZona. Al següent apartat s'expliquen els canvis concrets de cadascuna de les classes.
Les classes de servei són les que inclouen el negoci de l'aplicació i no tenen accés a la vista. Com que les aplicacions fan servir JPA i un contenidor de transaccions del servidor d'aplicacions, no cal una capa de DAO.
El servei generat en base a la plantilla és el següent:
package cat.diba.jee.fam.service; import javax.ejb.TransactionManagement; import javax.ejb.TransactionManagementType; import com.querydsl.core.types.EntityPath; import cat.diba.jee.core.service.DibaService; import cat.diba.jee.fam.entity.FamZona; import cat.diba.jee.fam.entity.QFamZona; @TransactionManagement(TransactionManagementType.BEAN) public class ZonaService extends DibaService<FamZona> { /** * Constructor per defecte * */ public ZonaService() { super(FamZona.class); } @Override public EntityPath<FamZona> entityPath() { return QFamZona.famZona; } }
Es pot observa l'ús de la classe generada per QueryDSL: QFamZona. En aquest cas no cal modificar ni afegir res al servei.
Aquesta classse s'utilitza a la vista ja que proporciona els camps per filtrar o cercar resultats i alhora utilitza el servei per poder aplicar les condicions de cerca als resultats.
La classe generada en base a la plantilla és la següent:
package cat.diba.jee.fam.view.search; import javax.faces.bean.ManagedBean; import javax.faces.bean.SessionScoped; import org.primefaces.model.SortOrder; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Predicate; import cat.diba.jee.core.view.search.DibaSearchBean; import cat.diba.jee.fam.entity.QFamZona; @ManagedBean @SessionScoped public class ZonaSearchBean extends DibaSearchBean { /** * */ private static final long serialVersionUID = 1L; /* * (non-Javadoc) * * @see cat.diba.jee.core.view.search.DibaSearchBean#orderBy() */ @Override public OrderSpecifier<?> orderBy() { // FIXME // return QFamZona.zona..asc(); return null; } /* * (non-Javadoc) * * @see cat.diba.jee.core.view.search.DibaSearchBean#order(java.lang.String, * org.primefaces.model.SortOrder) */ @Override public OrderSpecifier<?> orderBy(String field, SortOrder sortOrder) { // FIXME return orderBy(); } /* * (non-Javadoc) * * @see cat.diba.jee.core.view.search.DibaSearchBean#reset() */ public void reset() { // FIXME } /* * (non-Javadoc) * * @see cat.diba.jee.core.view.search.DibaSearchBean#where() */ public Predicate where() { QFamZona zona = QFamZona.famZona; BooleanBuilder where = new BooleanBuilder(); // FIXME return where; } }
A tenir en compte que l'abast del bean és @SessionScoped
per poder mantenir els criteris de cerca i l'índex de la pàgina al navegar per l'aplicació. Si això no és necessari, llavors por canviar-se per @ViewScoped
que és més eficient des del punt de vista de memòria al servidor.
La cerca de zona és només pel nom, llavors caldrà afegir a la classe un atribut que permeti introduir aquest valor a la vista:
private String name; public String getName() { return name; } @Override public void reset() { this.name = null; } public void setName(String name) { this.name = name; }
I adaptar les condicions de cerca amb l'ajuda de la classse QFamZona generada per QueryDSL:
public Predicate where() { QFamZona famZona = QFamZona.famZona; BooleanBuilder where = new BooleanBuilder(); if (null != name) { where = where.and(famZona.famZonaNom.containsIgnoreCase(name)); } return where; }
I per últim, es modifican les condicions d'ordenació. Més endavant s'explica com es lliga amb la vista i la taula:
/** * Ordenació per defecte */ @Override public OrderSpecifier<?> orderBy() { return QFamZona.famZona.famZonaNom.asc(); } /** * Ordenació segons les columnes de la taula de la vista */ @Override public OrderSpecifier<?> orderBy(String sortField, SortOrder sortOrder) { OrderSpecifier<?> orderBy = orderBy(); if (sortField != null && sortOrder != null) { if (sortField.endsWith(QFamZona.famZona.famZonaNom.getMetadata().getName())) { orderBy = SortOrder.DESCENDING.equals(sortOrder) ? QFamZona.famZona.famZonaNom.desc() : QFamZona.famZona.famZonaNom.asc(); } } return orderBy; }
Aquesta classe està relacionada amb el model de dades necessari per a les taules de PrimeFaces [16] amb càrrega lazy dels elements.
El model de dades generat en base a la plantilla és el següent:
package cat.diba.jee.fam.view.datamodel; import javax.inject.Inject; import cat.diba.jee.core.service.DibaService; import cat.diba.jee.core.view.datamodel.DibaDataModel; import cat.diba.jee.fam.entity.FamZona; import cat.diba.jee.fam.service.ZonaService; public class ZonaDataModel extends DibaDataModel<FamZona> { @Inject private ZonaService zonaService; @Override public DibaService<FamZona> service() { return zonaService; } }
No cal fer cap modificació.
Aquesta és la classe que gestiona totes les crides de la vista i retorna els valors a mostrar.
La classe generada en base a la plantilla és la següent:
package cat.diba.jee.fam.view.bb; import javax.faces.bean.ManagedBean; import javax.faces.bean.ManagedProperty; import javax.faces.bean.ViewScoped; import javax.inject.Inject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import cat.diba.jee.core.service.DibaService; import cat.diba.jee.core.view.bb.DibaBB; import cat.diba.jee.core.view.search.DibaSearchBean; import cat.diba.jee.fam.entity.FamZona; import cat.diba.jee.fam.service.ZonaService; import cat.diba.jee.fam.view.datamodel.ZonaDataModel; import cat.diba.jee.fam.view.search.ZonaSearchBean; @ManagedBean @ViewScoped public class ZonaBB extends DibaBB<FamZona> { private static final long serialVersionUID = 1L; private static final String CLASS_ID = ZonaBB.class.getName(); private static final Log LOG = LogFactory.getLog(CLASS_ID); @Inject private ZonaService zonaService; @Inject private ZonaDataModel dataModel; @ManagedProperty(value = "#{zonaSearchBean}") private ZonaSearchBean zonaSearchBean; @Override public ZonaDataModel getDataModel() { return dataModel; } @Override public String index() { LOG.trace(CLASS_ID + "::index()"); return "pretty:zonas"; } @Override public DibaService<FamZona> service() { return zonaService; } @Override protected FamZona newEntity() { LOG.trace(CLASS_ID + "::newEntity()"); return new FamZona(); } /** * Instància el bean de cerca, que és de sessió, per poder mantenir els * valors dels camps i la darrera pàgina de la taula al tornar d'editar una * entitat. * * @see DibaSearchBean#setLastFirst(int) * @see DibaSearchBean#getLastFirst() * */ public void setZonaSearchBean(ZonaSearchBean searchBean) { this.zonaSearchBean = searchBean; this.dataModel.setSearchBean(searchBean); } }
Aspectes a considerar:
@ManagedProperty(value = "#{zonaSearchBean}")
@ViewScoped
per una millor gestió de la memòria al servidorpublic void setZonaSearchBean(ZonaSearchBean searchBean)
"pretty:zones"
Segons les necessitats del negoci, cal ampliar ZonaBB per poder tornar totes les zones ordenades pel nom i mostrar-les en un camp de selecció desplegable. A la vista veurem com es tracta la llista d'entitats sense necessitat de transformar-la.
/** * Zones amb l'ordenació per defecte (el nom) * * @return */ public List<FamZona> getZones() { List<FamZona> zones = new ArrayList<FamZona>(); try { zones = zonaService.findAll(null, zonaSearchBean.orderBy()); } catch (IllegalStateException | SecurityException | SystemException | RollbackException | HeuristicRollbackException e) { MessagesUtils.errorMessage(new DibaException(e)); } return zones; }
I aquestes són les modificacions a fer en les classes generades. A continuació es veure com definir la navegació amb PrettyFaces y les vistes.
Les regles de navegació que cal agefir a l'arxiu pretty-aplicacio.xml
són
<!-- Índex o llista: des del menu o des de l'edició --> <url-mapping id="zones"> <pattern value="/zones" /> <view-id value="/views/zona/zonaLlista.xhtml" /> </url-mapping> <!-- Edició de la zona des de la fila de la taula --> <url-mapping id="viewZona"> <pattern value="/zones/#{id}/edita" /> <view-id value="/views/zona/zonaDetall.xhtml" /> <!-- Evita executar l'acció amb submit o Ajax, només amb GET --> <action onPostback="false">#{zonaBB.edit}</action> </url-mapping> <!-- Nova zona des de l'índex o llistat --> <url-mapping id="newZona"> <pattern value="/zones/crea" /> <view-id value="/views/zona/zonaDetall.xhtml" /> <!-- Evita executar l'acció amb submit o Ajax, només amb GET --> <action onPostback="false">#{zonaBB.create}</action> </url-mapping>
A l'article navegació [17]es pot trobar una explicació detallada.
Ara les vistes tenen una plantilla base, /WebRoot/WEB-INF/templates/layout.xhtml
, i els arxius xhtml
concrets de les entitats només descriuen la vista concreta.
Com a pas previ, cal afegir a l'arxiu on es defineix el menú, /WebRoot/WEB-INF/templates/menuAplicacio.xhtml, l'opció de la llista de zones:
<li><h:link outcome="pretty:zones">#{literalsAplicacio['Menu.Zona']}</h:link></li>
El scaffolding també genera dos plantilles per les vistes més habituals de l'aplicació dins d'un directori amb el nom de l'entitat:
/WebRoot/views/zona/zonaDetall.xhtml
/WebRoot/views/zona/zonaLlista.xhtml
Cal tenir en compte que el layout està basat en Bootstrap 3 [18].
El codi generat per la vista és el següent:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" xmlns:o="http://omnifaces.org/ui" xmlns:of="http://omnifaces.org/functions" xmlns:diba="http://java.sun.com/jsf/composite/diba/components" template="/WEB-INF/templates/layout.xhtml"> <ui:define name="content"> <div class="page-header c-diba"> <h2>#{literalsAplicacio['Zona.IndexTitle']} <small>#{literalsCore['Helper.Index']}</small> </h1> </div> <h:form id="zona_llistat_form" styleClass="form-horizontal"> <!-- http://stackoverflow.com/questions/4573190/jsf-primefaces-update-attribu... [19] --> <h:panelGroup id="zona_search_fields" layout="block" styleClass="well well-sm"> <!-- FIXME: camps de cerca --> <div class="form-group"> <!-- Exemple <o:outputLabel for="descripcio" styleClass="col-sm-1 control-label" value="#{literalsAplicacio['Zona.Descripcio']}" /> <div class="col-sm-3"> <h:inputText styleClass="form-control" id="descripcio" value="#{zonaSearchBean.descripcio}" /> </div> --> </div> <diba:llistaCercaButtons search="#{zonaBB.dataModel.search}" reset="#{zonaBB.dataModel.reset}" /> </h:panelGroup> <div class="table-responsive well well-sm"> <p:dataTable id="zona_taula" paginatorPosition="top" var="element" tableStyleClass="table table-striped table-hover" tableStyle="width: 100%;" value="#{zonaBB.dataModel.lazyDataModel}" lazy="true" paginator="true" rows="20" paginatorTemplate="{RowsPerPageDropdown} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {CurrentPageReport} {New}" currentPageReportTemplate="#{literalsCore['Helper.Table.PageTemplate']}" emptyMessage="#{literalsCore['Helper.Table.Empty']}" first="#{zonaBB.dataModel.first}"> <f:facet name="{New}"> <span class="pull-right"> <h:link styleClass="btn btn-default" style="text-align: right;" role="button" outcome="pretty:newZona" > <i class="fa fa-plus" aria-hidden="true"></i> #{literalsCore['Helper.New']} </h:link> </span> </f:facet> <!-- FIXME: columnes de la taula --> <!-- Exemple <p:column headerText="#{literalsAplicacio['Zona.Descripcio']}"> <h:link outcome="pretty:viewZona" value="#{element.id}"> <f:param name="id" value="#{element.id}"></f:param> </h:link> </p:column> --> </p:dataTable> </div> </h:form> </ui:define> </ui:composition>
Aspectes a destacar:
<diba:llistaCercaButtons search="#{zonaBB.dataModel.search}" reset="#{zonaBB.dataModel.reset}" />
value="#{zonaBB.dataModel.lazyDataModel}"
<f:facet name="{New}"> <span class="pull-right"> <h:link styleClass="btn btn-default" style="text-align: right;" role="button" outcome="pretty:newZona" > <i class="fa fa-plus" aria-hidden="true"></i> #{literalsCore['Helper.New']} </h:link> </span> </f:facet>
Les modificacions a fer són:
<div class="form-group"> <o:outputLabel for="nom" styleClass="col-sm-1 control-label" value="#{literalsAplicacio['Zona.Nom']}" /> <div class="col-sm-3"> <h:inputText styleClass="form-control" id="nom" value="#{zonaSearchBean.name}" /> </div> </div>
Indicar a la taula la columna per defecte afegint la propietat següent a p:dataTable: sortBy="#{element.famZonaNom}" <p:column headerText="#{literalsAplicacio['Zona.Nom']}" sortBy="#{element.famZonaNom}"> <h:link outcome="pretty:viewZona" value="#{element.famZonaNom}"> <f:param name="id" value="#{element.id}"></f:param> </h:link> </p:column>
I aquest entitat no necessita més canvis a la llista.
El codi generat per la vista és el següent:
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:p="http://primefaces.org/ui" xmlns:o="http://omnifaces.org/ui" xmlns:of="http://omnifaces.org/functions" xmlns:diba="http://java.sun.com/jsf/composite/diba/components" template="/WEB-INF/templates/layout.xhtml"> <ui:define name="content"> <o:importConstants type="cat.diba.jee.fam.view.bb.ZonaBB" /> <div class="page-header c-diba"> <h2>#{literalsAplicacio['Zona.EditTitle']} <small>#{literalsCore['Helper.Edit']}</small> </h2> </div> <h:form styleClass="form-horizontal" id="zona_detail_form"> <div class="form-edit"> <!-- FIXME: camps del formulari --> <div class="form-group"> </div> </div> <diba:detailButtons deleteAction="#{zonaBB.delete}" saveOrUpdateAction="#{zonaBB.saveOrUpdate}" cancelLink="pretty:zones" isNew="#{zonaBB.detailEntity.id eq null}" /> </h:form> <o:highlight styleClass="has-error" focus="true" /> </ui:define> </ui:composition>
Aspectes a destacar:
<diba:detailButtons deleteAction="#{zonaBB.delete}" saveOrUpdateAction="#{zonaBB.saveOrUpdate}" cancelLink="pretty:zones" isNew="#{zonaBB.detailEntity.id eq null}" />
<o:highlight styleClass="has-error" focus="true" />
La plantilla generada no incorpora el contingut del formulari amb els camps, per tant si afegim els camps al formulari el resultat és:
<div class="form-edit"> <div class="form-group"> <o:outputLabel for="zonaNom" value="#{literalsAplicacio['Zona.Nom']}" styleClass="control-label col-sm-1" /> <div class="col-sm-3"> <h:inputText value="#{zonaBB.detailEntity.famZonaNom}" id="zonaNom" styleClass="form-control"> <f:validateRequired /> </h:inputText> </div> </div> </div>
El punt més important és que l'atribut for de l'outputLabel coincideix amb l'id de l'InputText i llavors queda lligat el missatge d'error que és obligatori. La documentació original d'OmniFaces [8] és molt clara i amb exemples: <o:outputLabel> [10] <o:highlight> [9]
Tenir instal·lada la versió 3.0 de l'entorn corporatiu. En funció del tipus de projecte caldrà tenir accés a l'esquema de Oracle.
L'entorn corporatiu té definit un data source d'exemple
Marca el desplegable d'executar i selecciona l'opció 'Nou projecte Web Service'.
Demanarà un codi de projecte, introduir-lo.
Des del menu File.
Tipus de projecte a importar
Selecció del projecte
Per defecte, el projecte creat té definits uns valors per poder verificar el seu funcionament a l'entorn corporatiu proporcionat. Es recomana realitzar la verificació del projecte i a continuació modificar els següents paràmetres amb els valors específics del projecte:
Engegar el servidor Weblogic local
Executar la opció de desplegament local seleccionant préviament el projecte a l'explorador
Verificar la consola per confirmar que s'ha desplegat correctament:
I, finalment, obrir el navegador per comprobar que el servei RESTful d'exemple que incorpora el projecte funciona:
http://localhost:7001/ws_utc/begin.do?wsdlUrl=http://172.17.0.108:7001/apiSoap/ZonaWSService?WSDL [21]
Tenir instal·lada la versió 4.0 de l'entorn corporatiu. En funció del tipus de projecte caldrà tenir accés a l'esquema de Oracle.
L'entorn corporatiu té definit un data source d'exemple
Marcar el desplegable d'executar i escollir la opció 'Nou projecte Restful'
Demanarà un codi de projecte, introduir-lo.
Des del menu File.
Tipus de projecte a importar
Selecció del projecte
Per defecte, el projecte creat té definits uns valors per poder verificar el seu funcionament a l'entorn corporatiu proporcionat. Es recomana realitzar la verificació del projecte i a continuació modificar els següents paràmetres amb els valors específics del projecte:
Engegar el servidor Weblogic local
Executar la opció de desplegament local seleccionant préviament el projecte a l'explorador
Verificar la consola per confirmar que s'ha desplegat correctament:
I, finalment, obrir el navegador per comprobar que el servei RESTful d'exemple que incorpora el projecte funciona:
http://localhost:7001/demoRestful/rest/v1/zones [22]
La resposta és una excepció perquè és la resposta per defecte de l'exemple. L'objectiu és la verificació del correcte desplegament del servei RESTful.
Tenir instal·lada la versió 4.0 del framework l'entorn corporatiu.
Si en el nou framework, el model del FAM - Backoffice mostra un missatge de error en el pom.xml en un plugin de maven
Feu el següent:
a) Seleccionar des de el fitxer “pom.xml” la pestanya “Overview” i a continuació clicar en el “Overview Pluguin execution not covered by licecycle configuration org.apache.maven.plugin (Click for details)” que apareix en vermell.
b) Apareix una finestra amb 3 opcions. Agafa l’última “Mark goal test-jdkinternals as ingoned in Eclipse build in ....”
L’error desapareixerà.
a) Selecciona el projecte “FAM Backoffice” i seleccionant el buttó de RUN executar “Deploy Local” del maven.
b) A continuació arrancar el servidor "Oracle Weblogic Server 12c".
c) La URL per executar el model de FAM en local és la següent: http://localhost:7001/fambo/vusSimulacio.jsp [23]
i apareixerà aquesta pantalla:
Donant-li al buttó “Entrar” s'entrarà a l’aplicació:
Adjunt | Mida |
---|---|
imatgefambackoffice_1.png [24] | 119.43 KB |
imatgefambackoffice_2.png [25] | 96.98 KB |
imatgefambackoffice_3.png [26] | 44.6 KB |
imatgefambackoffice_4.png [27] | 83.71 KB |
imatgefambackoffice_5.png [28] | 35.76 KB |
imatgefambackoffice_6.png [29] | 121.92 KB |
El projecte FAM, i l'arquetip de projectes Web basat en ell, està configurat per generar el nom del war amb la revision del projecte SVN i incloure aquest numero en un fitxer de propietats que es pot consulta des de l'aplicació a la finestra d'informació de l'entorn.
Si el projecte fa servir GIT, cal modificar alguns arxius per mostrar la revision i la branca correctament.
Cal instal·lar el client Git de windows i verificar que està al PATH del sistema la ruta <directori d'instal·lació>/git/cmd
https://gitforwindows.org/
[30]
Edita l'arxiu resources/literals/core.properties
i afegeix dos entrades:
Aplicacio.GIT.Branch = Branca Aplicacio.GIT.Revision = GIT revision
Edita l'arxiu WebRoot\views\ajuda\infoAplicacio.xhtml.
Elimina la informació de SVN
<tr> <th scope="row">#{literalsCore['Aplicacio.SVN.Title']}</th> <td>#{literalsCore['Aplicacio.SVN.Revision']}</td> </tr>
I afegeix la informació de GIT
<tr>
<th scope="row">#{literalsCore['Aplicacio.GIT.Branch']}</th>
<td>#{applicationBB.gitBranch}</td>
</tr>
<tr>
<th scope="row">#{literalsCore['Aplicacio.GIT.Revision']}</th>
<td>#{applicationBB.gitRevision}</td>
</tr>
Afegeix a la classe ApplicationBB
el següent codi
import java.util.Properties; import java.io.IOException; private String gitRevision = null; private String gitBranch = null; public String getGitRevision() { if (gitRevision == null) { try { Properties prop = new Properties(); prop.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("build.properties")); gitRevision = prop.getProperty("revision"); gitBranch = prop.getProperty("branch"); } catch (IOException e) { LOG.error("sense propietats del build.properties", e); } } return gitRevision; } public String getGitBranch() { if (gitBranch == null) { try { Properties prop = new Properties(); prop.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("build.properties")); gitRevision = prop.getProperty("revision"); gitBranch = prop.getProperty("branch"); } catch (IOException e) { LOG.error("sense propietats del build.properties", e); } } return gitBranch; }
Edita l'arxiu pom.xml i afegeix la següent informació:
SCM del git desprès de les propietats
<scm> <url>https://codifont.diba.es</url> <connection>scm:git:https://codifont.diba.es/CODI_PROJECTE/CODI_PROJECTEgit</connection> <developerConnection>scm:git:https://codifont.diba.es/CODI_PROJECTE/CODI_PROJECTE.git</developerConnection> </scm>
Afegeix la part en negreta al plugin del build que genera el war
<plugin> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <webappDirectory>${basedir}/WebRoot</webappDirectory> <warSourceDirectory>${basedir}/WebRoot</warSourceDirectory> <failOnMissingWebXml>false</failOnMissingWebXml> <!-- TODO revisar per que excloure config --> <warSourceExcludes>**/.svn/**,**/config/**,filters/**</warSourceExcludes> <!-- release peu --> <nonFilteredFileExtensions> <!-- default value contains jpg,jpeg,gif,bmp,png --> <nonFilteredFileExtension>pdf</nonFilteredFileExtension> <nonFilteredFileExtension>png</nonFilteredFileExtension> <nonFilteredFileExtension>css</nonFilteredFileExtension> <nonFilteredFileExtension>bmp</nonFilteredFileExtension> <nonFilteredFileExtension>jpg</nonFilteredFileExtension> <nonFilteredFileExtension>jpeg</nonFilteredFileExtension> <nonFilteredFileExtension>gif</nonFilteredFileExtension> </nonFilteredFileExtensions> <archive> <manifest> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> </manifest> <manifestEntries> <Implementation-Branch>${scmBranch}</Implementation-Branch> <Implementation-Build>${buildNumber}</Implementation-Build> </manifestEntries> </archive> </configuration> </plugin>
Afegeix dins del build, desprès del plugin de construcció del war, aquest plugin
<!-- GIT revision --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>buildnumber-maven-plugin</artifactId> <version>1.4</version> <executions> <execution> <id>create</id> <phase>validate</phase> <goals> <goal>create</goal> </goals> </execution> <execution> <id>metadata</id> <phase>generate-resources</phase> <goals> <goal>create-metadata</goal> </goals> <configuration> <attach>true</attach> <!--make it available for jar/war classpath resource --> <addOutputDirectoryToResources>true</addOutputDirectoryToResources> <properties> <branch>${scmBranch}</branch> </properties> </configuration> </execution> </executions> <configuration> <shortRevisionLength>8</shortRevisionLength> <doCheck>false</doCheck> <doCreate>false</doCreate> </configuration> </plugin>
Modifica el patró del nom a cadascun dels profile segons l'exemple del profile dev:
<finalName>${project.artifactId}-${project.version}-dev-r${svnProperties.revision}${svnProperties.specialStatus}</finalName> per <finalName>${project.artifactId}-${project.version}-dev-r${buildNumber}</finalName>
Pels projectes FAM3 que hagin migrat el repositori de SVN a GIT, les modificacions difereixen en alguns punts.
La configuració del pom és igual que la descrita a l'apartat Configuració. La diferència està en l'apartat Modificacions. Per adaptar aquests projectes només caldrà modificar els següents dos literals del fitxer literalsCore.properties amb aquests valors.
EntornRevisionApp = GIT revision appSvnRevision = ${buildNumber}
No cal fer cap modificació en la vista ni en el backing bean.
Enllaços:
[1] https://comunitatdstsc.diba.cat/wiki/fam-documentacio
[2] https://comunitatdstsc.diba.cat/wiki/fam-30-creacio-dun-manteniment-simple
[3] https://comunitatdstsc.diba.cat/wiki/FAM%203.0%20-%20Scaffolding
[4] https://comunitatdstsc.diba.cat/wiki/fam-generacio-del-model-de-dades
[5] http://www.querydsl.com/
[6] https://comunitatdstsc.diba.cat/wiki/FAM%203.0%20-%20Navegacio
[7] https://github.com/gary-rowe/SimpleScaffolding
[8] http://showcase.omnifaces.org/
[9] http://showcase.omnifaces.org/components/highlight
[10] http://showcase.omnifaces.org/components/outputLabel
[11] http://showcase.omnifaces.org/validators/validateMultiple
[12] http://www.primefaces.org/showcase/index.xhtml
[13] http://stackoverflow.com/questions/29490141/validation-disabled-pinputtext-primefaces
[14] http://www.ocpsoft.org/prettyfaces/
[15] https://comunitatdstsc.diba.cat/wiki/fam-30-scaffolding
[16] http://www.primefaces.org/showcase/ui/data/datatable/basic.xhtml
[17] https://comunitatdstsc.diba.cat/wiki/fam-30-navegacio
[18] http://getbootstrap.com/
[19] http://stackoverflow.com/questions/4573190/jsf-primefaces-update-attribute-does-not-update-component#4574266
[20] https://comunitatdstsc.diba.cat/wiki/fam-30-validacio
[21] http://localhost:7001/ws_utc/begin.do?wsdlUrl=http://172.17.0.108:7001/apiSoap/ZonaWSService?WSDL
[22] http://localhost:7001/demoRestful/rest/v1/zones
[23] http://localhost:7001/fambo/vusSimulacio.jsp
[24] https://comunitatdstsc.diba.cat/sites/comunitatdstsc.diba.cat/files/imatgefambackoffice_1_0.png
[25] https://comunitatdstsc.diba.cat/sites/comunitatdstsc.diba.cat/files/imatgefambackoffice_2_0.png
[26] https://comunitatdstsc.diba.cat/sites/comunitatdstsc.diba.cat/files/imatgefambackoffice_3_0.png
[27] https://comunitatdstsc.diba.cat/sites/comunitatdstsc.diba.cat/files/imatgefambackoffice_4.png
[28] https://comunitatdstsc.diba.cat/sites/comunitatdstsc.diba.cat/files/imatgefambackoffice_5_0.png
[29] https://comunitatdstsc.diba.cat/sites/comunitatdstsc.diba.cat/files/imatgefambackoffice_6.png
[30] https://gitforwindows.org/