lumnik. / scenarios All scenarios · Français

Struts 1 without an API: the screen's CSV export, read as it comes out

Goal Read a Struts 1 application whose only output is its screen's CSV export, and be told when a customer return has waited for a part for more than 30 days.

A pump manufacturer handles its customer returns in a Struts 1 intranet written in 2007: Tomcat, JSP, DisplayTag. No API; the screen has a CSV export link, and a scheduled job fetches it every night.

rma-ouverts-2026-11-05.csv
RMA-26-0001,20/08/2026,Agri-Services Dubreuil,Vanne V-40,Surchauffe,Reçu,"89,90",77
RMA-26-0002,21/08/2026,"Léon, Fils & Cie",Surpresseur S-3,Casse au transport,Avoir à émettre,"2 310,00",76
RMA-26-0003,22/08/2026,Établissements Morvan,Pompe P-220,Erreur de référence,Ouvert,"1 234,50",75
RMA-26-0004,27/08/2026,Thermique de l'Ouest,Pompe P-220,Ne démarre pas,En attente pièce,"1 234,50",70
RMA-26-0005,28/08/2026,Agri-Services Dubreuil,Vanne V-40,"Client: \"pièce jamais reçue\", relancé",En expertise,"179,80",69
RMA-26-0006,28/08/2026,Quincaillerie Bérard,Carte électronique CE-12,Ne démarre pas,En attente pièce,"636,00",69
RMA-26-0007,29/08/2026,Côté Jardin Négoce,Carte électronique CE-12,Erreur de référence,En attente pièce,"318,00",68
RMA-26-0008,31/08/2026,Thermique de l'Ouest,Surpresseur S-3,"Bruit anormal, vibrations",En attente pièce,"4 620,00",66
RMA-26-0009,01/09/2026,Plomberie Garnier SARL,Carte électronique CE-12,Erreur de référence,Ouvert,"318,00",65
RMA-26-0010,03/09/2026,Hydro Saône,Carte électronique CE-12,Fuite au joint,En attente pièce,"636,00",63
RMA-26-0011,04/09/2026,"Léon, Fils & Cie",Pompe P-220,"Client: \"pièce jamais reçue\", relancé",Avoir à émettre,"1 234,50",62
RMA-26-0012,08/09/2026,Plomberie Garnier SARL,Carte électronique CE-12,"Client: \"pièce jamais reçue\", relancé",Avoir à émettre,"636,00",58
RMA-26-0013,11/09/2026,Thermique de l'Ouest,Surpresseur S-3,Surchauffe,En attente pièce,"2 310,00",55
RMA-26-0014,13/09/2026,"Léon, Fils & Cie","Moteur M-1,5 kW","Client: \"pièce jamais reçue\", relancé",En expertise,"612,75",53
RMA-26-0015,14/09/2026,Thermique de l'Ouest,Vanne V-40,Ne démarre pas,Ouvert,"89,90",52
RMA-26-0016,16/09/2026,Établissements Morvan,Vanne V-40,Fuite au joint,Reçu,"89,90",50
RMA-26-0017,17/09/2026,Plomberie Garnier SARL,Pressostat PR-6,"Bruit anormal, vibrations",En attente pièce,"46,20",49
RMA-26-0018,19/09/2026,Plomberie Garnier SARL,Pompe P-220,Casse au transport,Ouvert,"1 234,50",47
RMA-26-0019,19/09/2026,Agri-Services Dubreuil,Surpresseur S-3,Erreur de référence,Ouvert,"2 310,00",47
RMA-26-0020,21/09/2026,"Léon, Fils & Cie",Pompe P-220,Surchauffe,En expertise,"1 234,50",45

… 26 more lines

manifests/10-rma-export.yaml
apiVersion: connectors.lumnik.io/v1kind: Connectormetadata:  name: scenario-struts-a  connector_type: csv-file  scopes: [sav-a]spec:  transport:    kind: local    path: /tmp/lumnik-scenario/struts-displaytag-export/a/rma-ouverts.csv   # one file, rewritten every night: the table follows it  parser:    delimiter: ","    encoding: ISO-8859-1           # DisplayTag writes the response's charset: text/csv;charset=ISO-8859-1    has_header: false              # export.csv.include_header=false, DisplayTag's default    columns: [rma_no, opened, customer, item, reason, status, amount, age_days]    escape: "\\"                   # DisplayTag's CsvView escapes a quote with a backslash, never doubles it  after_process: none  mapping:    cell:      - parse-date: { column: opened, formats: ["dd/MM/yyyy"] }      - parse-number: { column: amount, decimal-separator: "," }   # "2 310,00": grouped by a no-break space      - parse-number: { column: age_days, integer: true }
manifests/20-waiting-for-a-part.yaml
apiVersion: apps.lumnik.io/v1kind: Processmetadata:  name: scenario-struts-waiting-aspec:  scope: sav-a  on: { event: data.row.changed, table: connector.t_scenario_struts_a }  when:    - status=En attente pièce    - age_days>30                    # the screen's own Ancienneté (j): the application computes it  emit:    type: decision.waiting-for-a-part-a    message: "{rma_no} ({customer}, {item}): waiting for a part for {age_days} days"    key: rma_no
rma-ouverts-2026-11-06.csv
RMA-26-0001,20/08/2026,Agri-Services Dubreuil,Vanne V-40,Surchauffe,Reçu,"89,90",78
RMA-26-0002,21/08/2026,"Léon, Fils & Cie",Surpresseur S-3,Casse au transport,Avoir à émettre,"2 310,00",77
RMA-26-0003,22/08/2026,Établissements Morvan,Pompe P-220,Erreur de référence,Ouvert,"1 234,50",76
RMA-26-0005,28/08/2026,Agri-Services Dubreuil,Vanne V-40,"Client: \"pièce jamais reçue\", relancé",En expertise,"179,80",70
RMA-26-0006,28/08/2026,Quincaillerie Bérard,Carte électronique CE-12,Ne démarre pas,En attente pièce,"636,00",70
RMA-26-0007,29/08/2026,Côté Jardin Négoce,Carte électronique CE-12,Erreur de référence,En attente pièce,"318,00",69
RMA-26-0008,31/08/2026,Thermique de l'Ouest,Surpresseur S-3,"Bruit anormal, vibrations",En attente pièce,"4 620,00",67
RMA-26-0009,01/09/2026,Plomberie Garnier SARL,Carte électronique CE-12,Erreur de référence,Ouvert,"318,00",66
RMA-26-0010,03/09/2026,Hydro Saône,Carte électronique CE-12,Fuite au joint,En attente pièce,"636,00",64
RMA-26-0011,04/09/2026,"Léon, Fils & Cie",Pompe P-220,"Client: \"pièce jamais reçue\", relancé",Avoir à émettre,"1 234,50",63
RMA-26-0012,08/09/2026,Plomberie Garnier SARL,Carte électronique CE-12,"Client: \"pièce jamais reçue\", relancé",Avoir à émettre,"636,00",59
RMA-26-0013,11/09/2026,Thermique de l'Ouest,Surpresseur S-3,Surchauffe,En attente pièce,"2 310,00",56
RMA-26-0014,13/09/2026,"Léon, Fils & Cie","Moteur M-1,5 kW","Client: \"pièce jamais reçue\", relancé",En expertise,"612,75",54
RMA-26-0015,14/09/2026,Thermique de l'Ouest,Vanne V-40,Ne démarre pas,Ouvert,"89,90",53
RMA-26-0016,16/09/2026,Établissements Morvan,Vanne V-40,Fuite au joint,Reçu,"89,90",51
RMA-26-0017,17/09/2026,Plomberie Garnier SARL,Pressostat PR-6,"Bruit anormal, vibrations",En attente pièce,"46,20",50
RMA-26-0018,19/09/2026,Plomberie Garnier SARL,Pompe P-220,Casse au transport,Ouvert,"1 234,50",48
RMA-26-0020,21/09/2026,"Léon, Fils & Cie",Pompe P-220,Surchauffe,En expertise,"1 234,50",46
RMA-26-0021,23/09/2026,Hydro Saône,Surpresseur S-3,"Bruit anormal, vibrations",Reçu,"2 310,00",44
RMA-26-0022,24/09/2026,Hydro Saône,Pressostat PR-6,Surchauffe,En attente pièce,"46,20",43

… 25 more lines

Limits

  • The run says "1 row(s) removed": that is how much the table shrank. The lines have no key; three returns closed and two opened make 1.
  • The age changes every night, so every line is rewritten: that is how the rule, declared after the first read, sees the seven returns. What becomes of the decision of a return still waiting on the third night was not replayed.
  • The export link is called without a session. An application that requires a login for its screen needs a session cookie: not replayed here.
  • The cron job is played by the scenario's script, not by a crontab.
  • Returns, customers and amounts synthetic; Struts 1.3.10, DisplayTag 1.2 and Tomcat 9 are real.

After-sales · Struts 1

The customer returns live in a 2007 intranet.

Struts 1, DisplayTag, Tomcat. No API, one "Export CSV" link.

?6578706f7274=1&d-16516-e=1

The problem

The hub read 5 lines out of 46.

  • DisplayTag writes \" inside a quote instead of doubling it.
  • And 2 310,00 is grouped by a no-break space: the amount was lost.

What is not changed

The application stays as it is.

  • No line of Java, no JSP touched.
  • The export is the screen's own, called every night.

What had to be written

escape: "\\"
encoding: ISO-8859-1

Two lines in the connector.

  • The reason keeps its quotes.
  • One file: the table follows it.

What was fixed

parser.escape exists (#1877).

  • parse-number strips the no-break space (#1878).
  • 46 returns read, 7 decisions the next day.

Try it

The scenario replays locally.

Every step is published with its output.

This scenario runs on lumnik Open, free and open source.

Try lumnik Open →