Architettura tecnica
Stack, layering, packaging, configurazione Spring e sicurezza
Stack
| Layer | Tecnologia | Versione | Note |
|---|---|---|---|
| Application Server | Apache Tomcat | 7.0.106 | Tomcat 7 = Servlet 3.0 / JSP 2.2 / EL 2.2. Richiede Java 8 max. |
| Framework MVC | Spring MVC | 4.1.x | Annotation-driven (@Controller, @RequestMapping). Zero XML config per i controller. |
| Security | Spring Security | 3.2.x | Form-login, ShaPasswordEncoder, JDBC user service. |
| ORM | Hibernate | 4.3.x | Annotation JPA. Fetch LAZY di default, JOIN esplicito nei DAO. |
| Database | MySQL | 5.7 | Charset latin1 (eredità applicativa). Da convertire a utf8mb4 in migrazione. |
| View | JSP + JSTL + Bootstrap 3 | — | Form Spring (<form:form>). Datepicker Bootstrap, dropdown AJAX dipendenti via jQuery. |
| Stampa Excel | Apache POI | 3.x | Endpoint /semiorto-report/report in WAR separato. |
| Stampa DOCX | docx4j | — | Template .docx con placeholder $$_NOME_$$. |
| Build | WAR exploded | — | Nessun Maven/Gradle nel deploy: classi compilate + .java sorgenti lasciati a fianco. |
Deployment topology
┌─ Client browser ───────────────────────────────────────────────┐
│ Bootstrap 3 + jQuery (vanilla, nessun framework SPA) │
└──────────────────────┬─────────────────────────────────────────┘
│ HTTP(S), form POST + AJAX GET JSON
▼
┌─ nginx (front door) ───────────────────────────────────────────┐
│ location ^~ /germinabilita-old/ → proxy_pass 127.0.0.1:8085 │
│ sub_filter riscrive path assoluti /assets/**, /j_spring_** │
│ proxy_redirect http://127.0.0.1:8085/ → /germinabilita-old/ │
└──────────────────────┬─────────────────────────────────────────┘
│
▼
┌─ Tomcat 7.0.106 (container Docker) ────────────────────────────┐
│ webapps/ROOT/ │
│ WEB-INF/ │
│ web.xml spring-security.xml mvc-dispatcher-servlet.xml │
│ classes/it/dss/ │
│ controller/ · service/ · dao/ · model/vo/ │
│ view/BO/ · view/validator/ │
│ pages/ (JSP) lib/ (49 JAR) │
│ TEMPLATES_DIRECTORY/ (docx template — vuoto nel nostro) │
└──────────────────────┬─────────────────────────────────────────┘
│ JDBC (Hibernate)
▼
┌─ MySQL 5.7 (container Docker, bind 127.0.0.1:3307) ────────────┐
│ database: semiorto (charset latin1, 25 tabelle) │
│ User app: semiorto/iron666M · User root: root/iron666M │
└────────────────────────────────────────────────────────────────┘
Layering applicativo
it.dss.controller)Ogni controller gestisce un dominio (Analisi, Lotto, Produttore, Specie, Varietà, Utente, Amministrazione). Configurazione solo via @RequestMapping, nessuna XML. Il controller:
- Riceve richieste HTTP e preleva parametri
- Popola il
ModelMapcon liste statiche (tipologieProva,tipologieVigore, ecc.) iniettate da Spring - Valida l'input via
@Valid+MyValidator - Delega al service
- Restituisce il nome della JSP o un
redirect:
it.dss.service[.impl])Interfaccia + implementazione. Annotazioni @Transactional(readOnly=…). Il service:
- Orchestra la logica di business
- Converte
VO(entità Hibernate) inBO(view objects di Spring) - Esegue calcoli (germinabilità media, generazione FT, flusso "Da Analizzare")
- Richiama uno o più DAO
it.dss.dao[.impl])Wrapper su Hibernate SessionFactory. Due stili:
- Hibernate Criteria API (
createCriteria + Restrictions) per le ricerche dinamiche - SQL grezzo (
createSQLQuery) per le query complesse come "Da Analizzare" che fanno join su 5 tabelle + subquery aggregata
VO = entità JPA (it.dss.model.vo): mappate 1:1 con tabelle DB.
BO = view objects (it.dss.view): servono per bind con form Spring, hanno campi aggiuntivi come dataArrivoS (stringa formattata) accanto a dataArrivo (Date).
La conversione VO↔BO avviene nei service (createBO / createVO).
Configurazione Spring
mvc-dispatcher-servlet.xml
Definisce le liste statiche iniettate via setter injection nei controller. Questo permette di modificare i valori senza ricompilare.
<bean class="it.dss.controller.AnalisiController">
<property name="tipologieProva">
<list>
<value>PETRI</value>
<value>TORBA</value>
<value>CONTENITORI ALVEOLATI</value>
</list>
</property>
<property name="tipologieVigore">
<list><value>SCARSO</value><value>MEDIO</value><value>OTTIMO</value></list>
</property>
...
</bean>
In TopSeed questo diventerà una tabella config_liste o ENUM MySQL, editabile dall'UI admin.
spring-security.xml
Un solo <http> block con 3 intercept-url:
<intercept-url pattern="/assets/**" access="permitAll" />
<intercept-url pattern="/accesso/**" access="!isAuthenticated()" />
<intercept-url pattern="/**" access="isAuthenticated()" />
Login form-based con redirect always a /. Logout URL /logout.
Nessun hasRole(). La filtrazione per ruolo NON è sugli URL ma solo sui menu visualizzati. Significa che un utente Magazzino che conosca l'URL /analisis/analisi/new può accedervi, anche se non vede il link nel menu. Da fissare nella riscrittura TopSeed.
Authentication provider (JDBC)
users-by-username-query:
SELECT username, password, attivo
FROM utente WHERE USERNAME=? AND attivo=1
authorities-by-username-query:
SELECT u.username, p.permesso
FROM utente u, permessi ps, permesso p
WHERE ps.id_utente = u.id
AND p.id = ps.id_permesso
AND u.username = ?
password-encoder: sha (Spring ShaPasswordEncoder, SHA-1)
Struttura packaging completa
ROOT/ ├── META-INF/ │ └── MANIFEST.MF ├── WEB-INF/ │ ├── web.xml — servlet + filter bootstrap │ ├── spring-security.xml — auth + authz │ ├── spring-beans.xml — datasource + sessionFactory Hibernate │ ├── mvc-dispatcher-servlet.xml — controller beans + liste statiche │ ├── classes/ │ │ ├── hibernate.cfg.xml — mappings VO │ │ └── it/dss/ │ │ ├── controller/ — 8 controller + DocxManipulator + StampaSchedaControllo │ │ ├── service/ — 7 service interface │ │ │ └── impl/ — 7 service implementation │ │ ├── dao/ — 6 DAO interface │ │ │ └── impl/ — 6 DAO implementation │ │ ├── model/ │ │ │ ├── vo/ — 16 entità JPA │ │ │ ├── dao/ — UtenteDao (eccezione) │ │ │ └── vo_generated/ — VO generati automaticamente │ │ ├── view/ — 20+ BO (view objects) │ │ │ └── validator/ — MyValidator, regole validazione │ │ └── exception/ — NotFoundException, DuplicateException, DeleteException │ ├── pages/ — 23 JSP │ │ ├── accesso/ — login, recupera password │ │ ├── analisi/ — cerca, lista, nuova, update │ │ ├── lotto/ — lista, update │ │ ├── produttore/ — lista, update │ │ ├── specie/ — lista, update (con matrice mesi×tipologie) │ │ ├── varieta/ — lista, update │ │ ├── utente/ — list, update, mieiDati, cambioPassword │ │ ├── template/ — head.jsp + menu.jsp (incluse ovunque) │ │ ├── assets/ — CSS, JS, img, less, fonts (static) │ │ ├── errore.jsp · vuota.jsp · index.jsp │ ├── messages/ — i18n (mai usato in produzione) │ ├── lib/ — 49 JAR (Spring, Hibernate, POI, docx4j, …) │ └── TEMPLATES_DIRECTORY/ — docx template stampa (VUOTO nel nostro dump)
Flusso request tipico
- Browser invia
GET /analisis/analisi/{id} - nginx proxy → Tomcat 8085
- Spring Security intercept → controlla cookie
JSESSIONID. Se non auth → redirect/accesso/login DispatcherServletroute aAnalisiController.singola(id, model)- Controller chiama
analisiService.analisi(id)che restituisceAnalisiBO(conconte,lotto,tecnicoeagerly loaded via Criteria JOIN) - Controller popola
ModelMapconbo,tipologieProva,tipologieCategoria, … - Restituisce nome JSP
analisi/update - JSP
update.jsprenderizza il form (739 righe HTML con Spring form tag) - Response HTML al browser
Build & deploy originali di Diego
Dal docker-compose.yml ricevuto:
services:
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: iron666M
MYSQL_DATABASE: semiorto
MYSQL_USER: semiorto
MYSQL_PASSWORD: iron666M
volumes:
- ./db:/docker-entrypoint-initdb.d
web:
image: tomcat:7.0.106
environment:
JDBC_URL: jdbc:mysql://db:3306/semiorto?connectTimeout=0&socketTimeout=0&autoReconnect=true
JDBC_USER: semiorto
JDBC_PASS: iron666M
ports:
- "80:8080"
volumes:
- ./tomcat/webapps:/usr/local/tomcat/webapps
Questa config è stata riprodotta sul server TopSeed (porta 8085 invece di 80 per evitare conflitti).