-
Notifications
You must be signed in to change notification settings - Fork 161
/
Workbench.java
336 lines (308 loc) · 14 KB
/
Workbench.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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.uberfire.client.workbench;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Window.ClosingEvent;
import com.google.gwt.user.client.Window.ClosingHandler;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import org.jboss.errai.bus.client.api.ClientMessageBus;
import org.jboss.errai.bus.client.framework.ClientMessageBusImpl;
import org.jboss.errai.ioc.client.api.AfterInitialization;
import org.jboss.errai.ioc.client.api.EnabledByProperty;
import org.jboss.errai.ioc.client.api.EntryPoint;
import org.jboss.errai.ioc.client.container.SyncBeanDef;
import org.jboss.errai.ioc.client.container.SyncBeanManager;
import org.jboss.errai.security.shared.api.identity.User;
import org.slf4j.Logger;
import org.uberfire.backend.vfs.Path;
import org.uberfire.client.mvp.PerspectiveActivity;
import org.uberfire.client.mvp.PlaceManager;
import org.uberfire.client.resources.WorkbenchResources;
import org.uberfire.client.workbench.events.ApplicationReadyEvent;
import org.uberfire.mvp.ParameterizedCommand;
import org.uberfire.mvp.impl.DefaultPlaceRequest;
import org.uberfire.mvp.impl.PathPlaceRequest;
import org.uberfire.rpc.SessionInfo;
import org.uberfire.rpc.impl.SessionInfoImpl;
import org.uberfire.security.authz.AuthorizationManager;
import org.uberfire.security.authz.AuthorizationPolicy;
import org.uberfire.security.authz.PermissionManager;
/**
* Responsible for bootstrapping the client-side Workbench user interface by coordinating calls to the PanelManager and
* PlaceManager. Normally this happens automatically with no need for assistance or interference from the application.
* Thus, applications don't usually need to do anything with the Workbench class directly.
* <p>
* <h2>Delaying Workbench Startup</h2>
* <p>
* In special cases, applications may wish to delay the startup of the workbench. For example, an application that
* relies on global variables (also known as singletons or Application Scoped beans) that are initialized based on
* response data from the server doesn't want UberFire to start initializing its widgets until that server response has
* come in.
* <p>
* To delay startup, add a <i>Startup Blocker</i> before Errai starts calling {@link AfterInitialization} methods. The
* best place to do this is in the {@link PostConstruct} method of an {@link EntryPoint} bean. You would then remove the
* startup blocker from within the callback from the server:
* <p>
* <pre>
* {@code @EntryPoint}
* public class MyMutableGlobal() {
* {@code @Inject private Workbench workbench;}
* {@code @Inject private Caller<MyRemoteService> remoteService;}
*
* // set up by a server call. don't start the app until it's populated!
* {@code private MyParams params;}
*
* {@code @PostConstruct}
* private void earlyInit() {
* workbench.addStartupBlocker(MyMutableGlobal.class);
* }
*
* {@code @AfterInitialization}
* private void lateInit() {
* remoteService.call(new {@code RemoteCallback<MyParams>}{
* public void callback(MyParams params) {
* MyMutableGlobal.this.params = params;
* workbench.removeStartupBlocker(MyMutableGlobal.class);
* }
* }).fetchParameters();
* }
* }
* </pre>
*/
@EntryPoint
@EnabledByProperty(value = "uberfire.plugin.mode.active", negated = true)
public class Workbench {
/**
* List of classes who want to do stuff (often server communication) before the workbench shows up.
*/
private final Set<Class<?>> startupBlockers = new HashSet<>();
private final Set<String> headersToKeep = new HashSet<>();
/**
* This indirection exists so we can ignore spurious WindowCloseEvents in IE10.
* In all other cases, the {@link WorkbenchCloseHandler} simply executes whatever command we pass it.
*/
private final WorkbenchCloseHandler workbenchCloseHandler = GWT.create(WorkbenchCloseHandler.class);
@Inject
LayoutSelection layoutSelection;
/**
* Fired when all startup blockers have cleared and just before the workbench starts to build its components.
*/
@Inject
private Event<ApplicationReadyEvent> appReady;
private boolean isStandaloneMode = false;
@Inject
private SyncBeanManager iocManager;
@Inject
private PlaceManager placeManager;
private final Command workbenchCloseCommand = new Command() {
@Override
public void execute() {
placeManager.closeAllPlaces(); // would be preferable to close current perspective, which should be recursive
}
};
@Inject
private PermissionManager permissionManager;
@Inject
private AuthorizationManager authorizationManager;
@Inject
private VFSServiceProxy vfsService;
private WorkbenchLayout layout;
@Inject
private User identity;
@Inject
private ClientMessageBus bus;
@Inject
private Logger logger;
private SessionInfo sessionInfo = null;
/**
* Requests that the workbench does not attempt to create any UI parts until the given responsible party has
* been removed as a startup blocker. Blockers are tracked as a set, so adding the same class more than once has no
* effect.
* @param responsibleParty any Class object; typically it will be the class making the call to this method.
* Must not be null.
*/
public void addStartupBlocker(Class<?> responsibleParty) {
startupBlockers.add(responsibleParty);
logger.info(responsibleParty.getName() + " is blocking workbench startup.");
}
/**
* Causes the given responsible party to no longer block workbench initialization.
* If the given responsible party was not already in the blocking set (either because
* it was never added, or it has already been removed) then the method call has no effect.
* <p>
* After removing the blocker, if there are no more blockers left in the blocking set, the workbench UI is
* bootstrapped immediately. If there are still one or more blockers left in the blocking set, the workbench UI
* remains uninitialized.
* @param responsibleParty any Class object that was previously passed to {@link #addStartupBlocker(Class)}.
* Must not be null.
*/
public void removeStartupBlocker(Class<?> responsibleParty) {
if (startupBlockers.remove(responsibleParty)) {
logger.info(responsibleParty.getName() + " is no longer blocking startup.");
} else {
logger.info(responsibleParty.getName() + " tried to unblock startup, but it wasn't blocking to begin with!");
}
startIfNotBlocked();
}
// package-private so tests can call in
void startIfNotBlocked() {
logger.info(startupBlockers.size() + " workbench startup blockers remain.");
if (startupBlockers.isEmpty()) {
bootstrap();
}
}
@AfterInitialization
private void afterInit() {
removeStartupBlocker(Workbench.class);
}
@PostConstruct
private void earlyInit() {
layout = layoutSelection.get();
WorkbenchResources.INSTANCE.CSS().ensureInjected();
Map<String, List<String>> windowParamMap = Window.Location.getParameterMap();
isStandaloneMode = windowParamMap.containsKey("standalone");
List<String> headers = windowParamMap.getOrDefault("header", Collections.emptyList());
headersToKeep.addAll(headers);
addStartupBlocker(Workbench.class);
}
private void bootstrap() {
logger.info("Starting workbench...");
((SessionInfoImpl) currentSession()).setId(((ClientMessageBusImpl) bus).getSessionId());
layout.setMarginWidgets(isStandaloneMode,
headersToKeep);
layout.onBootstrap();
addLayoutToRootPanel(layout);
//Lookup PerspectiveProviders and if present launch it to set-up the Workbench
if (!isStandaloneMode) {
final PerspectiveActivity homePerspective = getHomePerspectiveActivity();
if (homePerspective != null) {
appReady.fire(new ApplicationReadyEvent());
placeManager.goTo(new DefaultPlaceRequest(homePerspective.getIdentifier()));
} else {
logger.error("No home perspective available!");
}
} else {
handleStandaloneMode(Window.Location.getParameterMap());
}
// Ensure orderly shutdown when Window is closed (eg. saves workbench state)
Window.addWindowClosingHandler(new ClosingHandler() {
@Override
public void onWindowClosing(ClosingEvent event) {
workbenchCloseHandler.onWindowClose(workbenchCloseCommand);
}
});
// Resizing the Window should resize everything
Window.addResizeHandler(new ResizeHandler() {
@Override
public void onResize(ResizeEvent event) {
layout.resizeTo(event.getWidth(),
event.getHeight());
}
});
// Defer the initial resize call until widgets are rendered and sizes are available
Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
@Override
public void execute() {
layout.onResize();
}
});
}
// TODO add tests for standalone startup vs. full startup
private void handleStandaloneMode(final Map<String, List<String>> parameters) {
if (parameters.containsKey("perspective") && !parameters.get("perspective").isEmpty()) {
placeManager.goTo(new DefaultPlaceRequest(parameters.get("perspective").get(0)));
} else if (parameters.containsKey("path") && !parameters.get("path").isEmpty()) {
placeManager.goTo(new DefaultPlaceRequest("StandaloneEditorPerspective"));
vfsService.get(parameters.get("path").get(0),
new ParameterizedCommand<Path>() {
@Override
public void execute(final Path response) {
if (parameters.containsKey("editor") && !parameters.get("editor").isEmpty()) {
placeManager.goTo(new PathPlaceRequest(response,
parameters.get("editor").get(0)));
} else {
placeManager.goTo(new PathPlaceRequest(response));
}
}
});
}
}
/**
* Get the home perspective defined at the workbench authorization policy.
* <p>
* <p>If no home is defined then the perspective marked as "{@code isDefault=true}" is taken.</p>
* <p>
* <p>Notice that access permission over the selected perspective is always required.</p>
* @return A perspective instance or null if no perspective is found or access to it has been denied.
*/
public PerspectiveActivity getHomePerspectiveActivity() {
// Get the user's home perspective
PerspectiveActivity homePerspective = null;
AuthorizationPolicy authPolicy = permissionManager.getAuthorizationPolicy();
String homePerspectiveId = authPolicy.getHomePerspective(identity);
// Get the workbench's default perspective
PerspectiveActivity defaultPerspective = null;
final Collection<SyncBeanDef<PerspectiveActivity>> perspectives = iocManager.lookupBeans(PerspectiveActivity.class);
for (final SyncBeanDef<PerspectiveActivity> perspective : perspectives) {
final PerspectiveActivity instance = perspective.getInstance();
if (homePerspectiveId != null && homePerspectiveId.equals(instance.getIdentifier())) {
homePerspective = instance;
if (defaultPerspective != null) {
iocManager.destroyBean(defaultPerspective);
}
} else if (instance.isDefault()) {
defaultPerspective = instance;
} else {
iocManager.destroyBean(instance);
}
}
// The home perspective has always priority over the default
PerspectiveActivity targetPerspective = homePerspective != null ? homePerspective : defaultPerspective;
// Check access rights
if (targetPerspective != null && authorizationManager.authorize(targetPerspective,
identity)) {
return targetPerspective;
}
return null;
}
@Produces
@ApplicationScoped
private SessionInfo currentSession() {
if (sessionInfo == null) {
sessionInfo = new SessionInfoImpl(identity);
}
return sessionInfo;
}
void addLayoutToRootPanel(final WorkbenchLayout layout) {
RootLayoutPanel.get().add(layout.getRoot());
}
}