View Javadoc
1   package fr.ifremer.dali.ui.swing.content.manage.program.strategies;
2   
3   /*
4    * #%L
5    * Dali :: UI
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2014 - 2015 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   *
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import com.google.common.collect.ArrayListMultimap;
27  import com.google.common.collect.Maps;
28  import com.google.common.collect.Multimap;
29  import fr.ifremer.dali.dto.DaliBeans;
30  import fr.ifremer.dali.dto.configuration.programStrategy.AppliedStrategyDTO;
31  import fr.ifremer.dali.dto.configuration.programStrategy.PmfmStrategyDTO;
32  import fr.ifremer.dali.service.DaliTechnicalException;
33  import fr.ifremer.dali.ui.swing.content.manage.program.ProgramsUI;
34  import fr.ifremer.dali.ui.swing.content.manage.program.programs.ProgramsTableRowModel;
35  import fr.ifremer.dali.ui.swing.util.AbstractDaliBeanUIModel;
36  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableModel;
37  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableUIHandler;
38  import fr.ifremer.quadrige3.ui.swing.table.SwingTable;
39  import jaxx.runtime.SwingUtil;
40  import org.jdesktop.swingx.table.TableColumnExt;
41  
42  import javax.swing.SortOrder;
43  import javax.swing.SwingWorker;
44  import java.util.Collection;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.concurrent.ExecutionException;
48  import java.util.stream.Collectors;
49  
50  import static org.nuiton.i18n.I18n.t;
51  
52  /**
53   * Controleur pour la zone des programmes.
54   */
55  public class StrategiesTableUIHandler extends AbstractDaliTableUIHandler<StrategiesTableRowModel, StrategiesTableUIModel, StrategiesTableUI> {
56  
57      private PmfmAndAppliedStrategyLoader pmfmAndAppliedStrategyLoader;
58  
59      /**
60       * <p>Constructor for StrategiesTableUIHandler.</p>
61       */
62      public StrategiesTableUIHandler() {
63          super(StrategiesTableRowModel.PROPERTY_NAME,
64                  StrategiesTableRowModel.PROPERTY_COMMENT);
65      }
66  
67      /**
68       * {@inheritDoc}
69       */
70      @Override
71      public AbstractDaliTableModel<StrategiesTableRowModel> getTableModel() {
72          return (StrategiesTableModel) getTable().getModel();
73      }
74  
75      /**
76       * {@inheritDoc}
77       */
78      @Override
79      public SwingTable getTable() {
80          return getUI().getStrategiesTable();
81      }
82  
83      /**
84       * {@inheritDoc}
85       */
86      @Override
87      public void beforeInit(final StrategiesTableUI ui) {
88          super.beforeInit(ui);
89  
90          // create model and register to the JAXX context
91          final StrategiesTableUIModel model = new StrategiesTableUIModel();
92          ui.setContextValue(model);
93      }
94  
95      /**
96       * {@inheritDoc}
97       */
98      @Override
99      public void afterInit(StrategiesTableUI ui) {
100 
101         // Initialiser l UI
102         initUI(ui);
103 
104         // Initialiser le tableau
105         initTable();
106         SwingUtil.setLayerUI(ui.getTableScrollPane(), ui.getTableBlockLayer());
107 
108         // Ajout des listeners
109         initListeners();
110 
111     }
112 
113     /**
114      * {@inheritDoc}
115      */
116     @Override
117     protected boolean isRowValid(final StrategiesTableRowModel row) {
118         row.getErrors().clear();
119         return (!row.isEditable() || super.isRowValid(row)) && isStrategyValid(row);
120     }
121 
122     private boolean isStrategyValid(StrategiesTableRowModel row) {
123 
124         // no need to check duplicates if number of rows < 2
125         if (getModel().getRowCount() >= 2) {
126 
127             for (StrategiesTableRowModel otherRow : getModel().getRows()) {
128                 if (row == otherRow) continue;
129                 if (row.getName().equals(otherRow.getName())) {
130                     // duplicate found
131                     DaliBeans.addError(row, t("dali.program.strategy.name.duplicates"), StrategiesTableRowModel.PROPERTY_NAME);
132                 }
133             }
134         }
135 
136         if (row.isAppliedStrategiesLoaded()) {
137 
138             Map<Integer, AppliedStrategyDTO> validAppliedStrategies = Maps.newHashMap();
139 
140             // check at least 1 applied strategy has date range
141             boolean isStrategyApplied = false;
142             if (!row.isAppliedStrategiesEmpty()) {
143                 for (AppliedStrategyDTO appliedStrategy : row.getAppliedStrategies()) {
144                     if (appliedStrategy.getStartDate() != null && appliedStrategy.getEndDate() != null) {
145                         isStrategyApplied = true;
146                         validAppliedStrategies.put(appliedStrategy.getId(), appliedStrategy);
147                     }
148                 }
149             }
150             if (!isStrategyApplied) {
151                 DaliBeans.addError(row, t("dali.programs.validation.error.noAppliedPeriod"), StrategiesTableRowModel.PROPERTY_NAME);
152             } else {
153 
154                 // check there is no crossing strategies
155                 Multimap<Integer, AppliedStrategyDTO> otherAppliedStrategiesByLocationId = ArrayListMultimap.create();
156                 for (StrategiesTableRowModel otherRow : getModel().getRows()) {
157 
158                     if (otherRow == row) continue;
159 
160                     // build map of other applied strategies not in this row
161                     for (AppliedStrategyDTO appliedStrategy : otherRow.getAppliedStrategies()) {
162                         if (validAppliedStrategies.containsKey(appliedStrategy.getId()) && appliedStrategy.getStartDate() != null && appliedStrategy.getEndDate() != null) {
163                             otherAppliedStrategiesByLocationId.put(appliedStrategy.getId(), appliedStrategy);
164                         }
165                     }
166                 }
167 
168                 // exploit map to found overlapping
169                 for (Integer appliedStrategyId : validAppliedStrategies.keySet()) {
170                     boolean overlappingFound = false;
171                     AppliedStrategyDTO appliedStrategy = validAppliedStrategies.get(appliedStrategyId);
172                     Collection<AppliedStrategyDTO> otherAppliedStrategies = otherAppliedStrategiesByLocationId.get(appliedStrategyId);
173                     for (AppliedStrategyDTO otherAppliedStrategy : otherAppliedStrategies) {
174 
175                         if (appliedStrategy.getStartDate().compareTo(otherAppliedStrategy.getEndDate()) <= 0
176                                 && otherAppliedStrategy.getStartDate().compareTo(appliedStrategy.getEndDate()) <= 0) {
177 
178                             // overlapping found
179                             overlappingFound = true;
180                             break;
181                         }
182 
183                     }
184 
185                     if (overlappingFound) {
186                         DaliBeans.addError(row, t("dali.programs.validation.error.overlappingPeriods"), StrategiesTableRowModel.PROPERTY_NAME);
187                         break;
188                     }
189                 }
190 
191                 // Check other applied period errors
192                 for (AppliedStrategyDTO appliedStrategy : row.getAppliedStrategies()) {
193                     if (!appliedStrategy.isErrorsEmpty()) {
194                         DaliBeans.addError(row, t("dali.programs.validation.error.appliedStrategies"), StrategiesTableRowModel.PROPERTY_NAME);
195                     }
196                 }
197             }
198         }
199 
200         // check pmfm integrity
201         if (row.isPmfmStrategiesLoaded()) {
202 
203             for (PmfmStrategyDTO pmfmStrategy : row.getPmfmStrategies()) {
204                 if (!pmfmStrategy.isSurvey() && !pmfmStrategy.isSampling()) {
205                     DaliBeans.addError(row, t("dali.program.tables.error"), StrategiesTableRowModel.PROPERTY_NAME);
206                 }
207 
208             }
209         }
210 
211         return row.isErrorsEmpty();
212     }
213 
214     /**
215      * {@inheritDoc}
216      */
217     @Override
218     protected void onRowModified(int rowIndex, StrategiesTableRowModel row, String propertyName, Integer propertyIndex, Object oldValue, Object newValue) {
219         saveToProgram();
220         super.onRowModified(rowIndex, row, propertyName, propertyIndex, oldValue, newValue);
221     }
222 
223     /**
224      * {@inheritDoc}
225      */
226     @Override
227     protected String[] getRowPropertiesToIgnore() {
228         return new String[]{StrategiesTableRowModel.PROPERTY_APPLIED_STRATEGIES,
229                 StrategiesTableRowModel.PROPERTY_APPLIED_STRATEGIES_LOADED,
230                 StrategiesTableRowModel.PROPERTY_PMFM_STRATEGIES,
231                 StrategiesTableRowModel.PROPERTY_PMFM_STRATEGIES_LOADED,
232                 StrategiesTableRowModel.PROPERTY_ERRORS};
233     }
234 
235     /**
236      * Load strategies from program
237      *
238      * @param selectedProgram a {@link fr.ifremer.dali.ui.swing.content.manage.program.programs.ProgramsTableRowModel} object.
239      */
240     public void load(ProgramsTableRowModel selectedProgram) {
241 
242         getModel().setProgramStatus(selectedProgram.getStatus());
243         getModel().setBeans(selectedProgram.getStrategies());
244         getModel().setEditable(selectedProgram.isEditable());
245         getModel().setLoading(false);
246         getModel().setLoaded(true);
247         recomputeRowsValidState();
248     }
249 
250     private ProgramsUI getProgramsUI() {
251         return getUI().getParentContainer(ProgramsUI.class);
252     }
253 
254     /**
255      * Initialisation des listeners.
256      */
257     private void initListeners() {
258 
259         // Listener sur le tableau
260         getModel().addPropertyChangeListener(StrategiesTableUIModel.PROPERTY_SINGLE_ROW_SELECTED, evt -> {
261 
262             // Get selected program and strategies
263             final ProgramsTableRowModel program = getModel().getParentModel().getProgramsUIModel().getSingleSelectedRow();
264             final StrategiesTableRowModel strategy = getModel().getSingleSelectedRow();
265 
266             // If only once selected
267             if (program != null && strategy != null) {
268 
269                 // load applied strategies
270                 if (pmfmAndAppliedStrategyLoader != null && !pmfmAndAppliedStrategyLoader.isDone()) {
271                     pmfmAndAppliedStrategyLoader.cancel(true);
272                 }
273                 getModel().getParentModel().getLocationsUIModel().setLoading(true);
274                 getModel().getParentModel().getPmfmsUIModel().setLoading(true);
275                 pmfmAndAppliedStrategyLoader = new PmfmAndAppliedStrategyLoader(program, strategy);
276                 pmfmAndAppliedStrategyLoader.execute();
277 
278             } else {
279 
280                 // Suppression des PSFMs
281                 getModel().getParentModel().getPmfmsUIModel().clear();
282             }
283         });
284 
285         getModel().addPropertyChangeListener(StrategiesTableUIModel.EVENT_SAVE_APPLIED_STRATEGIES, evt -> {
286             if (getModel().getSingleSelectedRow() != null) {
287                 getModel().getSingleSelectedRow().setAppliedStrategies(getModel().getParentModel().getLocationsUIModel().getBeans());
288                 getModel().getSingleSelectedRow().setAppliedStrategiesLoaded(true);
289             }
290         });
291         getModel().addPropertyChangeListener(StrategiesTableUIModel.EVENT_SAVE_PMFM_STRATEGIES, evt -> {
292             if (getModel().getSingleSelectedRow() != null) {
293                 getModel().getSingleSelectedRow().setPmfmStrategies(getModel().getParentModel().getPmfmsUIModel().getBeans());
294                 getModel().getSingleSelectedRow().setPmfmStrategiesLoaded(true);
295 
296                 // tell also applied strategies tables to refresh rows validity
297                 getModel().getParentModel().getLocationsUIModel().fireValidateRows();
298             }
299         });
300 
301         getModel().addPropertyChangeListener(StrategiesTableUIModel.EVENT_VALIDATE_ROWS, evt -> recomputeRowsValidState());
302         //noinspection unchecked
303         getModel().addPropertyChangeListener(StrategiesTableUIModel.EVENT_REMOVE_LOCATIONS, evt -> removeLocations((List<Integer>) evt.getNewValue()));
304 
305     }
306 
307     /**
308      * Initialisation de le tableau.
309      */
310     private void initTable() {
311 
312         final SwingTable table = getTable();
313 
314         // Colonne libelle
315         final TableColumnExt colonneLibelle = addColumn(
316                 StrategiesTableModel.NAME);
317         colonneLibelle.setSortable(true);
318 
319         // Colonne description
320         TableColumnExt colonneDescription = addCommentColumn(StrategiesTableModel.COMMENT);
321 
322         final StrategiesTableModel tableModel = new StrategiesTableModel(getTable().getColumnModel());
323         table.setModel(tableModel);
324 
325         // Les colonnes obligatoire sont toujours presentes
326         colonneLibelle.setHideable(false);
327         colonneDescription.setHideable(false);
328 
329         // Initialisation de la table
330         initTable(table);
331 
332         // Number rows visible
333         table.setVisibleRowCount(4);
334 
335         // Tri par defaut
336         table.setSortOrder(StrategiesTableModel.NAME, SortOrder.ASCENDING);
337     }
338 
339     /**
340      * <p>saveToProgram.</p>
341      */
342     private void saveToProgram() {
343 
344         if (getModel().isLoading()) return;
345 
346         // programs contains strategies, strategies contains pmfm
347         // save modification on master object
348         getModel().getParentModel().getProgramsUIModel().fireSaveStrategies();
349 
350         // force model modify
351         recomputeRowsValidState();
352         getModel().firePropertyChanged(AbstractDaliBeanUIModel.PROPERTY_MODIFY, null, true);
353 
354     }
355 
356     /**
357      * {@inheritDoc}
358      */
359     @Override
360     protected void onRowsAdded(List<StrategiesTableRowModel> addedRows) {
361         super.onRowsAdded(addedRows);
362 
363         if (addedRows.size() == 1) {
364             StrategiesTableRowModel row = addedRows.get(0);
365 
366             getModel().setModify(true);
367 
368             setFocusOnCell(row);
369 
370             saveToProgram();
371         }
372     }
373 
374     /**
375      * <p>removeStrategies.</p>
376      */
377     void removeStrategies() {
378 
379         if (getModel().getSelectedRows().isEmpty())
380             return;
381 
382         if (!checkExistingSurveysInsideRemovedStrategy()) {
383 
384             getContext().getDialogHelper().showErrorDialog(
385                     t("dali.program.location.period.surveyInsidePeriod"),
386                     t("dali.program.strategy.delete.titre")
387             );
388 
389             return;
390         }
391 
392         // Demande de confirmation avant la suppression
393         if (askBeforeDelete(t("dali.program.strategy.delete.titre"), t("dali.program.strategy.delete.message"))) {
394 
395             // Suppression des lignes
396             getModel().deleteSelectedRows();
397 
398             // Suppression des psfms du tableau
399             getModel().getParentModel().getPmfmsUIModel().clear();
400 
401             saveToProgram();
402 
403             // Reselect the program to refresh location/applied strategies table
404             getModel().getParentModel().getProgramsUIModel().fireReselect();
405 
406         }
407 
408     }
409 
410     void removeLocations(List<Integer> locationIds) {
411         getModel().getRows().stream().filter(strategy -> !strategy.isAppliedStrategiesEmpty())
412             .forEach(strategy -> strategy.getAppliedStrategies().removeIf(appliedStrategy -> locationIds.contains(appliedStrategy.getId())));
413     }
414 
415 
416     /**
417      * check if strategies to delete have data inside their applied periods (Mantis #48011)
418      *
419      * @return true if no data inside strategies, delete is allowed
420      */
421     private boolean checkExistingSurveysInsideRemovedStrategy() {
422 
423         getModel().setLoading(true);
424         try {
425 
426             List<AppliedStrategyDTO> appliedStrategies = getModel().getSelectedBeans().stream()
427                     .filter(strategyDTO -> strategyDTO.getId() != null)
428                     .flatMap(strategyDTO -> strategyDTO.getAppliedStrategies().stream())
429                     .filter(appliedStrategyDTO -> appliedStrategyDTO.getStartDate() != null && appliedStrategyDTO.getEndDate() != null)
430                     .collect(Collectors.toList());
431 
432             if (appliedStrategies.isEmpty()) return true;
433 
434             String programCode = getModel().getParentModel().getProgramsUIModel().getSingleSelectedRow().getCode();
435 
436             return appliedStrategies.stream().noneMatch(appliedStrategy ->
437                     getContext().getObservationService().countSurveysWithProgramLocationAndInsideDates(
438                             programCode,
439                             appliedStrategy.getAppliedStrategyId(),
440                             appliedStrategy.getId(),
441                             appliedStrategy.getStartDate(),
442                             appliedStrategy.getEndDate()
443                     ) > 0
444             );
445 
446         } finally {
447             getModel().setLoading(false);
448         }
449     }
450 
451     private class PmfmAndAppliedStrategyLoader extends SwingWorker<Object, Object> {
452 
453         private final ProgramsUI adminProgrammeUI = getProgramsUI();
454         private final ProgramsTableRowModel selectedProgram;
455         private final StrategiesTableRowModel selectedStrategy;
456 
457         PmfmAndAppliedStrategyLoader(ProgramsTableRowModel selectedProgram, StrategiesTableRowModel selectedStrategy) {
458             this.selectedProgram = selectedProgram;
459             this.selectedStrategy = selectedStrategy;
460         }
461 
462         @Override
463         protected Object doInBackground() {
464 
465             // Make sure applied strategies and pmfm strategies are loaded into the selected strategy
466             getContext().getProgramStrategyService().loadAppliedPeriodsAndPmfmStrategies(selectedProgram, selectedStrategy);
467 
468             return null;
469         }
470 
471         @Override
472         protected void done() {
473             if (isCancelled()) {
474                 return;
475             }
476             try {
477                 get();
478             } catch (InterruptedException | ExecutionException e) {
479                 throw new DaliTechnicalException(e.getMessage(), e);
480             }
481 
482             // loading locations
483             adminProgrammeUI.getLocationsTableUI().getHandler().loadAppliedStrategiesFromStrategy(selectedProgram, selectedStrategy);
484 
485             // loading pmfms
486             adminProgrammeUI.getPmfmsTableUI().getHandler().load(selectedProgram, selectedStrategy);
487 
488         }
489     }
490 
491 }