]> git.argeo.org Git - gpl/argeo-suite.git/blob - argeo/suite/ui/SuiteApp.java
Prepare next development cycle
[gpl/argeo-suite.git] / argeo / suite / ui / SuiteApp.java
1 package org.argeo.suite.ui;
2
3 import static org.argeo.cms.ui.CmsView.CMS_VIEW_UID_PROPERTY;
4
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11 import java.util.TreeMap;
12 import java.util.TreeSet;
13
14 import javax.jcr.Node;
15 import javax.jcr.RepositoryException;
16 import javax.jcr.Session;
17 import javax.jcr.nodetype.NodeType;
18 import javax.naming.InvalidNameException;
19 import javax.naming.ldap.LdapName;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23 import org.argeo.api.NodeUtils;
24 import org.argeo.cms.CmsUserManager;
25 import org.argeo.cms.auth.CmsSession;
26 import org.argeo.cms.ui.AbstractCmsApp;
27 import org.argeo.cms.ui.CmsTheme;
28 import org.argeo.cms.ui.CmsUiProvider;
29 import org.argeo.cms.ui.CmsView;
30 import org.argeo.cms.ui.dialogs.CmsFeedback;
31 import org.argeo.cms.ui.util.CmsEvent;
32 import org.argeo.cms.ui.util.CmsUiUtils;
33 import org.argeo.entity.EntityConstants;
34 import org.argeo.entity.EntityNames;
35 import org.argeo.entity.EntityType;
36 import org.argeo.jcr.Jcr;
37 import org.argeo.suite.RankedObject;
38 import org.argeo.suite.SuiteUtils;
39 import org.argeo.util.LangUtils;
40 import org.eclipse.swt.SWT;
41 import org.eclipse.swt.widgets.Composite;
42 import org.osgi.framework.Constants;
43 import org.osgi.service.event.Event;
44 import org.osgi.service.event.EventHandler;
45 import org.osgi.service.useradmin.User;
46
47 /** The Argeo Suite App. */
48 public class SuiteApp extends AbstractCmsApp implements EventHandler {
49 private final static Log log = LogFactory.getLog(SuiteApp.class);
50
51 public final static String PID_PREFIX = "argeo.suite.ui.";
52 public final static String HEADER_PID = PID_PREFIX + "header";
53 public final static String LEAD_PANE_PID = PID_PREFIX + "leadPane";
54 public final static String LOGIN_SCREEN_PID = PID_PREFIX + "loginScreen";
55 public final static String DASHBOARD_PID = PID_PREFIX + "dashboard";
56 public final static String RECENT_ITEMS_PID = PID_PREFIX + "recentItems";
57
58 private final static String DEFAULT_UI_NAME = "app";
59 private final static String DEFAULT_THEME_ID = "org.argeo.suite.theme.default";
60
61 private Map<String, RankedObject<CmsUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
62 private Map<String, RankedObject<CmsUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
63 private Map<String, RankedObject<SuiteLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
64 private Map<String, RankedObject<SuiteLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
65
66 private CmsUserManager cmsUserManager;
67
68 // TODO make more optimal or via CmsSession/CmsView
69 private Map<String, SuiteUi> managedUis = new HashMap<>();
70
71 // private CmsUiProvider headerPart = null;
72
73 public void init(Map<String, String> properties) {
74 if (log.isDebugEnabled())
75 log.info("Argeo Suite App started");
76 }
77
78 public void destroy(Map<String, String> properties) {
79 for (SuiteUi ui : managedUis.values())
80 if (!ui.isDisposed())
81 ui.dispose();
82 if (log.isDebugEnabled())
83 log.info("Argeo Suite App stopped");
84
85 }
86
87 @Override
88 public Set<String> getUiNames() {
89 HashSet<String> uiNames = new HashSet<>();
90 uiNames.add(DEFAULT_UI_NAME);
91 return uiNames;
92 }
93
94 @Override
95 public Composite initUi(Composite parent) {
96 String uiName = parent.getData(UI_NAME_PROPERTY) != null ? parent.getData(UI_NAME_PROPERTY).toString() : null;
97 CmsView cmsView = CmsView.getCmsView(parent);
98 if (cmsView == null)
99 throw new IllegalStateException("No CMS view is registered.");
100 CmsTheme theme = getTheme(uiName);
101 if (theme != null)
102 CmsTheme.registerCmsTheme(parent.getShell(), theme);
103 SuiteUi argeoSuiteUi = new SuiteUi(parent, SWT.NONE);
104 String uid = cmsView.getUid();
105 managedUis.put(uid, argeoSuiteUi);
106 argeoSuiteUi.addDisposeListener((e) -> {
107 managedUis.remove(uid);
108 if (log.isDebugEnabled())
109 log.debug("Suite UI " + uid + " has been disposed.");
110 });
111 refreshUi(argeoSuiteUi, null);
112 return argeoSuiteUi;
113 }
114
115 @Override
116 public String getThemeId(String uiName) {
117 // TODO make it configurable
118 return DEFAULT_THEME_ID;
119 }
120
121 @Override
122 public void refreshUi(Composite parent, String state) {
123 try {
124 Node context = null;
125 SuiteUi ui = (SuiteUi) parent;
126 refreshPart(findUiProvider(HEADER_PID), ui.getHeader(), context);
127 CmsView cmsView = CmsView.getCmsView(parent);
128 if (cmsView.isAnonymous()) {
129 ui.logout();
130 ui.refreshBelowHeader(false);
131 refreshPart(findUiProvider(LOGIN_SCREEN_PID), ui.getBelowHeader(), context);
132 ui.layout(true, true);
133 } else {
134 if (ui.getUserDir() == null) {
135 CmsSession cmsSession = cmsView.getCmsSession();
136 Session adminSession = null;
137 try {
138 adminSession = NodeUtils.openDataAdminSession(getRepository(), null);
139 Node userDir = SuiteUtils.getOrCreateSessionDir(adminSession, cmsSession);
140 ui.initSessions(getRepository(), userDir.getPath());
141 } finally {
142 Jcr.logout(adminSession);
143 }
144 }
145 context = stateToNode(ui, state);
146 if (context == null)
147 context = ui.getUserDir();
148
149 ui.refreshBelowHeader(true);
150 for (String key : layersByPid.keySet()) {
151 SuiteLayer layer = layersByPid.get(key).get();
152 ui.addLayer(key, layer);
153 }
154 refreshPart(findUiProvider(LEAD_PANE_PID), ui.getLeadPane(), context);
155 ui.layout(true, true);
156 setState(parent, state);
157 }
158 } catch (Exception e) {
159 CmsFeedback.show("Unexpected exception", e);
160 }
161 }
162
163 private void refreshPart(CmsUiProvider uiProvider, Composite part, Node context) {
164 CmsUiUtils.clear(part);
165 uiProvider.createUiPart(part, context);
166 }
167
168 private CmsUiProvider findUiProvider(String pid) {
169 if (!uiProvidersByPid.containsKey(pid))
170 throw new IllegalArgumentException("No UI provider registered as " + pid);
171 return uiProvidersByPid.get(pid).get();
172 }
173
174 private <T> T findByType(Map<String, RankedObject<T>> byType, Node context) {
175 try {
176 // mixins
177 Set<String> types = new TreeSet<>();
178 for (NodeType nodeType : context.getMixinNodeTypes()) {
179 String typeName = nodeType.getName();
180 if (byType.containsKey(typeName)) {
181 types.add(typeName);
182 }
183 }
184 // primary node type
185 {
186 NodeType nodeType = context.getPrimaryNodeType();
187 String typeName = nodeType.getName();
188 if (byType.containsKey(typeName)) {
189 types.add(typeName);
190 }
191 for (NodeType mixin : nodeType.getDeclaredSupertypes()) {
192 if (byType.containsKey(mixin.getName())) {
193 types.add(mixin.getName());
194 }
195 }
196 }
197 // entity type
198 if (context.isNodeType(EntityType.entity.get())) {
199 if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
200 String typeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
201 if (byType.containsKey(typeName)) {
202 types.add(typeName);
203 }
204 }
205 }
206
207 // if (context.getPath().equals("/")) {// root node
208 // types.add("nt:folder");
209 // }
210 if (NodeUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
211 types.add("nt:folder");
212 }
213
214 if (types.size() == 0)
215 throw new IllegalArgumentException("No type found for " + context);
216 String type = types.iterator().next();
217 if (!byType.containsKey(type))
218 throw new IllegalArgumentException("No component found for " + context + " with type " + type);
219 return byType.get(type).get();
220 } catch (RepositoryException e) {
221 throw new IllegalStateException(e);
222 }
223 }
224
225 @Override
226 public void setState(Composite parent, String state) {
227 if (state == null || !state.startsWith("/"))
228 return;
229 SuiteUi suiteUi = (SuiteUi) parent;
230 Node node = stateToNode(suiteUi, state);
231 if (node == null) {
232 suiteUi.getCmsView().navigateTo("~");
233 } else {
234 suiteUi.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
235 suiteUi.getCmsView().sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
236 }
237
238 // CmsView cmsView = CmsView.getCmsView(parent);
239 // if (cmsView.isAnonymous())
240 // return;
241 // Session session = null;
242 // try {
243 // if (state != null && state.startsWith("/")) {
244 // String path = state.substring(1);
245 // String workspace;
246 // if (path.equals("")) {
247 // workspace = null;
248 // path = "/";
249 // } else {
250 // int index = path.indexOf('/');
251 // if (index == 0) {
252 // log.error("Cannot interpret " + state);
253 // cmsView.navigateTo("~");
254 // return;
255 // } else if (index > 0) {
256 // workspace = path.substring(0, index);
257 // path = path.substring(index);
258 // } else {// index<0, assuming root node
259 // workspace = path;
260 // path = "/";
261 // }
262 // }
263 // session = cmsView.doAs(() -> Jcr.login(getRepository(), workspace));
264 //
265 // Node node = session.getNode(path);
266 //
267 // cmsView.sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
268 // cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
269 // }
270 // } catch (RepositoryException e) {
271 // log.error("Cannot load state " + state, e);
272 // cmsView.navigateTo("~");
273 // } finally {
274 // JcrUtils.logoutQuietly(session);
275 // }
276 }
277
278 private String nodeToState(Node node) {
279 return '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
280 }
281
282 private Node stateToNode(SuiteUi suiteUi, String state) {
283 if (suiteUi == null)
284 return null;
285 if (state == null)
286 return null;
287
288 String path = state.substring(1);
289 String workspace;
290 if (path.equals("")) {
291 workspace = null;
292 path = "/";
293 } else {
294 int index = path.indexOf('/');
295 if (index == 0) {
296 log.error("Cannot interpret " + state);
297 // cmsView.navigateTo("~");
298 return null;
299 } else if (index > 0) {
300 workspace = path.substring(0, index);
301 path = path.substring(index);
302 } else {// index<0, assuming root node
303 workspace = path;
304 path = "/";
305 }
306 }
307 // session = cmsView.doAs(() -> Jcr.login(getRepository(), workspace));
308
309 Session session = suiteUi.getSession(workspace);
310 if (session == null)
311 return null;
312 Node node = Jcr.getNode(session, path);
313 return node;
314 }
315
316 /*
317 * Events management
318 */
319
320 @Override
321 public void handleEvent(Event event) {
322
323 // Specific UI related events
324 SuiteUi ui = getRelatedUi(event);
325 try {
326 // String currentLayerId = ui.getCurrentLayerId();
327 // SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null;
328 if (isTopic(event, SuiteEvent.refreshPart)) {
329 Node node = getNode(ui, event);
330 CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
331 SuiteLayer layer = findByType(layersByType, node);
332 ui.switchToLayer(layer, node);
333 layer.view(uiProvider, ui.getCurrentWorkArea(), node);
334 ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
335 } else if (isTopic(event, SuiteEvent.openNewPart)) {
336 Node node = getNode(ui, event);
337 CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
338 SuiteLayer layer = findByType(layersByType, node);
339 ui.switchToLayer(layer, node);
340 layer.open(uiProvider, ui.getCurrentWorkArea(), node);
341 ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
342 } else if (isTopic(event, SuiteEvent.switchLayer)) {
343 String layerId = get(event, SuiteEvent.LAYER);
344 if (layerId != null) {
345 ui.switchToLayer(layerId, Jcr.getRootNode(ui.getSession(null)));
346 //ui.getCmsView().navigateTo("~");
347 } else {
348 Node node = getNode(ui, event);
349 if (node != null) {
350 SuiteLayer layer = findByType(layersByType, node);
351 ui.switchToLayer(layer, node);
352 }
353 }
354 }
355 } catch (Exception e) {
356 log.error("Cannot handle event " + event, e);
357 // CmsView.getCmsView(ui).exception(e);
358 }
359
360 }
361
362 private Node getNode(SuiteUi ui, Event event) {
363 String nodePath = get(event, SuiteEvent.NODE_PATH);
364 String workspaceName = get(event, SuiteEvent.WORKSPACE);
365 Session session = ui.getSession(workspaceName);
366 Node node;
367 if (nodePath == null) {
368 // look for a user
369 String username = get(event, SuiteEvent.USERNAME);
370 if (username == null)
371 return null;
372 User user = cmsUserManager.getUser(username);
373 if (user == null)
374 return null;
375 LdapName userDn;
376 try {
377 userDn = new LdapName(user.getName());
378 } catch (InvalidNameException e) {
379 throw new IllegalArgumentException("Badly formatted username", e);
380 }
381 String userNodePath = SuiteUtils.getUserNodePath(userDn);
382 if (Jcr.itemExists(session, userNodePath))
383 node = Jcr.getNode(session, userNodePath);
384 else {
385 Session adminSession = null;
386 try {
387 adminSession = NodeUtils.openDataAdminSession(getRepository(), workspaceName);
388 SuiteUtils.getOrCreateUserNode(adminSession, userDn);
389 } finally {
390 Jcr.logout(adminSession);
391 }
392 node = Jcr.getNode(session, userNodePath);
393 }
394 } else {
395 node = Jcr.getNode(session, nodePath);
396 }
397 return node;
398 }
399
400 private SuiteUi getRelatedUi(Event event) {
401 return managedUis.get(get(event, CMS_VIEW_UID_PROPERTY));
402 }
403
404 private static boolean isTopic(Event event, CmsEvent cmsEvent) {
405 return event.getTopic().equals(cmsEvent.topic());
406 }
407
408 private static String get(Event event, String key) {
409 Object value = event.getProperty(key);
410 if (value == null)
411 return null;
412 // throw new IllegalArgumentException("Property " + key + " must be set");
413 return value.toString();
414
415 }
416
417 /*
418 * Dependency injection.
419 */
420
421 public void addUiProvider(CmsUiProvider uiProvider, Map<String, Object> properties) {
422 if (properties.containsKey(Constants.SERVICE_PID)) {
423 String pid = (String) properties.get(Constants.SERVICE_PID);
424 RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
425 }
426 if (properties.containsKey(EntityConstants.TYPE)) {
427 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
428 for (String type : types)
429 RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
430 }
431 }
432
433 public void removeUiProvider(CmsUiProvider uiProvider, Map<String, Object> properties) {
434 if (properties.containsKey(Constants.SERVICE_PID)) {
435 String pid = (String) properties.get(Constants.SERVICE_PID);
436 if (uiProvidersByPid.containsKey(pid)) {
437 if (uiProvidersByPid.get(pid).equals(new RankedObject<CmsUiProvider>(uiProvider, properties))) {
438 uiProvidersByPid.remove(pid);
439 }
440 }
441 }
442 if (properties.containsKey(EntityConstants.TYPE)) {
443 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
444 for (String type : types) {
445 if (uiProvidersByType.containsKey(type)) {
446 if (uiProvidersByType.get(type).equals(new RankedObject<CmsUiProvider>(uiProvider, properties))) {
447 uiProvidersByType.remove(type);
448 }
449 }
450 }
451 }
452 }
453
454 public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
455 if (properties.containsKey(Constants.SERVICE_PID)) {
456 String pid = (String) properties.get(Constants.SERVICE_PID);
457 RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
458 }
459 if (properties.containsKey(EntityConstants.TYPE)) {
460 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
461 for (String type : types)
462 RankedObject.putIfHigherRank(layersByType, type, layer, properties);
463 }
464 }
465
466 public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
467 if (properties.containsKey(Constants.SERVICE_PID)) {
468 String pid = (String) properties.get(Constants.SERVICE_PID);
469 if (layersByPid.containsKey(pid)) {
470 if (layersByPid.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
471 layersByPid.remove(pid);
472 }
473 }
474 }
475 if (properties.containsKey(EntityConstants.TYPE)) {
476 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
477 for (String type : types) {
478 if (layersByType.containsKey(type)) {
479 if (layersByType.get(type).equals(new RankedObject<CmsUiProvider>(layer, properties))) {
480 layersByType.remove(type);
481 }
482 }
483 }
484 }
485 }
486
487 public void setCmsUserManager(CmsUserManager cmsUserManager) {
488 this.cmsUserManager = cmsUserManager;
489 }
490
491 }