View Javadoc
1   package fr.ifremer.dali.ui.swing.content.manage.program.locations;
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.Lists;
27  import com.google.common.collect.Sets;
28  import fr.ifremer.dali.dto.DaliBeans;
29  import fr.ifremer.dali.dto.configuration.programStrategy.AppliedStrategyDTO;
30  import fr.ifremer.dali.dto.configuration.programStrategy.ProgStratDTO;
31  import fr.ifremer.dali.dto.enums.FilterTypeValues;
32  import fr.ifremer.dali.dto.referential.LocationDTO;
33  import fr.ifremer.dali.service.StatusFilter;
34  import fr.ifremer.dali.ui.swing.content.manage.filter.select.SelectFilterUI;
35  import fr.ifremer.dali.ui.swing.content.manage.program.locations.updatePeriod.UpdatePeriodUI;
36  import fr.ifremer.dali.ui.swing.content.manage.program.programs.ProgramsTableRowModel;
37  import fr.ifremer.dali.ui.swing.content.manage.program.strategies.StrategiesTableRowModel;
38  import fr.ifremer.dali.ui.swing.util.AbstractDaliBeanUIModel;
39  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableModel;
40  import fr.ifremer.dali.ui.swing.util.table.AbstractDaliTableUIHandler;
41  import fr.ifremer.quadrige3.core.dao.technical.Assert;
42  import fr.ifremer.quadrige3.ui.swing.ApplicationUIUtil;
43  import fr.ifremer.quadrige3.ui.swing.table.SwingTable;
44  import fr.ifremer.quadrige3.ui.swing.table.SwingTableColumnModel;
45  import jaxx.runtime.SwingUtil;
46  import org.apache.commons.logging.Log;
47  import org.apache.commons.logging.LogFactory;
48  import org.jdesktop.swingx.table.TableColumnExt;
49  
50  import javax.swing.JOptionPane;
51  import javax.swing.SortOrder;
52  import java.awt.Dimension;
53  import java.util.List;
54  import java.util.Set;
55  import java.util.stream.Collectors;
56  
57  import static org.nuiton.i18n.I18n.t;
58  
59  /**
60   * Strategy locations .
61   */
62  public class LocationsTableUIHandler extends AbstractDaliTableUIHandler<LocationsTableRowModel, LocationsTableUIModel, LocationsTableUI> {
63  
64      /**
65       * Logger.
66       */
67      private static final Log LOG = LogFactory.getLog(LocationsTableUIHandler.class);
68  
69      /**
70       * {@inheritDoc}
71       */
72      @Override
73      public AbstractDaliTableModel<LocationsTableRowModel> getTableModel() {
74          return (LocationsTableModel) getTable().getModel();
75      }
76  
77      /**
78       * {@inheritDoc}
79       */
80      @Override
81      public SwingTable getTable() {
82          return getUI().getLocationsTable();
83      }
84  
85      /**
86       * {@inheritDoc}
87       */
88      @Override
89      public void beforeInit(final LocationsTableUI ui) {
90          super.beforeInit(ui);
91  
92          // create model and register to the JAXX context
93          final LocationsTableUIModel model = new LocationsTableUIModel();
94          ui.setContextValue(model);
95      }
96  
97      /**
98       * {@inheritDoc}
99       */
100     @Override
101     public void afterInit(LocationsTableUI ui) {
102 
103         initUI(ui);
104 
105         // Initialize table without dates and departments columns
106         getModel().setPeriodsEnabled(false);
107         initTable();
108         SwingUtil.setLayerUI(ui.getTableScrollPane(), ui.getTableBlockLayer());
109 
110         initListeners();
111 
112         initActionComboBox(getUI().getEditCombobox());
113 
114     }
115 
116     /**
117      * Load all program's monitoring location
118      *
119      * @param selectedProgram a {@link fr.ifremer.dali.ui.swing.content.manage.program.programs.ProgramsTableRowModel} object.
120      */
121     public void loadMonitoringLocationsFromProgram(ProgramsTableRowModel selectedProgram) {
122 
123         // Initialize table without dates and departments columns
124         if (getModel().isPeriodsEnabled()) {
125             getModel().setPeriodsEnabled(false);
126             initTable();
127         }
128 
129         getModel().setSelectedProgram(selectedProgram);
130         getModel().setSelectedStrategy(null);
131 
132         // Load locations
133         load();
134     }
135 
136     /**
137      * Load all applied periods of the selected strategy
138      *
139      * @param selectedProgram  a {@link fr.ifremer.dali.ui.swing.content.manage.program.programs.ProgramsTableRowModel} object.
140      * @param selectedStrategy a {@link fr.ifremer.dali.ui.swing.content.manage.program.strategies.StrategiesTableRowModel} object.
141      */
142     public void loadAppliedStrategiesFromStrategy(ProgramsTableRowModel selectedProgram, StrategiesTableRowModel selectedStrategy) {
143 
144         // Initialize table with dates and departments columns
145         if (!getModel().isPeriodsEnabled()) {
146             getModel().setPeriodsEnabled(true);
147             initTable();
148         }
149 
150         getModel().setSelectedProgram(selectedProgram);
151         getModel().setSelectedStrategy(selectedStrategy);
152 
153         // Load appliedPeriods
154         load();
155     }
156 
157     private void load() {
158         try {
159             if (getModel().getSelectedProgram() == null) {
160                 return;
161             }
162 
163             getModel().setEditable(getModel().getSelectedProgram().isEditable());
164 
165             if (getModel().getSelectedStrategy() == null) {
166 
167                 // add all locations
168                 getModel().setBeans(DaliBeans.locationsToAppliedStrategyDTOs(getModel().getSelectedProgram().getLocations()));
169 
170             } else {
171 
172                 if (getUI().getStrategyFilterCheckbox().isSelected()) {
173 
174                     // add filtered locations
175                     getModel().setBeans(DaliBeans.filterNotEmptyAppliedPeriod(getModel().getSelectedStrategy().getAppliedStrategies()));
176 
177                 } else {
178 
179                     // add all locations
180                     getModel().setBeans(getModel().getSelectedStrategy().getAppliedStrategies());
181                 }
182             }
183 
184             if (getContext().getSelectedLocationId() != null) {
185                 selectRowById(getContext().getSelectedLocationId());
186             }
187 
188             recomputeRowsValidState();
189             getModel().setLoaded(true);
190 
191         } finally {
192             getModel().setLoading(false);
193         }
194     }
195 
196     /**
197      * <p>addLocations.</p>
198      */
199     void addLocations() {
200 
201         ProgramsTableRowModel program = getModel().getSelectedProgram(); // getProgramsUI().getProgramsTableUI().getModel().getSingleSelectedRow();
202         StrategiesTableRowModel strategy = getModel().getSelectedStrategy(); // getProgramsUI().getStrategiesTableUI().getModel().getSingleSelectedRow();
203 
204         Assert.notNull(program);
205 
206         SelectFilterUI dialog = new SelectFilterUI(getContext(), FilterTypeValues.LOCATION.getFilterTypeId());
207         dialog.setTitle(t("dali.program.location.new.dialog.title"));
208         List<LocationDTO> locations = program.getLocations().stream()
209                 .sorted(getDecorator(LocationDTO.class, null).getCurrentComparator())
210                 // set existing referential as read only to 'fix' these beans on double list
211                 .peek(location -> location.setReadOnly(true))
212                 .collect(Collectors.toList());
213         dialog.getModel().setSelectedElements(locations);
214 
215         // filter active location (for better performance on the first load without filter)
216 //        if (dialog.getFilterElementPanel().getComponentCount() == 1 && dialog.getFilterElementPanel().getComponent(0) instanceof FilterElementLocationUI) {
217 //            StatusDTO enableStatus = QuadrigeBeanFactory.newStatusDTO();
218 //            enableStatus.setCode(StatusCode.ENABLE.getValue());
219 //            ((FilterElementLocationUI) dialog.getFilterElementPanel().getComponent(0)).getHandler().getReferentialMenuUI().getModel().setStatus(enableStatus);
220 //        }
221 
222         openDialog(dialog, new Dimension(1024, 720));
223 
224         if (dialog.getModel().isValid()) {
225 
226             // get new location only
227             List<LocationDTO> newLocations = dialog.getModel().getSelectedElements().stream()
228                     .map(newLocation -> ((LocationDTO) newLocation))
229                     .filter(newLocation -> program.getLocations().stream().noneMatch(location -> location.equals(newLocation)))
230                     .collect(Collectors.toList());
231 
232             if (!newLocations.isEmpty()) {
233 
234                 // affect to program
235                 newLocations.forEach(program::addLocations);
236 
237                 // affect to strategy if selected
238                 if (strategy != null) {
239                     newLocations.stream()
240                             // filter by precaution but it should be absent too
241                             .filter(newLocation -> strategy.getAppliedStrategies().stream().noneMatch(appliedStrategy -> appliedStrategy.getId().equals(newLocation.getId())))
242                             .forEach(newLocation -> strategy.addAppliedStrategies(DaliBeans.locationToAppliedStrategyDTO(newLocation)));
243                 }
244 
245                 // re-affect lines
246                 List<AppliedStrategyDTO> beans = strategy != null ? strategy.getAppliedStrategies() : DaliBeans.locationsToAppliedStrategyDTOs(program.getLocations());
247                 getModel().setBeans(beans);
248 
249                 saveToStrategy();
250 
251                 // select new lines
252                 unselectAllRows();
253                 List<Integer> newLocIds = DaliBeans.collectIds(newLocations);
254                 selectRows(getModel().getRows().stream().filter(row -> newLocIds.contains(row.getId())).collect(Collectors.toList()));
255 
256             }
257         }
258     }
259 
260     /**
261      * Initialisation des listeners.
262      */
263     private void initListeners() {
264 
265         // Listener sur le tableau
266         getModel().addPropertyChangeListener(LocationsTableUIModel.PROPERTY_SINGLE_ROW_SELECTED, evt -> {
267 
268             // save selected location id
269             getContext().setSelectedLocationId(getModel().getSingleSelectedRow() == null ? null : getModel().getSingleSelectedRow().getId());
270 
271         });
272 
273         // Listener sur la checkbox
274         getUI().getStrategyFilterCheckbox().addItemListener(e -> {
275 
276             // Filter locations on strategy
277             load();
278         });
279 
280         getModel().addPropertyChangeListener(LocationsTableUIModel.EVENT_VALIDATE_ROWS, evt -> recomputeRowsValidState());
281 
282     }
283 
284     /**
285      * Initialisation de le tableau.
286      */
287     private void initTable() {
288 
289         // empty previous rows
290         getModel().setBeans(null);
291 
292         // Force a new column to avoid column duplication (Mantis #47426)
293         getTable().setColumnModel(new SwingTableColumnModel());
294 
295         // Column code
296         final TableColumnExt colCode = addColumn(LocationsTableModel.CODE);
297         colCode.setSortable(true);
298         colCode.setCellRenderer(newNumberCellRenderer(0));
299 
300         // Column name
301         final TableColumnExt colName = addColumn(
302                 LocationsTableModel.LABEL);
303         colName.setSortable(true);
304 
305         // Column comment
306         final TableColumnExt colComment = addColumn(
307                 LocationsTableModel.NAME);
308         colComment.setSortable(true);
309 
310         // Show following columns if a strategy is selected
311         if (getModel().isPeriodsEnabled()) {
312 
313             // Column date debut
314             final TableColumnExt colStartDate = addLocalDatePickerColumnToModel(
315                     LocationsTableModel.START_DATE, getConfig().getDateFormat());
316             colStartDate.setSortable(true);
317             colStartDate.setHideable(false);
318 
319             // Column date fin
320             final TableColumnExt colEndDate = addLocalDatePickerColumnToModel(
321                     LocationsTableModel.END_DATE, getConfig().getDateFormat());
322             colEndDate.setSortable(true);
323             colEndDate.setHideable(false);
324 
325             // Column sampling department
326             final TableColumnExt colSamplingDepartment = addFilterableComboDataColumnToModel(
327                     LocationsTableModel.SAMPLING_DEPARTMENT,
328                     getContext().getReferentialService().getDepartments(StatusFilter.ACTIVE),
329                     false);
330             colSamplingDepartment.setSortable(true);
331             colSamplingDepartment.setHideable(false);
332 
333             // Column analysis department
334             final TableColumnExt colAnalysisDepartment = addFilterableComboDataColumnToModel(
335                     LocationsTableModel.ANALYSIS_DEPARTMENT,
336                     getContext().getReferentialService().getDepartments(StatusFilter.ACTIVE),
337                     false);
338             colAnalysisDepartment.setSortable(true);
339             colAnalysisDepartment.setHideable(false);
340         }
341 
342         final LocationsTableModel tableModel = new LocationsTableModel(getTable().getColumnModel());
343         getTable().setModel(tableModel);
344 
345         tableModel.setNoneEditableCols();
346 
347         colCode.setHideable(false);
348         colName.setHideable(false);
349         colComment.setHideable(false);
350 
351         colCode.setEditable(false);
352         colName.setEditable(false);
353         colComment.setEditable(false);
354 
355         initTable(getTable());
356 
357         getTable().setVisibleRowCount(4);
358 
359         getTable().setSortOrder(LocationsTableModel.CODE, SortOrder.ASCENDING);
360 
361         if (getModel().isPeriodsEnabled()) {
362             overrideTabKeyActions(getTable(), true);
363         }
364     }
365 
366     /**
367      * {@inheritDoc}
368      */
369     @Override
370     protected String[] getRowPropertiesToIgnore() {
371         return new String[]{LocationsTableRowModel.PROPERTY_ERRORS};
372     }
373 
374     /**
375      * {@inheritDoc}
376      */
377     @Override
378     protected boolean isRowValid(LocationsTableRowModel row) {
379         row.getErrors().clear();
380         return super.isRowValid(row) && isDatesValid(row) && isAnalystValid(row);
381     }
382 
383     private boolean isAnalystValid(LocationsTableRowModel row) {
384 
385         // error if an analyst is set without pmfm strategies (Mantis #44055)
386         if (getModel().getSelectedStrategy() != null && getModel().getSelectedStrategy().isPmfmStrategiesEmpty() && row.getAnalysisDepartment() != null) {
387             DaliBeans.addError(row, t("dali.program.location.analysisDepartment.noPmfmStrategy"), LocationsTableRowModel.PROPERTY_ANALYSIS_DEPARTMENT);
388             return false;
389         }
390 
391         return true;
392     }
393 
394     private boolean isDatesValid(LocationsTableRowModel row) {
395 
396         // check first if start or end date has been cleared (Mantis #43965)
397         if (row.getStartDate() == null && row.getPreviousStartDate() != null
398                 && row.getEndDate() == null && row.getPreviousEndDate() != null) {
399             checkExistingSurveysInsideStrategy(row);
400         }
401 
402         // both start and end dates must be null or not null
403         if (row.getStartDate() == null && row.getEndDate() != null) {
404             DaliBeans.addError(row, t("dali.program.location.startDate.null"), LocationsTableRowModel.PROPERTY_START_DATE);
405 
406         } else if (row.getStartDate() != null && row.getEndDate() == null) {
407             DaliBeans.addError(row, t("dali.program.location.endDate.null"), LocationsTableRowModel.PROPERTY_END_DATE);
408 
409         } else if (row.getStartDate() != null && row.getEndDate() != null) {
410             // end date must be after start date
411             if (row.getStartDate().isAfter(row.getEndDate())) {
412                 DaliBeans.addError(row, t("dali.program.location.endDate.before"), LocationsTableRowModel.PROPERTY_END_DATE);
413             } else {
414 
415                 // if dates valid, check surveys outside dates
416                 checkExistingSurveysOutsideStrategy(row);
417             }
418         }
419 
420         return row.getErrors().isEmpty();
421     }
422 
423     private void checkExistingSurveysInsideStrategy(LocationsTableRowModel row) {
424         if (row.getAppliedStrategyId() != null && row.getId() != null) {
425             long surveysCount = getContext().getObservationService().countSurveysWithProgramLocationAndInsideDates(
426                     getModel().getSelectedProgram().getCode(),
427                     row.getAppliedStrategyId(),
428                     row.getId(), // = location id
429                     row.getPreviousStartDate(),
430                     row.getPreviousEndDate()
431             );
432             if (surveysCount > 0) {
433                 DaliBeans.addError(row, t("dali.program.location.period.surveyInsidePeriod"),
434                         LocationsTableRowModel.PROPERTY_START_DATE, LocationsTableRowModel.PROPERTY_END_DATE);
435             }
436         }
437     }
438 
439     private void checkExistingSurveysOutsideStrategy(LocationsTableRowModel row) {
440         if (row.getAppliedStrategyId() != null && row.getId() != null) {
441             long surveysCount = getContext().getObservationService().countSurveysWithProgramLocationAndOutsideDates(
442                     getModel().getSelectedProgram().getCode(),
443                     row.getAppliedStrategyId(),
444                     row.getId(), // = location id
445                     row.getStartDate(),
446                     row.getEndDate()
447             );
448             if (surveysCount > 0) {
449                 DaliBeans.addError(row, t("dali.program.location.period.surveyOutsidePeriod"),
450                         LocationsTableRowModel.PROPERTY_START_DATE, LocationsTableRowModel.PROPERTY_END_DATE);
451             }
452         }
453     }
454 
455     /**
456      * {@inheritDoc}
457      */
458     @Override
459     protected void onRowModified(int rowIndex, LocationsTableRowModel row, String propertyName, Integer propertyIndex, Object oldValue, Object newValue) {
460 
461         // Remove departments if no dates (Mantis #43233)
462         if (getModel().isPeriodsEnabled()) {
463             if (LocationsTableRowModel.PROPERTY_START_DATE.equals(propertyName) || LocationsTableRowModel.PROPERTY_END_DATE.equals(propertyName)) {
464                 if (row.getStartDate() == null && row.getEndDate() == null) {
465                     if (row.getSamplingDepartment() != null) row.setSamplingDepartment(null);
466                     if (row.getAnalysisDepartment() != null) row.setAnalysisDepartment(null);
467                 }
468             }
469         }
470 
471         saveToStrategy();
472         super.onRowModified(rowIndex, row, propertyName, propertyIndex, oldValue, newValue);
473     }
474 
475     /**
476      * <p>saveToStrategy.</p>
477      */
478     private void saveToStrategy() {
479 
480         if (getModel().isLoading()) return;
481 
482         // programs contains strategies, strategies contains pmfm
483         getModel().getParentModel().getStrategiesUIModel().fireSaveAppliedStrategies();
484         getModel().getParentModel().getProgramsUIModel().fireSaveStrategies();
485 
486         // force model modify and valid
487         recomputeRowsValidState();
488         getModel().firePropertyChanged(AbstractDaliBeanUIModel.PROPERTY_MODIFY, null, true);
489         getModel().firePropertyChanged(AbstractDaliBeanUIModel.PROPERTY_VALID, null, getModel().getRowCount() > 0);
490 
491     }
492 
493     /**
494      * {@inheritDoc}
495      */
496     @Override
497     public void recomputeRowsValidState() {
498         super.recomputeRowsValidState();
499 
500         // recompute strategy valid state
501         getModel().getParentModel().getStrategiesUIModel().fireValidateRows();
502     }
503 
504     /**
505      * <p>removeLocations.</p>
506      */
507     void removeLocations() {
508 
509         if (getModel().getSelectedRows().isEmpty()) {
510             LOG.warn("No location selected");
511             return;
512         }
513 
514         // the selected program
515         ProgramsTableRowModel program = getModel().getSelectedProgram();
516         List<Integer> locationIds = Lists.newArrayList();
517         for (final AppliedStrategyDTO row : getModel().getSelectedRows()) {
518             // list of location ids to delete
519             locationIds.add(row.getId());
520         }
521 
522         // check delete is for program locations or applied strategies
523         if (getModel().isPeriodsEnabled()) {
524 
525             // selected rows are applied strategies (with applied period)
526             if (!askBeforeDelete(t("dali.program.location.delete.titre"), t("dali.program.location.delete.appliedStrategy.message"))) {
527                 return;
528             }
529 
530         } else {
531 
532             // check usage of location and program in surveys
533             long surveysCount = getContext().getObservationService().countSurveysWithProgramAndLocations(program.getCode(), locationIds);
534             if (surveysCount > 0) {
535                 getContext().getDialogHelper().showErrorDialog(
536                         surveysCount == 1
537                                 ? t("dali.program.location.delete.usedInData")
538                                 : t("dali.program.location.delete.usedInData.many", surveysCount),
539                         t("dali.program.location.delete.titre"));
540                 return;
541             }
542 
543             // selected rows are program location
544             if (!askBeforeDelete(t("dali.program.location.delete.titre"), t("dali.program.location.delete.location.message"))) {
545                 return;
546             }
547 
548             // check usage of locations in strategies
549             Set<ProgStratDTO> usedStrategies = Sets.newHashSet();
550             for (Integer locationId : locationIds) {
551                 List<ProgStratDTO> appliedStrategies = getContext().getProgramStrategyService().getStrategyUsageByProgramAndLocationId(program.getCode(), locationId);
552                 usedStrategies.addAll(appliedStrategies);
553             }
554             if (!usedStrategies.isEmpty()) {
555                 List<String> locationNames = Lists.newArrayList();
556                 for (AppliedStrategyDTO row : getModel().getSelectedRows()) {
557                     locationNames.add(decorate(row));
558                 }
559                 List<String> strategyNames = DaliBeans.collectProperties(usedStrategies, ProgStratDTO.PROPERTY_NAME);
560 
561                 String topPart = t("dali.program.location.delete.location.formStrategy");
562                 String bottomMessage = t("dali.program.location.delete.location.fromStrategy.list", ApplicationUIUtil.formatHtmlList(strategyNames));
563 
564                 if (locationNames.size() > 8) {
565 
566                     if (getContext().getDialogHelper().showConfirmDialog(topPart, ApplicationUIUtil.getHtmlString(locationNames), bottomMessage, JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
567                         return;
568                     }
569 
570                 } else {
571 
572                     String message = String.format("%s\n%s\n%s", topPart, ApplicationUIUtil.formatHtmlList(locationNames), bottomMessage);
573                     if (getContext().getDialogHelper().showConfirmDialog(message, JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
574                         return;
575                     }
576                 }
577 
578             }
579         }
580 
581         // remove rows
582         getModel().deleteSelectedRows();
583 
584         if (!getModel().isPeriodsEnabled()) {
585             // remove from program model
586             removeFromModels(locationIds);
587         }
588 
589         saveToStrategy();
590 
591     }
592 
593     /**
594      * <p>removeFromModels.</p>
595      *
596      * @param locationIds a {@link java.util.List} object.
597      */
598     private void removeFromModels(List<Integer> locationIds) {
599 
600         // fix list of locations in program model
601         getModel().getParentModel().getProgramsUIModel().fireRemoveLocations(locationIds);
602 
603         // fix applied strategies lists in existing strategy models
604         getModel().getParentModel().getStrategiesUIModel().fireRemoveLocations(locationIds);
605     }
606 
607     /**
608      * <p>updatePeriod.</p>
609      */
610     void updatePeriod() {
611 
612         final UpdatePeriodUI dialogue = new UpdatePeriodUI(getContext());
613         dialogue.getModel().setTableModel(getModel());
614 
615         openDialog(dialogue, new Dimension(400, 220));
616 
617         getModel().setModify(true);
618         saveToStrategy();
619         getTable().repaint();
620     }
621 
622 }