1 package fr.ifremer.quadrige2.ui.swing.common;
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.base.Preconditions;
27 import com.google.common.collect.Lists;
28 import com.google.common.collect.Sets;
29 import fr.ifremer.quadrige2.core.config.Quadrige2CoreConfiguration;
30 import fr.ifremer.quadrige2.core.exception.Quadrige2BusinessException;
31 import fr.ifremer.quadrige2.core.exception.Quadrige2TechnicalException;
32 import fr.ifremer.quadrige2.core.security.AuthenticationInfo;
33 import fr.ifremer.quadrige2.synchro.vo.SynchroProgressionStatus;
34 import fr.ifremer.quadrige2.ui.swing.common.action.ActionFactory;
35 import fr.ifremer.quadrige2.ui.swing.common.action.ActionUI;
36 import fr.ifremer.quadrige2.ui.swing.common.content.MainUI;
37 import fr.ifremer.quadrige2.ui.swing.common.desktop.Desktop;
38 import fr.ifremer.quadrige2.ui.swing.common.desktop.DesktopPower;
39 import fr.ifremer.quadrige2.ui.swing.common.model.ProgressionModel;
40 import fr.ifremer.quadrige2.ui.swing.common.synchro.SynchroUI;
41 import fr.ifremer.quadrige2.ui.swing.common.synchro.SynchroUIContext;
42 import fr.ifremer.quadrige2.ui.swing.common.synchro.SynchroUIHandler;
43 import org.apache.commons.io.FileUtils;
44 import org.apache.commons.logging.Log;
45 import org.apache.commons.logging.LogFactory;
46 import org.jdesktop.beans.AbstractBean;
47 import org.nuiton.converter.ConverterUtil;
48 import org.nuiton.i18n.I18n;
49 import org.nuiton.i18n.init.DefaultI18nInitializer;
50 import org.nuiton.i18n.init.UserI18nInitializer;
51 import org.nuiton.jaxx.application.ApplicationBusinessException;
52 import org.nuiton.jaxx.application.ApplicationIOUtil;
53 import org.nuiton.jaxx.application.bean.JavaBeanObjectUtil;
54 import org.nuiton.jaxx.application.swing.action.ApplicationActionEngine;
55 import org.nuiton.jaxx.application.swing.util.ApplicationErrorHelper;
56
57 import javax.swing.JComponent;
58 import javax.swing.JFrame;
59 import java.awt.Component;
60 import java.beans.PropertyChangeEvent;
61 import java.beans.PropertyChangeListener;
62 import java.beans.PropertyChangeListenerProxy;
63 import java.io.Closeable;
64 import java.io.File;
65 import java.io.IOException;
66 import java.io.Writer;
67 import java.util.Date;
68 import java.util.LinkedList;
69 import java.util.Locale;
70 import java.util.Set;
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 Quadrige2CoreConfiguration configuration;
105
106
107
108 private SwingSession swingSession;
109 protected 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
122
123
124 private DialogHelper dialogHelper;
125
126
127
128 private Screen screen;
129 private LinkedList<Screen> screenBreadcrumb;
130
131
132
133 private Locale locale;
134
135
136
137 private boolean dbExist;
138
139
140
141 private boolean dbJustInstalled;
142
143
144
145 private boolean dbJustImportedFromFile;
146
147
148
149 private boolean persistenceLoaded;
150
151
152
153 private boolean busy;
154
155
156
157 private boolean hideBody;
158
159
160
161
162 private boolean closed;
163
164
165
166
167 private Writer lock;
168
169
170
171
172 private SynchroUIContext synchroUIContext;
173 private SynchroUIHandler synchroUIHandler;
174 private boolean synchroRunning;
175 private final PropertyChangeListener synchroRunningListener = new PropertyChangeListener() {
176 @Override
177 public void propertyChange(PropertyChangeEvent evt) {
178 if (SynchroUIContext.PROPERTY_PROGRESSION_STATUS.equals(evt.getPropertyName())) {
179 ApplicationUIContext.this.setSynchroRunning(!SynchroProgressionStatus.NOT_STARTED.equals(evt.getNewValue()));
180 }
181 }
182 };
183
184
185
186
187 private AuthenticationInfo authenticationInfo;
188
189
190
191 private boolean authenticated;
192
193 protected ApplicationUIContext(Quadrige2CoreConfiguration configuration) {
194 this.configuration = configuration;
195 instance = this;
196 }
197
198 public static ApplicationUIContext getInstance() {
199 return instance;
200 }
201
202 public static void setInstance(ApplicationUIContext applicationUIContext) {
203 instance = applicationUIContext;
204 }
205
206 @Override
207 public Quadrige2CoreConfiguration getConfiguration() {
208 return configuration;
209 }
210
211 @Override
212 public MainUI getMainUI() {
213 return mainUI;
214 }
215
216 public void setMainUI(MainUI mainUI) {
217 this.mainUI = mainUI;
218 }
219
220 public void installActionUI(JFrame frame) {
221 setActionUI(new ActionUI(frame, this));
222 }
223
224 @Override
225 public ActionFactory getActionFactory() {
226 if (actionFactory == null) {
227 actionFactory = new ActionFactory();
228 }
229 return actionFactory;
230 }
231
232 @Override
233 public ApplicationActionEngine getActionEngine() {
234 if (actionEngine == null) {
235 actionEngine = new ApplicationActionEngine(getActionFactory());
236 }
237 return actionEngine;
238 }
239
240 public DialogHelper getDialogHelper() {
241 if (dialogHelper == null) {
242 dialogHelper = new DialogHelper(this);
243 }
244 return dialogHelper;
245 }
246
247 @Override
248 public ApplicationErrorHelper getErrorHelper() {
249 return getDialogHelper();
250 }
251
252
253
254 public boolean isDbExist() {
255 return dbExist;
256 }
257
258 public void setDbExist(boolean dbExist) {
259 this.dbExist = dbExist;
260 firePropertyChange(PROPERTY_DB_EXIST, null, dbExist);
261 }
262
263 public boolean isDbJustInstalled() {
264 return dbJustInstalled;
265 }
266
267 public void setDbJustInstalled(boolean dbJustInstalled) {
268 this.dbJustInstalled = dbJustInstalled;
269 }
270
271 public boolean isDbJustImportedFromFile() {
272 return dbJustImportedFromFile;
273 }
274
275 public void setDbJustImportedFromFile(boolean dbJustImportedFromFile) {
276 this.dbJustImportedFromFile = dbJustImportedFromFile;
277 }
278
279 public boolean isPersistenceLoaded() {
280 return persistenceLoaded;
281 }
282
283 public void setPersistenceLoaded(boolean persistenceLoaded) {
284 this.persistenceLoaded = persistenceLoaded;
285 firePropertyChange(PROPERTY_DB_LOADED, null, persistenceLoaded);
286 }
287
288
289
290 @Override
291 public ActionUI getActionUI() {
292 return actionUI;
293 }
294
295 public void setActionUI(ActionUI actionUI) {
296 this.actionUI = actionUI;
297 }
298
299 public Screen getScreen() {
300 return screen;
301 }
302
303 public void setScreen(Screen screen) {
304 Screen oldScreen = getScreen();
305 this.screen = screen;
306 firePropertyChange(PROPERTY_SCREEN, oldScreen, screen);
307 }
308
309 public LinkedList<Screen> getScreenBreadcrumb() {
310 if (screenBreadcrumb == null) {
311 screenBreadcrumb = Lists.newLinkedList();
312 }
313 return screenBreadcrumb;
314 }
315
316 public void setFallBackScreen() {
317 if (isPersistenceLoaded()) {
318 setScreen(Screen.HOME);
319 } else {
320 setScreen(Screen.MANAGE_DB);
321 }
322 }
323
324 public Locale getLocale() {
325 return locale;
326 }
327
328 public void setLocale(Locale locale) {
329 this.locale = locale;
330
331
332 configuration.setI18nLocale(locale);
333 I18n.setDefaultLocale(locale);
334 Locale.setDefault(locale);
335 JComponent.setDefaultLocale(locale);
336
337 firePropertyChange(PROPERTY_LOCALE, null, locale);
338 }
339
340 public boolean acceptLocale(String expected) {
341 return getLocale() != null && getLocale().toString().equals(expected);
342 }
343
344 @Override
345 public boolean isBusy() {
346 return busy;
347 }
348
349 @Override
350 public void setBusy(boolean busy) {
351 this.busy = busy;
352 firePropertyChange(PROPERTY_BUSY, null, busy);
353 }
354
355 @Override
356 public boolean isHideBody() {
357 return hideBody;
358 }
359
360 @Override
361 public void setHideBody(boolean hideBody) {
362 this.hideBody = hideBody;
363 firePropertyChange(PROPERTY_HIDE_BODY, null, hideBody);
364 }
365
366 public final AuthenticationInfo getAuthenticationInfo() {
367 return authenticationInfo;
368 }
369
370 public void setAuthenticationInfo(AuthenticationInfo authenticationInfo) {
371 this.authenticationInfo = authenticationInfo;
372 }
373
374 public boolean isAuthenticated() {
375 return authenticated;
376 }
377
378 public void setAuthenticated(boolean authenticated) {
379 boolean oldValue = isAuthenticated();
380 this.authenticated = authenticated;
381 firePropertyChange(PROPERTY_AUTHENTICATED, oldValue, authenticated);
382 }
383
384
385
386
387 public boolean isSynchroEnabled() {
388 return isAuthenticated() && isPersistenceLoaded() && getConfiguration().isSynchronizationEnabled();
389 }
390
391 public SynchroUIContext getSynchroContext() {
392 return synchroUIContext;
393 }
394
395 public SynchroUIHandler getSynchroHandler() {
396 return synchroUIHandler;
397 }
398
399 public void setSynchroUI(SynchroUI synchroUI) {
400 this.synchroUIHandler = synchroUI.getHandler();
401 this.synchroUIContext = synchroUI.getModel();
402 if (synchroUIContext != null) {
403 synchroUIContext.addPropertyChangeListener(synchroRunningListener);
404 }
405 }
406
407 public boolean isSynchroRunning() {
408 return synchroRunning;
409 }
410
411 public void setSynchroRunning(boolean synchroRunning) {
412 this.synchroRunning = synchroRunning;
413 firePropertyChange(PROPERTY_SYNCHRO_RUNNING, null, synchroRunning);
414 }
415
416 public void setSwingSession(SwingSession swingSession) {
417 this.swingSession = swingSession;
418 }
419
420 public void initSwingSession(MainUI ui) {
421 getSwingSession().add(ui);
422
423 getSwingSession().addUnsavedRootComponentByName(ui.getName() + "/JRootPane");
424 getSwingSession().addUnsavedRootComponentByName(ui.getName() + "/JXLayer");
425 saveSwingSession(null);
426 }
427
428 public SwingSession getSwingSession() {
429 Preconditions.checkNotNull(swingSession, "SwingSession has not been initialized !");
430 return swingSession;
431 }
432
433 public void saveSwingSession(Component componentToRemove) {
434
435 try {
436 getSwingSession().setPartialSave(false);
437 getSwingSession().save();
438 if (LOG.isDebugEnabled()) LOG.debug("swing session saved");
439 } catch (IOException ex) {
440 LOG.error(ex.getLocalizedMessage());
441 }
442
443
444 if (componentToRemove != null) {
445 getSwingSession().remove(componentToRemove);
446 }
447 }
448
449 public void saveComponentInSwingSession(Component componentToSave, String stateContext) {
450
451 saveComponentInSwingSessionExecutor.execute(new Runnable() {
452 @Override
453 public void run() {
454
455 ApplicationUIContext.this.getSwingSession().updateState(componentToSave, stateContext);
456
457
458 ApplicationUIContext.this.getSwingSession().setPartialSave(true);
459
460
461 try {
462 ApplicationUIContext.this.getSwingSession().save();
463 if (LOG.isDebugEnabled()) LOG.debug("swing session saved (by worker)");
464 } catch (IOException ex) {
465 LOG.error(ex.getLocalizedMessage());
466 }
467
468 ApplicationUIContext.this.getSwingSession().setPartialSave(false);
469 }
470 });
471 }
472
473 public void restoreComponentFromSwingSession(Component component) {
474 getSwingSession().restoreState(component);
475 }
476
477 public abstract ApplicationUI<?, ?> getApplicationUI(Screen screen);
478
479 public abstract String getSelectedScreenTitle();
480
481 public abstract boolean isAuthenticatedAsLocalUser();
482
483 public abstract void reloadDbCache(ProgressionModel progressionModel);
484
485 public abstract String getAuthenticationLabel();
486
487 public abstract String getAuthenticationToolTipText();
488
489 public abstract void openPersistenceService(boolean isAfterImportDb);
490
491 public abstract void closePersistenceService();
492
493 public abstract void closePersistenceService(boolean compact, boolean keepAuthentication);
494
495 public abstract void checkDbContext(ProgressionModel progressionModel);
496
497 public boolean checkUpdateReachable(String url, boolean showErrorInPopup) {
498 boolean result = true;
499
500 try {
501 ApplicationUIUtil.tryToConnectToUpdateUrl(
502 url,
503 n("quadrige2.error.update.bad.url.syntax"),
504 n("quadrige2.error.update.could.not.reach.url"),
505 n("quadrige2.error.update.could.not.find.url")
506 );
507 } catch (ApplicationBusinessException e) {
508 if (showErrorInPopup) {
509 getErrorHelper().showWarningDialog(e.getMessage());
510 } else {
511 showInformationMessage(e.getMessage());
512 }
513 result = false;
514 }
515 return result;
516 }
517
518 public abstract void clearDbContext();
519
520
521
522
523
524 public void init(String i18nBundleName) {
525
526
527
528 ConverterUtil.deregister();
529 ConverterUtil.initConverters();
530
531
532 addShutdownHook();
533
534
535
536
537 File i18nDirectory = getConfiguration().getI18nDirectory();
538 if (!getConfiguration().isFullLaunchMode()) {
539
540 i18nDirectory = new File(getConfiguration().getDataDirectory(), "i18n");
541
542 if (i18nDirectory.exists()) {
543
544 ApplicationIOUtil.cleanDirectory(i18nDirectory, t("quadrige2.i18n.deleteCache.error", i18nDirectory));
545 }
546 }
547
548 ApplicationIOUtil.forceMkdir(i18nDirectory, t("quadrige2.i18n.mkDir.error", i18nDirectory));
549
550 if (LOG.isDebugEnabled()) {
551 LOG.debug("I18N directory: " + i18nDirectory);
552 }
553
554 locale = getConfiguration().getI18nLocale();
555 Locale.setDefault(locale);
556 JComponent.setDefaultLocale(locale);
557
558 if (LOG.isInfoEnabled()) {
559 LOG.info(String.format("Starts i18n with locale [%s] at [%s]", locale, i18nDirectory));
560 }
561 I18n.init(new UserI18nInitializer(i18nDirectory, new DefaultI18nInitializer(i18nBundleName + "-i18n")), locale);
562
563
564
565
566 File lockFile = getConfiguration().getLockFile();
567
568 if (lockFile.exists()) {
569
570 try {
571 FileUtils.forceDelete(lockFile);
572 } catch (IOException e) {
573 throw new Quadrige2BusinessException(t("quadrige2.error.application.already.started", lockFile));
574 }
575 }
576
577 lock = ApplicationIOUtil.newWriter(lockFile, "error");
578 try {
579 lock.write(new Date().toString());
580 } catch (IOException e) {
581 throw new Quadrige2TechnicalException("Could not create lock file", e);
582 }
583
584 if (LOG.isDebugEnabled()) {
585 LOG.debug("Create lock file: " + lockFile);
586 }
587
588 installActionUI(null);
589
590 saveComponentInSwingSessionExecutor = ActionFactory.createThreadExecutor(true);
591
592
593 save();
594
595
596 addPropertyChangeListener(new PropertyChangeListener() {
597 @Override
598 public void propertyChange(PropertyChangeEvent evt) {
599 if (PROPERTIES_TO_SAVE.contains(evt.getPropertyName())) {
600 ApplicationUIContext.this.save();
601 }
602 }
603 });
604
605 }
606
607 @Override
608 public void close() {
609
610
611 if (isClosed()) {
612 return;
613 }
614 if (LOG.isInfoEnabled()) {
615 LOG.info("Closing application ...");
616 }
617
618 try {
619
620
621
622
623 closePersistenceService();
624
625 setScreen(null);
626
627
628 PropertyChangeListener[] listeners = getPropertyChangeListeners();
629 for (PropertyChangeListener listener : listeners) {
630 if (listener instanceof PropertyChangeListenerProxy) {
631 PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener;
632 listener = proxy.getListener();
633 }
634 if (LOG.isDebugEnabled()) {
635 LOG.debug("Remove listener: " + listener);
636 }
637 removePropertyChangeListener(listener);
638 }
639 setMainUI(null);
640 if (actionUI != null) {
641
642
643 actionUI.getModel().clear();
644 }
645 setActionUI(null);
646
647 } finally {
648 closed = true;
649
650 if (lock != null) {
651
652
653 ApplicationIOUtil.close(lock, "Unable to close lock stream");
654 FileUtils.deleteQuietly(getConfiguration().getLockFile());
655 if (LOG.isDebugEnabled()) {
656 LOG.debug("Lock file released");
657 }
658
659 }
660
661 }
662
663 if (LOG.isInfoEnabled()) {
664 LOG.info("Application closed.");
665 }
666
667 }
668
669 public boolean isClosed() {
670 return closed;
671 }
672
673
674
675
676 private void addShutdownHook() {
677
678
679 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
680 @Override
681 public void run() {
682 close();
683 }
684 }));
685
686
687 DesktopPower desktopPower = Desktop.getDesktopPower();
688 if (desktopPower != null) {
689
690 desktopPower.addListener(new DesktopPower.Listener() {
691 @Override
692 public void quit() {
693 close();
694 }
695 });
696 }
697
698 }
699
700
701
702
703
704 protected void save() {
705 if (LOG.isDebugEnabled()) {
706 StringBuilder info = new StringBuilder("Save config (");
707 for (String property : PROPERTIES_TO_SAVE) {
708 info.append(property).append(": ").append(JavaBeanObjectUtil.getProperty(this, property)).append(", ");
709 }
710 info = new StringBuilder(info.substring(0, info.lastIndexOf(", ")));
711 info.append(")");
712 LOG.debug(info.toString());
713 }
714 getConfiguration().save();
715 }
716
717 }