]> git.argeo.org Git - gpl/argeo-suite.git/blob - org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java
Prepare lib pom for release.
[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.getOrCreateCmsSessionNode(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 if (context == null)
176 throw new IllegalArgumentException("A node should be provided");
177 try {
178 // mixins
179 Set<String> types = new TreeSet<>();
180 for (NodeType nodeType : context.getMixinNodeTypes()) {
181 String typeName = nodeType.getName();
182 if (byType.containsKey(typeName)) {
183 types.add(typeName);
184 }
185 }
186 // primary node type
187 {
188 NodeType nodeType = context.getPrimaryNodeType();
189 String typeName = nodeType.getName();
190 if (byType.containsKey(typeName)) {
191 types.add(typeName);
192 }
193 for (NodeType mixin : nodeType.getDeclaredSupertypes()) {
194 if (byType.containsKey(mixin.getName())) {
195 types.add(mixin.getName());
196 }
197 }
198 }
199 // entity type
200 if (context.isNodeType(EntityType.entity.get())) {
201 if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
202 String typeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
203 if (byType.containsKey(typeName)) {
204 types.add(typeName);
205 }
206 }
207 }
208
209 // if (context.getPath().equals("/")) {// root node
210 // types.add("nt:folder");
211 // }
212 if (NodeUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
213 types.add("nt:folder");
214 }
215
216 if (types.size() == 0)
217 throw new IllegalArgumentException("No type found for " + context);
218 String type = types.iterator().next();
219 if (!byType.containsKey(type))
220 throw new IllegalArgumentException("No component found for " + context + " with type " + type);
221 return byType.get(type).get();
222 } catch (RepositoryException e) {
223 throw new IllegalStateException(e);
224 }
225 }
226
227 @Override
228 public void setState(Composite parent, String state) {
229 if (state == null || state.equals("~"))
230 return;
231 if (!state.startsWith("/") && !state.equals("~")) {
232 if (parent instanceof SuiteUi) {
233 SuiteUi ui = (SuiteUi) parent;
234 String currentLayerId = ui.getCurrentLayerId();
235 if (state.equals(currentLayerId))
236 return; // does nothing
237 else {
238 Map<String, Object> properties = new HashMap<>();
239 properties.put(SuiteEvent.LAYER, state);
240 ui.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), properties);
241 }
242 }
243 return;
244 }
245 SuiteUi suiteUi = (SuiteUi) parent;
246 Node node = stateToNode(suiteUi, state);
247 if (node == null) {
248 suiteUi.getCmsView().navigateTo("~");
249 } else {
250 suiteUi.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
251 suiteUi.getCmsView().sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
252 }
253 }
254
255 private String nodeToState(Node node) {
256 return '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
257 }
258
259 private Node stateToNode(SuiteUi suiteUi, String state) {
260 if (suiteUi == null)
261 return null;
262 if (state == null || !state.startsWith("/"))
263 return null;
264
265 String path = state.substring(1);
266 String workspace;
267 if (path.equals("")) {
268 workspace = null;
269 path = "/";
270 } else {
271 int index = path.indexOf('/');
272 if (index == 0) {
273 log.error("Cannot interpret " + state);
274 // cmsView.navigateTo("~");
275 return null;
276 } else if (index > 0) {
277 workspace = path.substring(0, index);
278 path = path.substring(index);
279 } else {// index<0, assuming root node
280 workspace = path;
281 path = "/";
282 }
283 }
284 Session session = suiteUi.getSession(workspace);
285 if (session == null)
286 return null;
287 Node node = Jcr.getNode(session, path);
288 return node;
289 }
290
291 /*
292 * Events management
293 */
294
295 @Override
296 public void handleEvent(Event event) {
297
298 // Specific UI related events
299 SuiteUi ui = getRelatedUi(event);
300 try {
301 // String currentLayerId = ui.getCurrentLayerId();
302 // SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null;
303 if (isTopic(event, SuiteEvent.refreshPart)) {
304 Node node = getNode(ui, event);
305 if (node == null)
306 return;
307 CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
308 SuiteLayer layer = findByType(layersByType, node);
309 ui.switchToLayer(layer, node);
310 ui.getCmsView().runAs(() -> layer.view(uiProvider, ui.getCurrentWorkArea(), node));
311 ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
312 } else if (isTopic(event, SuiteEvent.openNewPart)) {
313 Node node = getNode(ui, event);
314 if (node == null)
315 return;
316 CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
317 SuiteLayer layer = findByType(layersByType, node);
318 ui.switchToLayer(layer, node);
319 ui.getCmsView().runAs(() -> layer.open(uiProvider, ui.getCurrentWorkArea(), node));
320 ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
321 } else if (isTopic(event, SuiteEvent.switchLayer)) {
322 String layerId = get(event, SuiteEvent.LAYER);
323 if (layerId != null) {
324 // ui.switchToLayer(layerId, ui.getUserDir());
325 ui.getCmsView().runAs(() -> ui.switchToLayer(layerId, ui.getUserDir()));
326 ui.getCmsView().navigateTo(layerId);
327 } else {
328 Node node = getNode(ui, event);
329 if (node != null) {
330 SuiteLayer layer = findByType(layersByType, node);
331 ui.getCmsView().runAs(() -> ui.switchToLayer(layer, node));
332 }
333 }
334 }
335 } catch (Exception e) {
336 log.error("Cannot handle event " + event, e);
337 // CmsView.getCmsView(ui).exception(e);
338 }
339
340 }
341
342 private Node getNode(SuiteUi ui, Event event) {
343 String nodePath = get(event, SuiteEvent.NODE_PATH);
344 String workspaceName = get(event, SuiteEvent.WORKSPACE);
345 Session session = ui.getSession(workspaceName);
346 Node node;
347 if (nodePath == null) {
348 // look for a user
349 String username = get(event, SuiteEvent.USERNAME);
350 if (username == null)
351 return null;
352 User user = cmsUserManager.getUser(username);
353 if (user == null)
354 return null;
355 LdapName userDn;
356 try {
357 userDn = new LdapName(user.getName());
358 } catch (InvalidNameException e) {
359 throw new IllegalArgumentException("Badly formatted username", e);
360 }
361 String userNodePath = SuiteUtils.getUserNodePath(userDn);
362 if (Jcr.itemExists(session, userNodePath))
363 node = Jcr.getNode(session, userNodePath);
364 else {
365 Session adminSession = null;
366 try {
367 adminSession = NodeUtils.openDataAdminSession(getRepository(), workspaceName);
368 SuiteUtils.getOrCreateUserNode(adminSession, userDn);
369 } finally {
370 Jcr.logout(adminSession);
371 }
372 node = Jcr.getNode(session, userNodePath);
373 }
374 } else {
375 node = Jcr.getNode(session, nodePath);
376 }
377 return node;
378 }
379
380 private SuiteUi getRelatedUi(Event event) {
381 return managedUis.get(get(event, CMS_VIEW_UID_PROPERTY));
382 }
383
384 private static boolean isTopic(Event event, CmsEvent cmsEvent) {
385 return event.getTopic().equals(cmsEvent.topic());
386 }
387
388 private static String get(Event event, String key) {
389 Object value = event.getProperty(key);
390 if (value == null)
391 return null;
392 // throw new IllegalArgumentException("Property " + key + " must be set");
393 return value.toString();
394
395 }
396
397 /*
398 * Dependency injection.
399 */
400
401 public void addUiProvider(CmsUiProvider uiProvider, Map<String, Object> properties) {
402 if (properties.containsKey(Constants.SERVICE_PID)) {
403 String pid = (String) properties.get(Constants.SERVICE_PID);
404 RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
405 }
406 if (properties.containsKey(EntityConstants.TYPE)) {
407 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
408 for (String type : types)
409 RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
410 }
411 }
412
413 public void removeUiProvider(CmsUiProvider uiProvider, Map<String, Object> properties) {
414 if (properties.containsKey(Constants.SERVICE_PID)) {
415 String pid = (String) properties.get(Constants.SERVICE_PID);
416 if (uiProvidersByPid.containsKey(pid)) {
417 if (uiProvidersByPid.get(pid).equals(new RankedObject<CmsUiProvider>(uiProvider, properties))) {
418 uiProvidersByPid.remove(pid);
419 }
420 }
421 }
422 if (properties.containsKey(EntityConstants.TYPE)) {
423 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
424 for (String type : types) {
425 if (uiProvidersByType.containsKey(type)) {
426 if (uiProvidersByType.get(type).equals(new RankedObject<CmsUiProvider>(uiProvider, properties))) {
427 uiProvidersByType.remove(type);
428 }
429 }
430 }
431 }
432 }
433
434 public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
435 if (properties.containsKey(Constants.SERVICE_PID)) {
436 String pid = (String) properties.get(Constants.SERVICE_PID);
437 RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
438 }
439 if (properties.containsKey(EntityConstants.TYPE)) {
440 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
441 for (String type : types)
442 RankedObject.putIfHigherRank(layersByType, type, layer, properties);
443 }
444 }
445
446 public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
447 if (properties.containsKey(Constants.SERVICE_PID)) {
448 String pid = (String) properties.get(Constants.SERVICE_PID);
449 if (layersByPid.containsKey(pid)) {
450 if (layersByPid.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
451 layersByPid.remove(pid);
452 }
453 }
454 }
455 if (properties.containsKey(EntityConstants.TYPE)) {
456 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
457 for (String type : types) {
458 if (layersByType.containsKey(type)) {
459 if (layersByType.get(type).equals(new RankedObject<CmsUiProvider>(layer, properties))) {
460 layersByType.remove(type);
461 }
462 }
463 }
464 }
465 }
466
467 public void setCmsUserManager(CmsUserManager cmsUserManager) {
468 this.cmsUserManager = cmsUserManager;
469 }
470
471 }