1 package fr.ifremer.quadrige3.ui.swing;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 import com.google.common.collect.Lists;
27 import com.google.common.collect.Sets;
28 import fr.ifremer.quadrige3.core.config.QuadrigeCoreConfiguration;
29 import fr.ifremer.quadrige3.core.dao.technical.Assert;
30 import fr.ifremer.quadrige3.core.exception.QuadrigeBusinessException;
31 import fr.ifremer.quadrige3.core.exception.QuadrigeTechnicalException;
32 import fr.ifremer.quadrige3.core.security.AuthenticationInfo;
33 import fr.ifremer.quadrige3.core.security.SecurityContextHelper;
34 import fr.ifremer.quadrige3.core.service.ClientServiceLocator;
35 import fr.ifremer.quadrige3.core.service.persistence.PersistenceService;
36 import fr.ifremer.quadrige3.synchro.vo.SynchroProgressionStatus;
37 import fr.ifremer.quadrige3.ui.swing.action.*;
38 import fr.ifremer.quadrige3.ui.swing.content.AbstractMainUIHandler;
39 import fr.ifremer.quadrige3.ui.swing.content.MainUI;
40 import fr.ifremer.quadrige3.ui.swing.desktop.Desktop;
41 import fr.ifremer.quadrige3.ui.swing.desktop.DesktopPower;
42 import fr.ifremer.quadrige3.ui.swing.model.ProgressionUIModel;
43 import fr.ifremer.quadrige3.ui.swing.synchro.SynchroUI;
44 import fr.ifremer.quadrige3.ui.swing.synchro.SynchroUIContext;
45 import fr.ifremer.quadrige3.ui.swing.synchro.SynchroUIHandler;
46 import org.apache.commons.io.FileUtils;
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49 import org.jdesktop.beans.AbstractBean;
50 import org.nuiton.converter.ConverterUtil;
51 import org.nuiton.i18n.I18n;
52 import org.nuiton.i18n.init.DefaultI18nInitializer;
53 import org.nuiton.i18n.init.UserI18nInitializer;
54 import org.nuiton.jaxx.application.ApplicationBusinessException;
55 import org.nuiton.jaxx.application.ApplicationIOUtil;
56 import org.nuiton.jaxx.application.bean.JavaBeanObjectUtil;
57 import org.nuiton.jaxx.application.swing.action.ApplicationActionEngine;
58 import org.nuiton.jaxx.application.swing.action.ApplicationUIAction;
59 import org.nuiton.jaxx.application.swing.util.ApplicationErrorHelper;
60
61 import javax.swing.JComponent;
62 import javax.swing.JFrame;
63 import java.awt.Component;
64 import java.beans.PropertyChangeListener;
65 import java.beans.PropertyChangeListenerProxy;
66 import java.io.Closeable;
67 import java.io.File;
68 import java.io.IOException;
69 import java.io.Writer;
70 import java.util.*;
71 import java.util.concurrent.ThreadPoolExecutor;
72
73 import static org.nuiton.i18n.I18n.n;
74 import static org.nuiton.i18n.I18n.t;
75
76
77
78
79 public abstract class ApplicationUIContext extends AbstractBean implements org.nuiton.jaxx.application.swing.ApplicationUIContext, UIMessageNotifier, Closeable {
80
81 private static final Log LOG = LogFactory.getLog(ApplicationUIContext.class);
82
83
84
85
86 private static ApplicationUIContext instance;
87
88 public static final String PROPERTY_LOCALE = "locale";
89 public static final String PROPERTY_SCREEN = "screen";
90 public static final String PROPERTY_BUSY = "busy";
91 public static final String PROPERTY_HIDE_BODY = "hideBody";
92 public static final String PROPERTY_AUTHENTICATED = "authenticated";
93 public static final String PROPERTY_SYNCHRO_RUNNING = "synchroRunning";
94 public static final String PROPERTY_DB_EXIST = "dbExist";
95 public static final String PROPERTY_DB_LOADED = "dbLoaded";
96
97 public static final Set<String> PROPERTIES_TO_SAVE = Sets.newHashSet(
98 PROPERTY_LOCALE
99 );
100
101
102
103
104 private final QuadrigeCoreConfiguration configuration;
105
106
107
108 private SwingSession swingSession;
109 private ThreadPoolExecutor saveComponentInSwingSessionExecutor;
110
111
112
113
114 private MainUI mainUI;
115 private ActionUI actionUI;
116
117
118
119 private ActionFactory actionFactory;
120 private ApplicationActionEngine actionEngine;
121 private final Map<Class<? extends AbstractAction<?, ?, ?>>, Boolean> actionInProgress = new LinkedHashMap<>();
122
123
124
125
126 private DialogHelper dialogHelper;
127
128
129
130 private Screen screen;
131 private LinkedList<Screen> screenBreadcrumb;
132
133
134
135 private Locale locale;
136
137
138
139 private boolean dbExist;
140
141
142
143 private boolean dbJustInstalled;
144
145
146
147 private boolean dbJustImportedFromFile;
148
149
150
151 private boolean persistenceLoaded;
152
153
154
155 private boolean busy;
156
157
158
159 private boolean hideBody;
160
161
162
163
164 private boolean closed;
165
166
167
168
169 private Writer lock;
170
171
172
173
174 private SynchroUIContext synchroUIContext;
175 private SynchroUIHandler synchroUIHandler;
176 private boolean synchroRunning;
177
178
179
180
181 private AuthenticationInfo authenticationInfo;
182
183
184
185 private boolean authenticated;
186
187 protected ApplicationUIContext(QuadrigeCoreConfiguration configuration) {
188 this.configuration = configuration;
189 instance = this;
190 }
191
192 public static ApplicationUIContext getInstance() {
193 return instance;
194 }
195
196 public static void setInstance(ApplicationUIContext applicationUIContext) {
197 instance = applicationUIContext;
198 }
199
200 @Override
201 public QuadrigeCoreConfiguration getConfiguration() {
202 return configuration;
203 }
204
205 @Override
206 public MainUI getMainUI() {
207 return mainUI;
208 }
209
210 public void setMainUI(MainUI mainUI) {
211 this.mainUI = mainUI;
212 }
213
214 public void installActionUI(JFrame frame) {
215 setActionUI(new ActionUI(frame, this));
216 }
217
218 @Override
219 public ActionFactory getActionFactory() {
220 if (actionFactory == null) {
221 actionFactory = new ActionFactory();
222 }
223 return actionFactory;
224 }
225
226 @Override
227 public ApplicationActionEngine getActionEngine() {
228 if (actionEngine == null) {
229 actionEngine = new ApplicationActionEngine(getActionFactory());
230 }
231 return actionEngine;
232 }
233
234 @Override
235 public final boolean isActionInProgress(ApplicationUIAction applicationUIAction) {
236 return false;
237 }
238
239 @Override
240 public final void setActionInProgress(ApplicationUIAction applicationUIAction, boolean b) {
241
242 }
243
244 public boolean isActionInProgress(Class<? extends AbstractAction<?, ?, ?>> actionClass) {
245 return actionInProgress.getOrDefault(actionClass, false);
246 }
247
248 public void setActionInProgress(Class<? extends AbstractAction<?, ?, ?>> actionClass, boolean progress) {
249 if (progress && isActionInProgress(actionClass)) {
250 LOG.warn(String.format("Action '%s' is already marked as running", actionClass.getName()));
251 }
252 actionInProgress.put(actionClass, progress);
253 }
254
255 public DialogHelper getDialogHelper() {
256 if (dialogHelper == null) {
257 dialogHelper = new DialogHelper(this);
258 }
259 return dialogHelper;
260 }
261
262 @Override
263 public ApplicationErrorHelper getErrorHelper() {
264 return getDialogHelper();
265 }
266
267
268
269 public boolean isDbExist() {
270 return dbExist;
271 }
272
273 public void setDbExist(boolean dbExist) {
274 this.dbExist = dbExist;
275 firePropertyChange(PROPERTY_DB_EXIST, null, dbExist);
276 }
277
278 public boolean isDbJustInstalled() {
279 return dbJustInstalled;
280 }
281
282 public void setDbJustInstalled(boolean dbJustInstalled) {
283 this.dbJustInstalled = dbJustInstalled;
284 }
285
286 public boolean isDbJustImportedFromFile() {
287 return dbJustImportedFromFile;
288 }
289
290 public void setDbJustImportedFromFile(boolean dbJustImportedFromFile) {
291 this.dbJustImportedFromFile = dbJustImportedFromFile;
292 }
293
294 public boolean isPersistenceLoaded() {
295 return persistenceLoaded;
296 }
297
298 public void setPersistenceLoaded(boolean persistenceLoaded) {
299 this.persistenceLoaded = persistenceLoaded;
300 firePropertyChange(PROPERTY_DB_LOADED, null, persistenceLoaded);
301 }
302
303
304
305 @Override
306 public ActionUI getActionUI() {
307 return actionUI;
308 }
309
310 public void setActionUI(ActionUI actionUI) {
311 this.actionUI = actionUI;
312 }
313
314 public Screen getScreen() {
315 return screen;
316 }
317
318 public void setScreen(Screen screen) {
319 this.screen = screen;
320 firePropertyChange(PROPERTY_SCREEN, null, screen);
321 }
322
323 public LinkedList<Screen> getScreenBreadcrumb() {
324 if (screenBreadcrumb == null) {
325 screenBreadcrumb = Lists.newLinkedList();
326 }
327 return screenBreadcrumb;
328 }
329
330 public void setFallBackScreen() {
331 if (isPersistenceLoaded()) {
332 setScreen(Screen.HOME);
333 } else {
334 setScreen(Screen.MANAGE_DB);
335 }
336 }
337
338 public Locale getLocale() {
339 return locale;
340 }
341
342 public void setLocale(Locale locale) {
343 this.locale = locale;
344
345
346 configuration.setI18nLocale(locale);
347 I18n.setDefaultLocale(locale);
348 Locale.setDefault(locale);
349 JComponent.setDefaultLocale(locale);
350
351 firePropertyChange(PROPERTY_LOCALE, null, locale);
352 }
353
354 public boolean acceptLocale(String expected) {
355 return getLocale() != null && getLocale().toString().equals(expected);
356 }
357
358 @Override
359 public boolean isBusy() {
360 return busy;
361 }
362
363 @Override
364 public void setBusy(boolean busy) {
365 this.busy = busy;
366 firePropertyChange(PROPERTY_BUSY, null, busy);
367 }
368
369 @Override
370 public boolean isHideBody() {
371 return hideBody;
372 }
373
374 @Override
375 public void setHideBody(boolean hideBody) {
376 this.hideBody = hideBody;
377 firePropertyChange(PROPERTY_HIDE_BODY, null, hideBody);
378 }
379
380 public final AuthenticationInfo getAuthenticationInfo() {
381 return authenticationInfo;
382 }
383
384 public void setAuthenticationInfo(AuthenticationInfo authenticationInfo) {
385 this.authenticationInfo = authenticationInfo;
386 }
387
388 public boolean isAuthenticated() {
389 return authenticated;
390 }
391
392 public void setAuthenticated(boolean authenticated) {
393 this.authenticated = authenticated;
394 firePropertyChange(PROPERTY_AUTHENTICATED, null, authenticated);
395 }
396
397
398
399
400
401
402
403 public boolean tryReAuthenticate() {
404
405 boolean result = false;
406
407
408 if (getAuthenticationInfo() != null) {
409 result = SecurityContextHelper.authenticate(getAuthenticationInfo().getLogin(), getAuthenticationInfo().getPassword());
410
411
412 setAuthenticated(result);
413 }
414
415 return result;
416 }
417
418
419
420
421 public boolean isSynchroEnabled() {
422 return isAuthenticated() && isPersistenceLoaded() && getConfiguration().isSynchronizationEnabled();
423 }
424
425 public SynchroUIContext getSynchroContext() {
426 return synchroUIContext;
427 }
428
429 public SynchroUIHandler getSynchroHandler() {
430 return synchroUIHandler;
431 }
432
433 public void setSynchroUI(SynchroUI synchroUI) {
434 this.synchroUIHandler = synchroUI.getHandler();
435 this.synchroUIContext = synchroUI.getModel();
436 if (synchroUIContext != null) {
437 synchroUIContext.addPropertyChangeListener(evt -> {
438 if (SynchroUIContext.PROPERTY_PROGRESSION_STATUS.equals(evt.getPropertyName())) {
439 ApplicationUIContext.this.setSynchroRunning(!SynchroProgressionStatus.NOT_STARTED.equals(evt.getNewValue()));
440 }
441 });
442 }
443 }
444
445 public boolean isSynchroRunning() {
446 return synchroRunning;
447 }
448
449 public void setSynchroRunning(boolean synchroRunning) {
450 this.synchroRunning = synchroRunning;
451 firePropertyChange(PROPERTY_SYNCHRO_RUNNING, null, synchroRunning);
452 }
453
454 public void setSwingSession(SwingSession swingSession) {
455 this.swingSession = swingSession;
456 }
457
458 public void initSwingSession(MainUI ui) {
459 getSwingSession().add(ui);
460
461 getSwingSession().addUnsavedRootComponentByName(ui.getName() + "/JRootPane");
462 getSwingSession().addUnsavedRootComponentByName(ui.getName() + "/JXLayer");
463 saveSwingSession(null);
464 }
465
466 public SwingSession getSwingSession() {
467 Assert.notNull(swingSession, "SwingSession has not been initialized !");
468 return swingSession;
469 }
470
471 public void saveSwingSession(Component componentToRemove) {
472
473 try {
474 getSwingSession().setPartialSave(false);
475 getSwingSession().save();
476 if (LOG.isDebugEnabled()) LOG.debug("swing session saved");
477 } catch (IOException ex) {
478 LOG.error(ex.getLocalizedMessage());
479 }
480
481
482 if (componentToRemove != null) {
483 getSwingSession().remove(componentToRemove);
484 }
485 }
486
487 public void saveComponentInSwingSession(Component componentToSave, String stateContext) {
488
489 saveComponentInSwingSessionExecutor.execute(() -> {
490
491
492 ApplicationUIContext.this.getSwingSession().updateState(componentToSave, stateContext);
493
494
495 ApplicationUIContext.this.getSwingSession().setPartialSave(true);
496
497
498 try {
499 ApplicationUIContext.this.getSwingSession().save();
500 if (LOG.isDebugEnabled()) LOG.debug("swing session saved (by worker)");
501 } catch (IOException ex) {
502 LOG.error(ex.getLocalizedMessage());
503 }
504
505 ApplicationUIContext.this.getSwingSession().setPartialSave(false);
506 });
507 }
508
509 public void restoreComponentFromSwingSession(Component component) {
510 getSwingSession().restoreState(component);
511 }
512
513 public abstract ApplicationUI<?, ?> getApplicationUI(Screen screen);
514
515 public abstract String getSelectedScreenTitle();
516
517 public abstract boolean isAuthenticatedAsLocalUser();
518
519 public abstract String getAuthenticationLabel();
520
521 public abstract String getAuthenticationToolTipText();
522
523 public abstract void openPersistenceService(boolean isAfterImportDb);
524
525 public abstract void closePersistenceService();
526
527 public abstract void closePersistenceService(boolean compact, boolean keepAuthentication);
528
529 public abstract void checkDbContext(ProgressionUIModel progressionModel);
530
531 public boolean checkUpdateReachable(String url, boolean showErrorInPopup) {
532 boolean result = true;
533
534 try {
535 ApplicationUIUtil.tryToConnectToUpdateUrl(
536 url,
537 n("quadrige3.error.update.bad.url.syntax"),
538 n("quadrige3.error.update.could.not.reach.url"),
539 n("quadrige3.error.update.could.not.find.url")
540 );
541 } catch (ApplicationBusinessException e) {
542 if (showErrorInPopup) {
543 getErrorHelper().showWarningDialog(e.getMessage());
544 } else {
545 showInformationMessage(e.getMessage());
546 }
547 result = false;
548 }
549 return result;
550 }
551
552
553
554
555
556
557 protected boolean doUpdates() {
558
559 boolean needRestart = false;
560
561 if (checkUpdateReachable(getConfiguration().getUpdateApplicationUrl(), true)) {
562
563 UpdateApplicationAction action = getActionFactory().createLogicAction(new DummyMainUIHandler(this), UpdateApplicationAction.class);
564 action.setSilent(true);
565 getActionEngine().runActionAndWait(action);
566
567 needRestart = action.isReload();
568 }
569
570 if (checkUpdateReachable(getConfiguration().getUpdateDataUrl(), true)) {
571
572 UpdateDataAction action = getActionFactory().createLogicAction(new DummyMainUIHandler(this), UpdateDataAction.class);
573 action.setSilent(true);
574 getActionEngine().runActionAndWait(action);
575
576 needRestart |= action.isReload();
577 }
578
579 return needRestart;
580 }
581
582 public abstract void clearDbContext();
583
584
585
586
587
588 public void init(String i18nBundleName) {
589
590
591
592 ConverterUtil.deregister();
593 ConverterUtil.initConverters();
594
595
596 addShutdownHook();
597
598
599
600
601 File i18nDirectory = getConfiguration().getI18nDirectory();
602 if (!getConfiguration().isFullLaunchMode()) {
603
604 i18nDirectory = new File(getConfiguration().getDataDirectory(), "i18n");
605
606 if (i18nDirectory.exists()) {
607
608 ApplicationIOUtil.cleanDirectory(i18nDirectory, String.format("Failed to delete translation cache at %s", i18nDirectory));
609 }
610 }
611
612 ApplicationIOUtil.forceMkdir(i18nDirectory, String.format("Failed to create translation directory at %s", i18nDirectory));
613
614 if (LOG.isDebugEnabled()) {
615 LOG.debug("I18N directory: " + i18nDirectory);
616 }
617
618 locale = getConfiguration().getI18nLocale();
619 Locale.setDefault(locale);
620 JComponent.setDefaultLocale(locale);
621
622 if (LOG.isInfoEnabled()) {
623 LOG.info(String.format("Starts i18n with locale [%s] at [%s]", locale, i18nDirectory));
624 }
625 I18n.init(new UserI18nInitializer(i18nDirectory, new DefaultI18nInitializer(i18nBundleName + "-i18n")), locale);
626
627
628
629
630 File lockFile = getConfiguration().getLockFile();
631
632 if (lockFile.exists()) {
633
634 try {
635 FileUtils.forceDelete(lockFile);
636 } catch (IOException e) {
637 throw new QuadrigeBusinessException(t("quadrige3.error.application.already.started", lockFile));
638 }
639 }
640
641 lock = ApplicationIOUtil.newWriter(lockFile, "error");
642 try {
643 lock.write(new Date().toString());
644 } catch (IOException e) {
645 throw new QuadrigeTechnicalException("Could not create lock file", e);
646 }
647
648 if (LOG.isDebugEnabled()) {
649 LOG.debug("Create lock file: " + lockFile);
650 }
651
652 installActionUI(null);
653
654 saveComponentInSwingSessionExecutor = ActionFactory.createSingleThreadExecutor(ActionFactory.ExecutionMode.CUMULATIVE);
655
656
657 save();
658
659
660 addPropertyChangeListener(evt -> {
661 if (PROPERTIES_TO_SAVE.contains(evt.getPropertyName())) {
662 ApplicationUIContext.this.save();
663 }
664 });
665
666 }
667
668 public void reloadDbCache(ProgressionUIModel progressionModel) {
669 if (isPersistenceLoaded() && !isDbJustInstalled()) {
670
671
672 int total = progressionModel.getTotal();
673 int current = progressionModel.getCurrent();
674 String message = progressionModel.getMessage();
675
676
677 clearCaches();
678
679
680 PersistenceService persistenceService = ClientServiceLocator.instance().getPersistenceService();
681 persistenceService.loadDefaultCaches(progressionModel);
682
683
684 progressionModel.setTotal(total);
685 progressionModel.setCurrent(current);
686 progressionModel.setMessage(message);
687 }
688 }
689
690 public void clearCaches() {
691 if (isPersistenceLoaded()) {
692 PersistenceService persistenceService = ClientServiceLocator.instance().getPersistenceService();
693 persistenceService.clearAllCaches();
694 }
695 }
696
697 @Override
698 public void close() {
699
700
701 if (isClosed()) {
702 return;
703 }
704 if (LOG.isInfoEnabled()) {
705 LOG.info("Closing application ...");
706 }
707
708 try {
709
710
711
712
713 closePersistenceService();
714
715 setScreen(null);
716
717
718 PropertyChangeListener[] listeners = getPropertyChangeListeners();
719 for (PropertyChangeListener listener : listeners) {
720 if (listener instanceof PropertyChangeListenerProxy) {
721 PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener;
722 listener = proxy.getListener();
723 }
724 if (LOG.isDebugEnabled()) {
725 LOG.debug("Remove listener: " + listener);
726 }
727 removePropertyChangeListener(listener);
728 }
729 setMainUI(null);
730
731
732
733
734
735
736 setActionUI(null);
737
738 } finally {
739 closed = true;
740
741 if (lock != null) {
742
743
744 ApplicationIOUtil.close(lock, "Unable to close lock stream");
745 FileUtils.deleteQuietly(getConfiguration().getLockFile());
746 if (LOG.isDebugEnabled()) {
747 LOG.debug("Lock file released");
748 }
749
750 }
751
752 }
753
754 if (LOG.isInfoEnabled()) {
755 LOG.info("Application closed.");
756 }
757
758 }
759
760 public boolean isClosed() {
761 return closed;
762 }
763
764
765
766
767 private void addShutdownHook() {
768
769
770 Runtime.getRuntime().addShutdownHook(new Thread(this::close));
771
772
773 DesktopPower desktopPower = Desktop.getDesktopPower();
774 if (desktopPower != null) {
775
776 desktopPower.addListener(this::close);
777 }
778
779 }
780
781
782
783
784
785 protected void save() {
786 if (LOG.isDebugEnabled()) {
787 StringBuilder info = new StringBuilder("Save config (");
788 for (String property : PROPERTIES_TO_SAVE) {
789 info.append(property).append(": ").append(JavaBeanObjectUtil.getProperty(this, property)).append(", ");
790 }
791 info = new StringBuilder(info.substring(0, info.lastIndexOf(", ")));
792 info.append(")");
793 LOG.debug(info.toString());
794 }
795 getConfiguration().save();
796 }
797
798 private class DummyMainUIHandler extends AbstractMainUIHandler {
799
800 private final ApplicationUIContext context;
801
802 DummyMainUIHandler(ApplicationUIContext context) {
803 this.context = context;
804 }
805
806 @Override
807 public ApplicationUIContext getContext() {
808 return context;
809 }
810
811 }
812 }