-
Notifications
You must be signed in to change notification settings - Fork 0
/
UriFragmentActionNavigatorWrapper.java
262 lines (245 loc) · 15.2 KB
/
UriFragmentActionNavigatorWrapper.java
1
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
package org.vaadin.uriactions;
import com.vaadin.navigator.NavigationStateManager;
import com.vaadin.navigator.Navigator;
import com.vaadin.navigator.Navigator.ComponentContainerViewDisplay;
import com.vaadin.navigator.Navigator.SingleComponentContainerViewDisplay;
import com.vaadin.navigator.View;
import com.vaadin.navigator.ViewChangeListener.ViewChangeEvent;
import com.vaadin.navigator.ViewDisplay;
import com.vaadin.navigator.ViewProvider;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.SingleComponentContainer;
import com.vaadin.ui.UI;
import org.roklib.urifragmentrouting.UriActionCommand;
import org.roklib.urifragmentrouting.UriActionMapperTree;
/**
* Wrapper class around a Vaadin {@link Navigator} which adds the option to use URI fragment actions with an externally
* defined {@link UriActionMapperTree}. The {@link Navigator} can be configured and used as usual by adding views with
* {@link Navigator#addView(String, Class)} and the overloaded variant thereof. The reference on the wrapped {@link
* Navigator} can be obtained with {@link #getNavigator()}. The returned navigator object can then be configured and
* used as usual.
* <p>
* In addition to that, you can set a preconfigured {@link UriActionMapperTree} with {@link
* #setUriActionMapperTree(UriActionMapperTree)}. Note that this is an optional operation. If no such object is passed
* to this wrapper, the wrapped navigator works just like a conventional {@link Navigator}. In that case, however, a
* plain {@link Navigator} object should be used.
* <p>
* When a {@link UriActionMapperTree} object has been passed to this wrapper, all URI fragments defined by this {@link
* UriActionMapperTree} will be handled by the wrapped navigator. This means that if the navigator handles a URI
* fragment which resolves to a {@link UriActionCommand} class as specified by the {@link UriActionMapperTree}, then
* this command object will be executed by the navigator. More specifically, the action command will be executed in the
* {@link View#enter(ViewChangeEvent)} method of a specific implementation of the {@link View} interface: {@link
* ActionExecutionView}. The sole purpose of this class is to execute the resolved {@link UriActionCommand}s in its
* {@link ActionExecutionView#enter(ViewChangeEvent)} method. <h1>The {@link UriActionMapperTree}</h1> Using a {@link
* UriActionMapperTree} you can define a hierarchical, path-like structure of URI fragments which can be resolved to
* command objects. Each segment of such a URI fragment can have an arbitrary number of parameters which will be
* automatically interpreted and converted into their respective domain type. For example the action command responsible
* for the URI fragment {@code /address/showOnMap/lon/49.508220/lat/8.523510} could show a map which is centered at the
* coordinates given as a URI parameter. Parameter value conversion is transparently taken care of by the API so the
* developer will directly work with a two-dimensional coordinate object instead of two Strings. <h1>Accessing the
* current {@link UriActionCommand} object</h1> It may be necessary for an application to access the {@link
* UriActionCommand} object for the currently interpreted URI fragment. This object is not directly accessible. It can
* be accessed, however, with a {@link com.vaadin.navigator.ViewChangeListener ViewChangeListener} added to the wrapped
* {@link Navigator}. In the listener's methods {@link com.vaadin.navigator.ViewChangeListener#beforeViewChange(ViewChangeEvent)
* ViewChangeListener#beforeViewChange(ViewChangeEvent)} and {@link com.vaadin.navigator.ViewChangeListener#afterViewChange(ViewChangeEvent)
* ViewChangeListener#afterViewChange(ViewChangeEvent)} you can check whether the new or old view is of type {@link
* ActionExecutionView} and if so cast it into this class. You can then retrieve the current {@link UriActionCommand}
* with {@link ActionExecutionView#getUriActionCommand()}. Refer to the following example code:
* <pre>
* uriFragmentActionNavigatorWrapper.getNavigator().addViewChangeListener(new ViewChangeListener() {
* public boolean beforeViewChange(final ViewChangeEvent event) {
* return true;
* }
*
* public void afterViewChange(final ViewChangeEvent event) {
* if (event.getNewView() instanceof ActionExecutionView) {
* final ActionExecutionView view = (ActionExecutionView) event.getNewView();
* final UriActionCommand uriActionCommand = (UriActionCommand) view.getUriActionCommand();
* // do something with the command object
* }
* }
* });
* </pre>
* <h1>The Routing Context</h1> The routing context is an arbitrary user-defined object which can be passed into the URI
* fragment interpretation process of the {@link UriActionMapperTree}. This object can be injected into the resolved
* {@link UriActionCommand} with a method annotated with {@link org.roklib.urifragmentrouting.annotation.RoutingContext
* RoutingContext}. By that, this context object can be accessed and used by the action command objects when they are
* executed. The routing context can be used, for instance, to provide the action commands with references to service
* classes, such as the current event bus etc. <h1>Using an alternative {@link ViewDisplay}</h1> This wrapper class
* provides several constructors, which relate directly to the various overloaded constructors of class {@link
* Navigator}. Using these constructors, you can add an alternative {@link ViewDisplay} to the wrapped navigator which
* will be used in parallel with the {@link UriActionMapperTree}. If the current URI fragment could not be successfully
* resolved by the {@link UriActionMapperTree}, the navigator tries to resolve it with the alternative {@link
* ViewProvider} which is automatically installed when an extra {@link ViewDisplay} is set on the navigator. Using this
* technique, you can still add views in the customary Vaadin-style where this very simple view resolution is
* sufficient. Using the {@link UriActionMapperTree} can then be reserved for the more complex cases where the standard
* Vaadin mechanism is not flexible enough.
*
* @see UriActionMapperTree
* @see UriActionCommand
* @see org.roklib.urifragmentrouting.annotation.RoutingContext
*/
public class UriFragmentActionNavigatorWrapper {
/**
* The wrapped {@link Navigator}.
*/
private final Navigator navigator;
private UriActionMapperTree uriActionMapperTree;
private Object routingContext;
/**
* Constructs a new navigator wrapper for the given {@link UI} object. When this constructor is used, the wrapped
* {@link Navigator} will only be able to handle URI fragments with a given {@link UriActionMapperTree}. No fallback
* {@link ViewDisplay} is used.
*
* @param ui the current {@link UI}
*/
public UriFragmentActionNavigatorWrapper(final UI ui) {
this(ui, null, null);
}
/**
* Constructs a new navigator wrapper for the given {@link UI} object.
* <p>
* This constructor allows to specify a {@link ComponentContainer} which will be wrapped by a {@link
* ComponentContainerViewDisplay} and will be used to display any {@link View} which has not been determined by the
* {@link UriActionMapperTree} but instead by any additional {@link ViewProvider} added to the wrapped navigator
* with {@link Navigator#addProvider(ViewProvider)}, {@link Navigator#addView(String, Class)}, or {@link
* Navigator#addView(String, View)}.
*
* @param ui the current {@link UI}
* @param container a {@link ComponentContainer} which will be wrapped by a {@link ComponentContainerViewDisplay}.
* This view display is used as an alternative {@link ViewDisplay} (see {@link
* #UriFragmentActionNavigatorWrapper(UI, NavigationStateManager, ViewDisplay)}.
*/
public UriFragmentActionNavigatorWrapper(final UI ui, final ComponentContainer container) {
this(ui, null, new ComponentContainerViewDisplay(container));
}
/**
* Constructs a new navigator wrapper for the given {@link UI} object.
* <p>
* This constructor allows to specify a {@link SingleComponentContainer} which will be wrapped by a {@link
* SingleComponentContainerViewDisplay} and will be used to display any {@link View} which has not been determined
* by the {@link UriActionMapperTree} but instead by any additional {@link ViewProvider} added to the wrapped
* navigator with {@link Navigator#addProvider(ViewProvider)}, {@link Navigator#addView(String, Class)}, or {@link
* Navigator#addView(String, View)}.
*
* @param ui the current {@link UI}
* @param container a {@link SingleComponentContainer} which will be wrapped by a {@link
* SingleComponentContainerViewDisplay}. This view display is used as an alternative {@link
* ViewDisplay} (see {@link #UriFragmentActionNavigatorWrapper(UI, NavigationStateManager,
* ViewDisplay)}.
*/
public UriFragmentActionNavigatorWrapper(final UI ui, final SingleComponentContainer container) {
this(ui, null, new SingleComponentContainerViewDisplay(container));
}
/**
* Constructs a new navigator wrapper for the given {@link UI} object and {@link NavigationStateManager}. If the
* given {@link NavigationStateManager} is not {@code null}, this will create the wrapped navigator with the
* constructor {@link Navigator#Navigator(UI, NavigationStateManager, ViewDisplay)}. Otherwise, the constructor
* {@link Navigator#Navigator(UI, ViewDisplay)} is used to create the navigator.
*
* @param ui the current {@link UI}
* @param navigationStateManager the {@link NavigationStateManager} keeping track of the active view and enabling
* bookmarking and direct navigation. May be {@code null} to use the default
* navigation state manager.
* @param viewDisplay alternative {@link ViewDisplay} to be used by the navigator. May be {@code null}.
* If an alternative {@link ViewDisplay} is given, this will be used to display any
* {@link View} which has not been determined by the {@link UriActionMapperTree} but
* instead by any additional {@link ViewProvider} added to the wrapped navigator with
* {@link Navigator#addProvider(ViewProvider)}, {@link Navigator#addView(String,
* Class)}, or {@link Navigator#addView(String, View)}.
*/
public UriFragmentActionNavigatorWrapper(final UI ui, final NavigationStateManager navigationStateManager, final ViewDisplay viewDisplay) {
final UriActionViewDisplay uriActionViewDisplay = new UriActionViewDisplay(viewDisplay);
if (navigationStateManager != null) {
navigator = new Navigator(ui, navigationStateManager, uriActionViewDisplay);
} else {
navigator = new Navigator(ui, uriActionViewDisplay);
}
navigator.addProvider(new UriActionViewProvider());
}
/**
* Provides the wrapped {@link Navigator} object.
*
* @return the wrapped {@link Navigator} object.
*/
public Navigator getNavigator() {
return navigator;
}
/**
* Sets the {@link UriActionMapperTree} object for this wrapper which defines the complete hierarchical and
* parameterizable URI fragment structure handled by the wrapped {@link Navigator}.
*
* @param actionMapperTree the {@link UriActionMapperTree} for this wrapper which defines the complete URI fragment
* structure handled by the navigator
*/
public void setUriActionMapperTree(final UriActionMapperTree actionMapperTree) {
uriActionMapperTree = actionMapperTree;
}
/**
* Sets the routing context object to be used for the URI fragment interpretation process. This object can be passed
* into the {@link UriActionCommand} objects executed by the navigator.
*
* @param routingContext the routing context object
* @see org.roklib.urifragmentrouting.annotation.RoutingContext
*/
public void setRoutingContext(final Object routingContext) {
this.routingContext = routingContext;
}
/**
* {@link ViewDisplay} which delegates the task to display the current {@link View} to a wrapped {@link ViewDisplay}
* if the {@link View} to be shown is <em>not</em> of type {@link ActionExecutionView}. The wrapped {@link
* ViewDisplay} can be provided by one of the constructors which accepts either a {@link ComponentContainer},
* a {@link SingleComponentContainer}, or a {@link ViewDisplay}.
*/
private class UriActionViewDisplay implements ViewDisplay {
private final ViewDisplay userProvidedDisplay;
public UriActionViewDisplay(final ViewDisplay userProvidedDisplay) {
this.userProvidedDisplay = userProvidedDisplay;
}
@Override
public void showView(final View view) {
if (userProvidedDisplay != null && !(view instanceof ActionExecutionView)) {
userProvidedDisplay.showView(view);
}
// Otherwise there is nothing to do in this case. Action command is executed in ActionExecutionView.enter().
}
}
/**
* {@link ViewProvider} which resolves the current URI fragment against the current {@link UriActionMapperTree} and
* creates a {@link ActionExecutionView} object with the corresponding {@link UriActionCommand} object if the
* fragment could successfully be resolved.
*/
private class UriActionViewProvider implements ViewProvider {
private ActionExecutionView currentView;
private String currentNavigationState;
@Override
public String getViewName(final String viewAndParameters) {
if (uriActionMapperTree == null) {
return null;
}
if (currentView != null && currentNavigationState != null) {
if (currentNavigationState.equals(viewAndParameters)) {
return viewAndParameters;
} else {
throw new IllegalStateException(
"Access synchronization problem: this action navigator is currently handling another request. " +
"Currently handled navigation state is: " + currentNavigationState +
". Current action is: " + currentView.getUriActionCommand());
}
}
final UriActionCommand action = uriActionMapperTree.interpretFragment(viewAndParameters, routingContext, false);
if (action != null) {
currentView = new ActionExecutionView(action);
currentNavigationState = viewAndParameters;
}
return action != null ? viewAndParameters : null;
}
@Override
public View getView(final String viewName) {
View returnedView = currentView;
currentNavigationState = null;
currentView = null;
return returnedView;
}
}
}