Commit b6f1b554 authored by PECQUOT's avatar PECQUOT

[enh] Add photo synchronisation (Mantis #47995)

Signed-off-by: PECQUOT's avatarlp1ee9d <ludovic.pecquot@e-is.pro>
parent da51c382
## Sprint 76 - v3.7.3
- Pas de mise à jour de modèle
- Le répertoire OLD contenant les anciennes versions de l'application est supprimé.
Si des fichiers autres que ceux de l'historique sont présents dans ce répertoire, ils seront supprimés également !
## Sprint 75 - v3.7.2
- Aucune mise à jour de base de données
......
......@@ -171,7 +171,7 @@
<maven.compiler.debug>true</maven.compiler.debug>
<!-- Quadrige3 Core version -->
<quadrige3-core.version>3.5.4-SNAPSHOT</quadrige3-core.version> <!-- to use 3.5.3 -->
<quadrige3-core.version>3.5.3</quadrige3-core.version>
<!-- Last ReefDb launcher version -->
<launcherVersion>3.0.3</launcherVersion>
......
......@@ -320,6 +320,9 @@ public class ReefDbPhotoDaoImpl extends PhotoDaoImpl implements ReefDbPhotoDao {
result.setPhotoType(referentialDao.getPhotoTypeByCode(photoTypeCode));
}
// remote id for download purpose
result.setRemoteId((Integer) it.next());
return result;
}
......
......@@ -2220,7 +2220,7 @@
<![CDATA[
SELECT p.photoId, p.objectType.objectTypeCd, p.objectId,
p.photoNm, p.photoCm, p.photoDt, p.photoDirDc, p.photoLk,
p.photoType.photoTypeCd
p.photoType.photoTypeCd, p.remoteId
FROM PhotoImpl p
WHERE (p.objectType.objectTypeCd = :surveyObjectTypeCode AND p.objectId = :surveyId)
OR
......
......@@ -29,7 +29,6 @@ import fr.ifremer.quadrige3.synchro.service.client.SynchroClientService;
import fr.ifremer.quadrige3.synchro.service.client.vo.SynchroClientExportToFileResult;
import fr.ifremer.quadrige3.ui.swing.ApplicationUI;
import fr.ifremer.quadrige3.ui.swing.action.AbstractReloadCurrentScreenAction;
import fr.ifremer.quadrige3.ui.swing.model.ProgressionUIModel;
import fr.ifremer.quadrige3.ui.swing.synchro.SynchroUIContext;
import fr.ifremer.quadrige3.ui.swing.synchro.SynchroUIHandler;
import fr.ifremer.reefdb.dto.ReefDbBeans;
......@@ -100,7 +99,7 @@ public class ExportDataToFileAction extends AbstractReloadCurrentScreenAction {
// Ask user to select program to import
programCodes = getSynchroUIContext().getExportDataProgramCodes();
{
ProgramSelectUI programSelectUI = new ProgramSelectUI((ApplicationUI) getUI(), StatusFilter.ALL, programCodes, true);
ProgramSelectUI programSelectUI = new ProgramSelectUI((ApplicationUI) getUI(), null, StatusFilter.ALL, programCodes, true, false);
handler.openDialog(programSelectUI, t("reefdb.action.synchro.export.dataProgramCodes.title"), new Dimension(800, 400));
List<ProgramDTO> programs = programSelectUI.getModel().getSelectedPrograms();
......@@ -150,14 +149,14 @@ public class ExportDataToFileAction extends AbstractReloadCurrentScreenAction {
SynchroClientService synchroService = ReefDbServiceLocator.instance().getSynchroClientService();
int userId = SecurityContextHelper.getQuadrigeUserId();
ProgressionUIModel mainProgressionModel = createProgressionUIModel(100);
createProgressionUIModel(100);
// Save the UI context
getSynchroUIContext().setExportDataProgramCodes(programCodes);
getSynchroUIContext().saveExportContext();
// build temp database and export local to temp
getProgressionUIModel().setMessage(t("reefdb.synchro.progress.export"));
getProgressionUIModel().setMessage(t("quadrige3.synchro.progress.export"));
// export directory is set by the synchronization service
SynchroClientExportToFileResult exportResult = synchroService.exportToFile(userId,
......@@ -180,7 +179,7 @@ public class ExportDataToFileAction extends AbstractReloadCurrentScreenAction {
getProgressionUIModel().setTotal(100);
// restore action progression model
setProgressionUIModel(mainProgressionModel);
setProgressionUIModel(getProgressionUIModel());
// do NOT reload screen if no data
setSkipScreenReload(!hasData);
......
......@@ -138,7 +138,7 @@ public class ImportFromFileSynchroAction extends AbstractReloadCurrentScreenActi
// If last synchronization has failedAction
if (getSynchroUIContext().getStatus() == SynchroProgressionStatus.FAILED) {
getSynchroHandler().report(t("reefdb.synchro.report.failed"));
getSynchroHandler().report(t("quadrige3.synchro.report.failed"));
return;
}
......@@ -287,7 +287,7 @@ public class ImportFromFileSynchroAction extends AbstractReloadCurrentScreenActi
// build temp database and export local to temp
getProgressionUIModel().setTotal(100);
getProgressionUIModel().setMessage(t("reefdb.synchro.progress.import"));
getProgressionUIModel().setMessage(t("quadrige3.synchro.progress.import"));
SynchroRejectedRowResolver rejectResolver = new SynchroRejectedRowUIResolver(getContext().getDialogHelper(), true /*isImport*/);
// export directory is set by the synchronization service
......@@ -307,7 +307,7 @@ public class ImportFromFileSynchroAction extends AbstractReloadCurrentScreenActi
if (log.isInfoEnabled()) {
log.info("Reset all caches.");
}
getProgressionUIModel().setMessage(t("reefdb.synchro.progress.resetCache"));
getProgressionUIModel().setMessage(t("quadrige3.synchro.progress.resetCache"));
getContext().reloadDbCache(getProgressionUIModel());
}
......@@ -375,7 +375,7 @@ public class ImportFromFileSynchroAction extends AbstractReloadCurrentScreenActi
public void postFailedAction(Throwable error) {
super.postFailedAction(error);
getSynchroHandler().report(t("reefdb.synchro.report.failed"), true);
getSynchroHandler().report(t("quadrige3.synchro.report.failed"), true);
getSynchroUIContext().setStatus(SynchroProgressionStatus.FAILED);
log.error("Synchronization import from file failed");
......
......@@ -12,12 +12,12 @@ package fr.ifremer.reefdb.ui.swing.action;
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
......@@ -49,7 +49,7 @@ import static org.nuiton.i18n.I18n.t;
/**
* Main import synchro action
*
* <p>
* Created by Ludovic on 18/05/2015.
*/
public class ImportSynchroAction extends AbstractReefDbMainUIAction {
......@@ -58,8 +58,6 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
private boolean referentialOnly = false;
private boolean silentIfNoUpdate = false;
private boolean silentIfUpdate = false;
private boolean checkOnly = false;
private Set<String> programCodes;
/**
* <p>Constructor for ImportSynchroAction.</p>
......@@ -100,15 +98,9 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
this.silentIfUpdate = silentIfUpdate;
}
public void setCheckOnly(boolean checkOnly) {
this.checkOnly = checkOnly;
}
public void forceProgramCodes(Set<String> programCodes) {
this.programCodes = programCodes;
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public boolean prepareAction() throws Exception {
......@@ -126,7 +118,9 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
return super.prepareAction();
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public void doAction() throws Exception {
// test actual progression
......@@ -137,7 +131,7 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
// If last synchronization has failedAction
if (getSynchroUIContext().getStatus() == SynchroProgressionStatus.FAILED) {
getSynchroHandler().report(t("reefdb.synchro.report.failed"));
getSynchroHandler().report(t("quadrige3.synchro.report.failed"));
return;
}
......@@ -173,15 +167,11 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
if (isSynchronizationUsingServer
&& !getContext().getProgramStrategyService().hasRemoteAccessRightOnProgram(getContext().getAuthenticationInfo())) {
referentialOnly = true;
}
else {
if (CollectionUtils.isEmpty(programCodes)) {
} else {
// Ask for programs
// Ask for programs
Set<String> programCodes = getSynchroUIContext().getImportDataProgramCodes();
ProgramSelectUI programSelectUI = new ProgramSelectUI(getUI(), StatusFilter.NATIONAL_ACTIVE, programCodes);
ProgramSelectUI programSelectUI = new ProgramSelectUI(getUI(), null, StatusFilter.NATIONAL_ACTIVE, programCodes, false, true);
handler.openDialog(programSelectUI, t("reefdb.action.synchro.import.programCodes.title"), new Dimension(800, 400));
......@@ -195,16 +185,13 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
// Transform selected programs into a code list
else {
programCodes = Sets.newHashSet(ReefDbBeans.<String, ProgramDTO>collectProperties(programs, ProgramDTO.PROPERTY_CODE));
getSynchroUIContext().setImportDataProgramCodes(programCodes);
}
} else {
// apply provided program code directly
programCodes = Sets.newHashSet(ReefDbBeans.<String, ProgramDTO>collectProperties(programs, ProgramDTO.PROPERTY_CODE));
getSynchroUIContext().setImportDataProgramCodes(programCodes);
}
// get photo option and set it in context
getSynchroUIContext().setImportPhoto(programSelectUI.getModel().isEnablePhoto());
}
}
......@@ -230,12 +217,6 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
checkAction.setSilentIfNoUpdate(silentIfNoUpdate);
checkAction.setSilentIfUpdate(silentIfUpdate);
nextSynchroAction = checkAction;
// if check only, just call the synchro action, wait for it and terminate this action normally
if (checkOnly) {
nextSynchroAction.executeAndWait();
nextSynchroAction = null;
}
}
// run apply direct synchro action
......@@ -248,7 +229,9 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
}
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public void postSuccessAction() {
super.postSuccessAction();
......@@ -261,15 +244,15 @@ public class ImportSynchroAction extends AbstractReefDbMainUIAction {
}
/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
protected void releaseAction() {
super.releaseAction();
referentialOnly = false;
silentIfNoUpdate = false;
silentIfUpdate = false;
checkOnly = false;
nextSynchroAction = null;
programCodes = null;
}
}
......@@ -262,7 +262,7 @@ public class ShowAboutAction extends AbstractReefDbMainUIAction {
ApplicationInfo info = entry.getValue();
String oldVersion = info != null ? info.oldVersion : t("reefdb.about.update.app.undefined");
String newVersion = info != null ? info.newVersion : null;
String i18nKey = "reefdb.update." + appName.toLowerCase();
String i18nKey = "quadrige3.update." + appName.toLowerCase(); // keep quadrige3. i18n base
String appLabel = t(i18nKey);
if (LOG.isInfoEnabled()) {
......
package fr.ifremer.reefdb.ui.swing.content.observation.photo;
/*
* #%L
* Reef DB :: UI
* $Id:$
* $HeadURL:$
* %%
* Copyright (C) 2014 - 2015 Ifremer
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import fr.ifremer.quadrige3.core.service.http.HttpNotFoundException;
import fr.ifremer.reefdb.service.ReefDbBusinessException;
import fr.ifremer.reefdb.service.ReefDbServiceLocator;
import fr.ifremer.reefdb.ui.swing.action.AbstractReefDbAction;
import static org.nuiton.i18n.I18n.t;
/**
* Download action.
*/
public class DownloadAction extends AbstractReefDbAction<PhotosTabUIModel, PhotosTabUI, PhotosTabUIHandler> {
private boolean downloaded;
/**
* Constructor.
*
* @param handler Controller
*/
public DownloadAction(PhotosTabUIHandler handler) {
super(handler, false);
}
/**
* {@inheritDoc}
*/
@Override
public boolean prepareAction() throws Exception {
if (!super.prepareAction() || getModel().getSelectedRows().isEmpty()) {
return false;
}
createProgressionUIModel();
downloaded = false;
return getModel().getSingleSelectedRow().isFileDownloadable();
}
/**
* {@inheritDoc}
*/
@Override
public void doAction() throws Exception {
try {
downloaded = ReefDbServiceLocator.instance().getSynchroRestClientService().downloadPhoto(
getContext().getAuthenticationInfo(),
getModel().getSingleSelectedRow().getRemoteId(),
getModel().getSingleSelectedRow().getFullPath(),
getProgressionUIModel()
);
} catch (HttpNotFoundException e) {
throw new ReefDbBusinessException(t("reefdb.photo.download.notFound", getModel().getSingleSelectedRow().getName()));
}
}
/**
* {@inheritDoc}
*/
@Override
public void postSuccessAction() {
if (!downloaded)
getContext().getDialogHelper().showErrorDialog(t("reefdb.photo.download.error"));
getHandler().updatePhotoViewerContent();
getUI().invalidate();
getUI().repaint();
getUI().processDataBinding(PhotosTabUI.BINDING_DOWNLOAD_PHOTO_BUTTON_ENABLED);
super.postSuccessAction();
}
@Override
protected void releaseAction() {
downloaded = false;
super.releaseAction();
}
}
......@@ -23,12 +23,15 @@ package fr.ifremer.reefdb.ui.swing.content.observation.photo;
* #L%
*/
import fr.ifremer.quadrige3.core.dao.technical.Files;
import fr.ifremer.reefdb.dto.ReefDbBeans;
import fr.ifremer.reefdb.dto.data.photo.PhotoDTO;
import fr.ifremer.reefdb.service.ReefDbBusinessException;
import fr.ifremer.reefdb.ui.swing.action.AbstractReefDbAction;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static org.nuiton.i18n.I18n.t;
......@@ -70,9 +73,12 @@ public class ExportAction extends AbstractReefDbAction<PhotosTabUIModel, PhotosT
List<String> filePaths = ReefDbBeans.collectProperties(getModel().getSelectedBeans(), PhotoDTO.PROPERTY_FULL_PATH);
for (String filePath: filePaths) {
File fileSrc = new File(filePath);
File fileDest = new File(destDir, fileSrc.getName());
FileUtils.copyFile(fileSrc, fileDest);
Path fileSrc = Paths.get(filePath);
if (!java.nio.file.Files.isRegularFile(fileSrc)) {
throw new ReefDbBusinessException(t("quadrige3.error.file.not.exists"));
}
Path fileDest = destDir.toPath().resolve(fileSrc.getFileName());
Files.copyFile(fileSrc, fileDest);
}
}
......
......@@ -64,7 +64,7 @@ public class ImportAction extends AbstractReefDbAction<PhotosTabUIModel, PhotosT
t("reefdb.action.photo.import.chooseFile.title"),
t("reefdb.action.photo.import.chooseFile.buttonLabel"),
"(.+(\\.(?i)(" + Images.AVAILABLE_EXTENSION + "))$)",
t("reefdb.action.photo.import.chooseFile.filterDescription"));
t("reefdb.action.photo.import.chooseFile.filterDescription", Images.AVAILABLE_EXTENSION_LIST.toString().toUpperCase()));
if (originalImage != null) {
// get file size
......@@ -115,7 +115,7 @@ public class ImportAction extends AbstractReefDbAction<PhotosTabUIModel, PhotosT
if (newPhoto != null) {
getHandler().setFocusOnCell(getModel().addNewRow(newPhoto));
getModel().setModify(true);
getModel().setModify(true);
}
}
......
......@@ -89,6 +89,7 @@
<JPanel layout="{new BorderLayout()}" constraints="BorderLayout.PAGE_END">
<JPanel constraints="BorderLayout.LINE_START">
<JButton id='importPhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}'/>
<JButton id='downloadPhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}'/>
<JButton id='deletePhotoButton' alignmentX='{Component.CENTER_ALIGNMENT}' onActionPerformed="handler.removePhoto()"/>
</JPanel>
<JPanel constraints='BorderLayout.LINE_END'>
......
......@@ -38,6 +38,14 @@
enabled: {model.getObservationModel().isEditable()};
}
#downloadPhotoButton {
actionIcon: synchro-import;
text: "reefdb.photo.download";
toolTipText: "reefdb.photo.download.tip";
_applicationAction: {DownloadAction.class};
enabled: {model.getSingleSelectedRow().isFileDownloadable()};
}
#deletePhotoButton {
actionIcon: delete;
text: "reefdb.common.delete";
......
......@@ -41,6 +41,7 @@ import org.jdesktop.swingx.table.TableColumnExt;
import org.nuiton.jaxx.application.swing.tab.TabHandler;
import javax.swing.SwingWorker;
import javax.swing.table.TableCellRenderer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
......@@ -137,69 +138,71 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
private void initTable() {
// La colonne mnemonique
final TableColumnExt colonneMnemonique = addColumn(
PhotosTableModel.NAME);
colonneMnemonique.setSortable(true);
colonneMnemonique.setMinWidth(100);
final TableColumnExt nameCol = addColumn(PhotosTableModel.NAME);
nameCol.setSortable(true);
nameCol.setMinWidth(100);
// La colonne type
final TableColumnExt colonneType = addFilterableComboDataColumnToModel(
PhotosTableModel.TYPE,
getContext().getReferentialService().getPhotoTypes(),
false);
colonneType.setSortable(true);
colonneType.setMinWidth(100);
// La colnne legende
final TableColumnExt colonneLegende = addColumn(
PhotosTableModel.CAPTION);
colonneLegende.setSortable(true);
colonneLegende.setMinWidth(100);
final TableColumnExt typeCol = addFilterableComboDataColumnToModel(PhotosTableModel.TYPE,
getContext().getReferentialService().getPhotoTypes(), false);
typeCol.setSortable(true);
typeCol.setMinWidth(100);
// La colonne legende
final TableColumnExt captionCol = addColumn(PhotosTableModel.CAPTION);
captionCol.setSortable(true);
captionCol.setMinWidth(100);
// La colonne date
final TableColumnExt colonneDate = addDatePickerColumnToModel(
PhotosTableModel.DATE,
getConfig().getDateFormat());
colonneDate.setSortable(true);
colonneDate.setMinWidth(100);
final TableColumnExt dateCol = addDatePickerColumnToModel(PhotosTableModel.DATE, getConfig().getDateFormat());
dateCol.setSortable(true);
dateCol.setMinWidth(100);
// La colonne prelevement
samplingOperationCellEditor = newExtendedComboBoxCellEditor(null, PhotosTableModel.SAMPLING_OPERATION, false);
final TableColumnExt colonnePrelevement = addColumn(
final TableColumnExt samplingCol = addColumn(
samplingOperationCellEditor,
newTableCellRender(PhotosTableModel.SAMPLING_OPERATION),
PhotosTableModel.SAMPLING_OPERATION);
colonnePrelevement.setSortable(true);
colonnePrelevement.setMinWidth(200);
samplingCol.setSortable(true);
samplingCol.setMinWidth(200);
// La colonne direction
final TableColumnExt colonneDirection = addColumn(PhotosTableModel.DIRECTION);
colonneDirection.setSortable(true);
colonneDirection.setMinWidth(200);
final TableColumnExt directionCol = addColumn(PhotosTableModel.DIRECTION);
directionCol.setSortable(true);
directionCol.setMinWidth(200);
// La colonne chemin physique
final TableColumnExt colonneCheminPhysique = addColumn(PhotosTableModel.PATH);
colonneCheminPhysique.setSortable(true);
colonneCheminPhysique.setMinWidth(200);
colonneCheminPhysique.setEditable(false);
final TableColumnExt pathCol = addColumn(
null,
(table, value, isSelected, hasFocus, row, column) -> {
if (!getTableModel().getEntry(getTable().convertRowIndexToModel(row)).isFileExists()) {
value = t("reefdb.photo.unavailable");
}
TableCellRenderer defaultRenderer = table.getDefaultRenderer(PhotosTableModel.PATH.getPropertyType());
return defaultRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
},
PhotosTableModel.PATH);
pathCol.setSortable(true);
pathCol.setMinWidth(200);
pathCol.setEditable(false);
// Modele de la table
final PhotosTableModel tableModel = new PhotosTableModel(getTable().getColumnModel());
getTable().setModel(tableModel);
// Colonne toujours visible
colonneMnemonique.setHideable(false);
// colonneType.setHideable(false);
// colonneLegende.setHideable(false);
// colonneDate.setHideable(false);
colonnePrelevement.setHideable(false);
nameCol.setHideable(false);
samplingCol.setHideable(false);
// Initialisation de la table
initTable(getTable());
// Les colonne non visibles
colonneDirection.setVisible(false);
colonneCheminPhysique.setVisible(false);
directionCol.setVisible(false);
pathCol.setVisible(false);
getTable().setVisibleRowCount(5);
......@@ -343,8 +346,8 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
}
if (rowModel != null) {
setFocusOnCell(rowModel);
getModel().setPhotoIndex(getTableModel().getRowIndex(rowModel));
setFocusOnCell(rowModel);
getModel().setPhotoIndex(getTableModel().getRowIndex(rowModel));
}
getModel().setModelAdjusting(false);
......@@ -363,6 +366,9 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
getModel().setModelAdjusting(false);
});
// Listener on table selection
getModel().addPropertyChangeListener(AbstractReefDbTableUIModel.PROPERTY_SELECTED_ROWS, evt -> getUI().processDataBinding(PhotosTabUI.BINDING_EXPORT_PHOTO_BUTTON_ENABLED));
// Listener on combo
getUI().getTypeDiaporamaComboBox().addActionListener(e -> updatePhotoViewerContent());
......@@ -410,7 +416,7 @@ public class PhotosTabUIHandler extends AbstractReefDbTableUIHandler<PhotosTable
/**
* Mettre a jour la ou les photos dans l'écran.
*/
private void updatePhotoViewerContent() {
public void updatePhotoViewerContent() {
// update photo index if model has changed
Integer photoIndex = getModel().getPhotoIndex();
......
......@@ -154,4 +154,8 @@ public class PhotosTabUIModel extends AbstractReefDbTableUIModel<PhotoDTO, Photo
public void setModelAdjusting(boolean modelAdjusting) {
this.modelAdjusting = modelAdjusting;
}
public boolean isExportEnabled() {
return !getSelectedRows().isEmpty() && getSelectedRows().stream().allMatch(PhotosTableRowModel::isFileExists);
}
}
......@@ -23,7 +23,6 @@ package fr.ifremer.reefdb.ui.swing.content.observation.photo;
* #L%
*/
import fr.ifremer.reefdb.dto.ErrorAware;