c11cb97ec01addd90f17d456d24de9216c01bd4b
[cdm-vaadin.git] / src / main / java / eu / etaxonomy / vaadin / ui / navigation / NavigationManagerBean.java
1 /**
2 * Copyright (C) 2017 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.vaadin.ui.navigation;
10
11 import java.util.Arrays;
12 import java.util.List;
13 import java.util.Stack;
14
15 import org.apache.commons.lang3.StringUtils;
16 import org.apache.logging.log4j.LogManager;
17 import org.apache.logging.log4j.Logger;
18 import org.springframework.beans.factory.DisposableBean;
19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.context.ApplicationContext;
21 import org.springframework.context.annotation.Lazy;
22 import org.vaadin.spring.events.EventBus;
23 import org.vaadin.spring.events.annotation.EventBusListenerMethod;
24
25 import com.vaadin.navigator.ViewChangeListener;
26 import com.vaadin.navigator.ViewDisplay;
27 import com.vaadin.spring.annotation.SpringView;
28 import com.vaadin.spring.annotation.UIScope;
29 import com.vaadin.spring.navigator.SpringNavigator;
30 import com.vaadin.spring.navigator.SpringViewProvider;
31 import com.vaadin.ui.Field;
32 import com.vaadin.ui.UI;
33 import com.vaadin.ui.Window;
34
35 import eu.etaxonomy.cdm.vaadin.event.EditorActionContext;
36 import eu.etaxonomy.vaadin.mvp.AbstractEditorPresenter;
37 import eu.etaxonomy.vaadin.mvp.AbstractPopupEditor;
38 import eu.etaxonomy.vaadin.mvp.ApplicationView;
39 import eu.etaxonomy.vaadin.ui.UIInitializedEvent;
40 import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent;
41 import eu.etaxonomy.vaadin.ui.view.PopEditorOpenedEvent;
42 import eu.etaxonomy.vaadin.ui.view.PopupView;
43
44 @UIScope
45 public class NavigationManagerBean extends SpringNavigator implements NavigationManager, DisposableBean {
46
47 private static final long serialVersionUID = 6599898650948333853L;
48
49 private final static Logger logger = LogManager.getLogger();
50
51 // injecting the viewDisplay as spring bean causes problems with older cdm vaadin code
52 // SingleComponentContainerViewDisplay for example can't be used
53 // the viewDisplay should be configurable per UI therefore it seems more elegant to
54 // let the UI pass the viewDisplay to the Navigator
55 // @Autowired
56 private ViewDisplay viewDisplay;
57
58 @Autowired
59 private SpringViewProvider viewProvider;
60
61 @Autowired
62 private List<ViewChangeListener> viewChangeListeners;
63
64 @Autowired
65 protected ApplicationContext applicationContext;
66
67 protected EventBus.UIEventBus uiEventBus;
68
69 /**
70 * if set the navigator will block all other views
71 * and exclusively navigates to the error view to indicate that the
72 * UI is disabled.
73 */
74 private String uiDisabledErrorViewName = null;
75
76 @Autowired
77 protected void setViewEventBus(EventBus.UIEventBus uiEventBus){
78 this.uiEventBus = uiEventBus;
79 uiEventBus.subscribe(this);
80 }
81
82 // /**
83 // * This reference will cause the scoped PermissionDebugUtils being initialized.
84 // * It is not used in this class but attaches itself to the vaadin session
85 // * from where it will be accessible via VaadinUserHelper.fromSession()
86 // *
87 // * <b>NOTE:</b> PermissionDebugUtils is only available if the spring profile "debug" is active,
88 // * See
89 // */
90 // @Autowired(required=false)
91 // private PermissionDebugUtils permissionDebugUtils;
92
93 private PopupViewRegistration popupViewRegistration;
94
95 private String defaultViewName = null;
96
97 /*
98 * Why UriFragmentManager must be initialized lazily:
99 *
100 * when the SpringVaadinServlet usually is being instantiated the ServletUIInitHandler(UIInitHandler).getBrowserDetailsUI(VaadinRequest, VaadinSession) method is called which will
101 * first cause the WebapplicationContext being created. Once this is done the initialization of the UI classes is completed. This means that the UI classes are not readily available
102 * via Page.getCurrent() which is used in the UriFragmentManager constructor. The NavigationManagerBean is initialized with the WebapplicationContext, that is when the current ui is
103 * not yet available, therefore the UriFragmentManager must be initialized lazily.
104 */
105 @Autowired
106 @Lazy
107 private UriFragmentManager uriFragmentManager;
108
109
110 // public void setUriFragmentManager(UriFragmentManager uriFragmentManager) {
111 // this.uriFragmentManager = uriFragmentManager;
112 // }
113
114
115 public NavigationManagerBean() {
116 popupViewRegistration = new PopupViewRegistration();
117 }
118
119 // private Collection<PopupView> popupViews = new HashSet<>();
120 // @Lazy
121 // @Autowired(required=false)
122 // private void popUpViews(Collection<PopupView> popupViews){
123 // this.popupViews = popupViews;
124 // // popupViews.forEach(view -> this.popupViews.put(view.getClass(), view));
125 // }
126
127 private <P extends PopupView> P findPopupView(Class<P> type){
128 P viewBean = applicationContext.getBean(type);
129 if(viewBean == null){
130 throw new NullPointerException("no popup-view bean of type " + type.getName() + " found");
131 }
132 return viewBean;
133 // return popupViews.stream().filter(p -> p.getClass().equals(type)).findFirst();
134 }
135
136 @EventBusListenerMethod
137 protected void onUIInitialized(UIInitializedEvent e) {
138 init(UI.getCurrent(), uriFragmentManager, viewDisplay);
139 addProvider(viewProvider);
140 viewChangeListeners.forEach(vcl -> addViewChangeListener(vcl));
141 }
142
143 public void navigateTo(String navigationState, boolean fireNavigationEvent) {
144 if(getUiDisabledErrorView() != null) {
145 super.navigateTo(getUiDisabledErrorView());
146 }
147 if(StringUtils.isEmpty(navigationState)){
148 navigationState = defaultViewName;
149 }
150 if (fireNavigationEvent) {
151 navigateTo(navigationState);
152 } else {
153 super.navigateTo(navigationState);
154 }
155 }
156
157 @Override
158 public void navigateTo(String navigationState) {
159 if(getUiDisabledErrorView() != null) {
160 super.navigateTo(getUiDisabledErrorView());
161 }
162 if(StringUtils.isEmpty(navigationState)){
163 navigationState = defaultViewName;
164 }
165 super.navigateTo(navigationState);
166 //eventBus.publishEvent(new NavigationEvent(navigationState));
167 }
168
169 @EventBusListenerMethod
170 protected void onNavigationEvent(NavigationEvent e) {
171 navigateTo(e.getViewName(), false);
172 }
173
174 @Override
175 public <T extends PopupView> T showInPopup(Class<T> popupType, ApplicationView parentView, Field<?> targetField) {
176
177 PopupView popupView = findPopupView(popupType);
178
179 if(AbstractPopupEditor.class.isAssignableFrom(popupView.getClass())){
180 if(parentView instanceof AbstractPopupEditor){
181 // retain the chain of EditorActionContexts when starting a new pupupEditor
182 Stack<EditorActionContext> parentEditorActionContext = ((AbstractPopupEditor)parentView).getEditorActionContext();
183 ((AbstractPopupEditor)popupView).setParentEditorActionContext(parentEditorActionContext, targetField);
184 }
185 }
186
187 Window window = new Window();
188 window.setCaption(popupView.getWindowCaption());
189 window.center();
190 window.setResizable(popupView.isResizable());
191 // due to issue #6673 (https://dev.e-taxonomy.eu/redmine/issues/6673) popup editors must be modal!
192 //window.setModal(popupView.isModal());
193 window.setModal(true);
194 window.setCaptionAsHtml(popupView.isWindowCaptionAsHtml());
195 window.setWidth(popupView.getWindowWidth(), popupView.getWindowWidthUnit());
196 window.setHeight(popupView.getWindowHeight(), popupView.getWindowHeightUnit());
197 window.setContent(popupView.asComponent());
198 // TODO need to disallow pressing the close [x] button:
199 // since window.addCloseListener(e -> popupView.cancel()); will
200 // cause sending cancel events even if save has been clicked
201 window.setClosable(popupView.isClosable());
202 UI.getCurrent().addWindow(window);
203 popupView.viewEntered();
204 popupView.focusFirst();
205 uiEventBus.publish(this, new PopEditorOpenedEvent(this, popupView));
206
207 popupViewRegistration.put(window, parentView, popupView, targetField);
208
209 return (T) popupView;
210 }
211
212 @Override
213 public Field<?> targetFieldOf(PopupView popupView){
214 return popupViewRegistration.get(popupView);
215 }
216
217 @EventBusListenerMethod
218 protected void onDoneWithTheEditor(DoneWithPopupEvent event) {
219
220 PopupView popup = event.getPopup();
221 if(DisposableBean.class.isAssignableFrom(popup.getClass())){
222 try {
223 ((DisposableBean)popup).destroy();
224 } catch (Exception e) {
225 logger.error(e);
226 }
227 }
228 Window window = popupViewRegistration.getWindow(popup);
229 if (window != null) {
230 window.close();
231 popupViewRegistration.remove(popup);
232 }
233 if(AbstractPopupEditor.class.isAssignableFrom(popup.getClass())){
234 ((AbstractPopupEditor)popup).presenter().unsubscribeFromEventBuses();
235 }
236
237 }
238
239 /**
240 * {@inheritDoc}
241 */
242 @Override
243 public void reloadCurrentView() {
244 if(logger.isTraceEnabled()){
245 logger.trace("reloading " + getState());
246 }
247 navigateTo(getState(), false);
248 }
249
250 /**
251 * This method requires that the {@SpringView} annotation is used to set the name of the <code>View</code>.
252 *
253 * @return the current view name or <code>null</code>
254 */
255 @Override
256 public String getCurrentViewName() {
257 if(getCurrentView() != null){
258 SpringView springViewAnnotation = getCurrentView().getClass().getAnnotation(SpringView.class);
259 if(springViewAnnotation != null){
260 return springViewAnnotation.name();
261 }
262 }
263 return null;
264 }
265
266 @Override
267 public List<String> getCurrentViewParameters(){
268 String substate = getState();
269 String currentViewName = getCurrentViewName();
270 if(currentViewName != null){
271 substate = substate.replaceAll("^" + currentViewName + "/?", "");
272
273 }
274 return Arrays.asList(substate.split("/"));
275 }
276
277 /**
278 * {@inheritDoc}
279 */
280 @Override
281 public List<AbstractEditorPresenter<?, ?>> getPopupEditorPresenters() {
282 // TODO Auto-generated method stub
283 return null;
284 }
285
286 /**
287 * @return the defaultViewName
288 */
289 public String getDefaultViewName() {
290 return defaultViewName;
291 }
292
293 /**
294 * @param defaultViewName the defaultViewName to set
295 */
296 public void setDefaultViewName(String defaultViewName) {
297 this.defaultViewName = defaultViewName;
298 }
299
300 public void setViewDisplay(ViewDisplay viewDisplay){
301 this.viewDisplay = viewDisplay;
302 }
303
304 /**
305 * implementation of the interface {@link DisposableBean} and overrides the
306 * method in {@link Navigator} which is not an implementation of {@link DisposableBean}
307 * in this class.
308 */
309 @Override
310 public void destroy() {
311 super.destroy();
312 uiEventBus.unsubscribe(this);
313 popupViewRegistration = null;
314 // release the reference to the view kept in currentView
315 // which could be expensive
316 // by navigating to the default view
317 navigateTo(defaultViewName);
318 }
319
320 public String getUiDisabledErrorView() {
321 return uiDisabledErrorViewName;
322 }
323
324 public void setUiDisabledErrorView(String uiDisabledErrorViewName) {
325 this.uiDisabledErrorViewName = uiDisabledErrorViewName;
326 }
327 }