]> git.argeo.org Git - gpl/argeo-suite.git/blob - org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java
db8a8dc17e0d589d01d2ae0de04580bfe0e7744d
[gpl/argeo-suite.git] / org.argeo.suite.ui / src / org / 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 currentLayer.view(uiProvider, ui.getCurrentWorkArea(), node);
332 ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
333 } else if (isTopic(event, SuiteEvent.openNewPart)) {
334 Node node = getNode(ui, event);
335 CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
336 currentLayer.open(uiProvider, ui.getCurrentWorkArea(), node);
337 ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
338 } else if (isTopic(event, SuiteEvent.switchLayer)) {
339 String layerId = get(event, SuiteEvent.LAYER);
340 if (layerId != null) {
341 ui.switchToLayer(layerId, Jcr.getRootNode(ui.getSession(null)));
342 } else {
343 Node node = getNode(ui, event);
344 if (node != null) {
345 SuiteLayer layer = findByType(layersByType, node);
346 ui.switchToLayer(layer, node);
347 }
348 }
349 }
350 } catch (Exception e) {
351 log.error("Cannot handle event " + event, e);
352 // CmsView.getCmsView(ui).exception(e);
353 }
354
355 }
356
357 private Node getNode(SuiteUi ui, Event event) {
358 String nodePath = get(event, SuiteEvent.NODE_PATH);
359 String workspaceName = get(event, SuiteEvent.WORKSPACE);
360 Session session = ui.getSession(workspaceName);
361 Node node;
362 if (nodePath == null) {
363 // look for a user
364 String username = get(event, SuiteEvent.USERNAME);
365 if (username == null)
366 return null;
367 User user = cmsUserManager.getUser(username);
368 if (user == null)
369 return null;
370 LdapName userDn;
371 try {
372 userDn = new LdapName(user.getName());
373 } catch (InvalidNameException e) {
374 throw new IllegalArgumentException("Badly formatted username", e);
375 }
376 String userNodePath = SuiteUtils.getUserNodePath(userDn);
377 if (Jcr.itemExists(session, userNodePath))
378 node = Jcr.getNode(session, userNodePath);
379 else {
380 Session adminSession = null;
381 try {
382 adminSession = NodeUtils.openDataAdminSession(getRepository(), workspaceName);
383 SuiteUtils.getOrCreateUserNode(adminSession, userDn);
384 } finally {
385 Jcr.logout(adminSession);
386 }
387 node = Jcr.getNode(session, userNodePath);
388 }
389 } else {
390 node = Jcr.getNode(session, nodePath);
391 }
392 return node;
393 }
394
395 private SuiteUi getRelatedUi(Event event) {
396 return managedUis.get(get(event, CMS_VIEW_UID_PROPERTY));
397 }
398
399 private static boolean isTopic(Event event, CmsEvent cmsEvent) {
400 return event.getTopic().equals(cmsEvent.topic());
401 }
402
403 private static String get(Event event, String key) {
404 Object value = event.getProperty(key);
405 if (value == null)
406 return null;
407 // throw new IllegalArgumentException("Property " + key + " must be set");
408 return value.toString();
409
410 }
411
412 /*
413 * Dependency injection.
414 */
415
416 public void addUiProvider(CmsUiProvider uiProvider, Map<String, Object> properties) {
417 if (properties.containsKey(Constants.SERVICE_PID)) {
418 String pid = (String) properties.get(Constants.SERVICE_PID);
419 RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
420 }
421 if (properties.containsKey(EntityConstants.TYPE)) {
422 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
423 for (String type : types)
424 RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
425 }
426 }
427
428 public void removeUiProvider(CmsUiProvider uiProvider, Map<String, Object> properties) {
429 if (properties.containsKey(Constants.SERVICE_PID)) {
430 String pid = (String) properties.get(Constants.SERVICE_PID);
431 if (uiProvidersByPid.containsKey(pid)) {
432 if (uiProvidersByPid.get(pid).equals(new RankedObject<CmsUiProvider>(uiProvider, properties))) {
433 uiProvidersByPid.remove(pid);
434 }
435 }
436 }
437 if (properties.containsKey(EntityConstants.TYPE)) {
438 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
439 for (String type : types) {
440 if (uiProvidersByType.containsKey(type)) {
441 if (uiProvidersByType.get(type).equals(new RankedObject<CmsUiProvider>(uiProvider, properties))) {
442 uiProvidersByType.remove(type);
443 }
444 }
445 }
446 }
447 }
448
449 public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
450 if (properties.containsKey(Constants.SERVICE_PID)) {
451 String pid = (String) properties.get(Constants.SERVICE_PID);
452 RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
453 }
454 if (properties.containsKey(EntityConstants.TYPE)) {
455 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
456 for (String type : types)
457 RankedObject.putIfHigherRank(layersByType, type, layer, properties);
458 }
459 }
460
461 public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
462 if (properties.containsKey(Constants.SERVICE_PID)) {
463 String pid = (String) properties.get(Constants.SERVICE_PID);
464 if (layersByPid.containsKey(pid)) {
465 if (layersByPid.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
466 layersByPid.remove(pid);
467 }
468 }
469 }
470 if (properties.containsKey(EntityConstants.TYPE)) {
471 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
472 for (String type : types) {
473 if (layersByType.containsKey(type)) {
474 if (layersByType.get(type).equals(new RankedObject<CmsUiProvider>(layer, properties))) {
475 layersByType.remove(type);
476 }
477 }
478 }
479 }
480 }
481
482 public void setCmsUserManager(CmsUserManager cmsUserManager) {
483 this.cmsUserManager = cmsUserManager;
484 }
485
486 }