]> git.argeo.org Git - gpl/argeo-suite.git/blob - org.argeo.suite.ui/src/org/argeo/suite/ui/SuiteApp.java
799fb9181d7d1248f4160c4acf3ab3e10c7c71cf
[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.startsWith("/"))
230 return;
231 SuiteUi suiteUi = (SuiteUi) parent;
232 Node node = stateToNode(suiteUi, state);
233 if (node == null) {
234 suiteUi.getCmsView().navigateTo("~");
235 } else {
236 suiteUi.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
237 suiteUi.getCmsView().sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
238 }
239 }
240
241 private String nodeToState(Node node) {
242 return '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
243 }
244
245 private Node stateToNode(SuiteUi suiteUi, String state) {
246 if (suiteUi == null)
247 return null;
248 if (state == null)
249 return null;
250
251 String path = state.substring(1);
252 String workspace;
253 if (path.equals("")) {
254 workspace = null;
255 path = "/";
256 } else {
257 int index = path.indexOf('/');
258 if (index == 0) {
259 log.error("Cannot interpret " + state);
260 // cmsView.navigateTo("~");
261 return null;
262 } else if (index > 0) {
263 workspace = path.substring(0, index);
264 path = path.substring(index);
265 } else {// index<0, assuming root node
266 workspace = path;
267 path = "/";
268 }
269 }
270 Session session = suiteUi.getSession(workspace);
271 if (session == null)
272 return null;
273 Node node = Jcr.getNode(session, path);
274 return node;
275 }
276
277 /*
278 * Events management
279 */
280
281 @Override
282 public void handleEvent(Event event) {
283
284 // Specific UI related events
285 SuiteUi ui = getRelatedUi(event);
286 try {
287 // String currentLayerId = ui.getCurrentLayerId();
288 // SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null;
289 if (isTopic(event, SuiteEvent.refreshPart)) {
290 Node node = getNode(ui, event);
291 if (node == null)
292 return;
293 CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
294 SuiteLayer layer = findByType(layersByType, node);
295 ui.switchToLayer(layer, node);
296 layer.view(uiProvider, ui.getCurrentWorkArea(), node);
297 ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
298 } else if (isTopic(event, SuiteEvent.openNewPart)) {
299 Node node = getNode(ui, event);
300 if (node == null)
301 return;
302 CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
303 SuiteLayer layer = findByType(layersByType, node);
304 ui.switchToLayer(layer, node);
305 layer.open(uiProvider, ui.getCurrentWorkArea(), node);
306 ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
307 } else if (isTopic(event, SuiteEvent.switchLayer)) {
308 String layerId = get(event, SuiteEvent.LAYER);
309 if (layerId != null) {
310 ui.switchToLayer(layerId, ui.getUserDir());
311 // ui.getCmsView().navigateTo("~");
312 } else {
313 Node node = getNode(ui, event);
314 if (node != null) {
315 SuiteLayer layer = findByType(layersByType, node);
316 ui.switchToLayer(layer, node);
317 }
318 }
319 }
320 } catch (Exception e) {
321 log.error("Cannot handle event " + event, e);
322 // CmsView.getCmsView(ui).exception(e);
323 }
324
325 }
326
327 private Node getNode(SuiteUi ui, Event event) {
328 String nodePath = get(event, SuiteEvent.NODE_PATH);
329 String workspaceName = get(event, SuiteEvent.WORKSPACE);
330 Session session = ui.getSession(workspaceName);
331 Node node;
332 if (nodePath == null) {
333 // look for a user
334 String username = get(event, SuiteEvent.USERNAME);
335 if (username == null)
336 return null;
337 User user = cmsUserManager.getUser(username);
338 if (user == null)
339 return null;
340 LdapName userDn;
341 try {
342 userDn = new LdapName(user.getName());
343 } catch (InvalidNameException e) {
344 throw new IllegalArgumentException("Badly formatted username", e);
345 }
346 String userNodePath = SuiteUtils.getUserNodePath(userDn);
347 if (Jcr.itemExists(session, userNodePath))
348 node = Jcr.getNode(session, userNodePath);
349 else {
350 Session adminSession = null;
351 try {
352 adminSession = NodeUtils.openDataAdminSession(getRepository(), workspaceName);
353 SuiteUtils.getOrCreateUserNode(adminSession, userDn);
354 } finally {
355 Jcr.logout(adminSession);
356 }
357 node = Jcr.getNode(session, userNodePath);
358 }
359 } else {
360 node = Jcr.getNode(session, nodePath);
361 }
362 return node;
363 }
364
365 private SuiteUi getRelatedUi(Event event) {
366 return managedUis.get(get(event, CMS_VIEW_UID_PROPERTY));
367 }
368
369 private static boolean isTopic(Event event, CmsEvent cmsEvent) {
370 return event.getTopic().equals(cmsEvent.topic());
371 }
372
373 private static String get(Event event, String key) {
374 Object value = event.getProperty(key);
375 if (value == null)
376 return null;
377 // throw new IllegalArgumentException("Property " + key + " must be set");
378 return value.toString();
379
380 }
381
382 /*
383 * Dependency injection.
384 */
385
386 public void addUiProvider(CmsUiProvider uiProvider, Map<String, Object> properties) {
387 if (properties.containsKey(Constants.SERVICE_PID)) {
388 String pid = (String) properties.get(Constants.SERVICE_PID);
389 RankedObject.putIfHigherRank(uiProvidersByPid, pid, uiProvider, properties);
390 }
391 if (properties.containsKey(EntityConstants.TYPE)) {
392 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
393 for (String type : types)
394 RankedObject.putIfHigherRank(uiProvidersByType, type, uiProvider, properties);
395 }
396 }
397
398 public void removeUiProvider(CmsUiProvider uiProvider, Map<String, Object> properties) {
399 if (properties.containsKey(Constants.SERVICE_PID)) {
400 String pid = (String) properties.get(Constants.SERVICE_PID);
401 if (uiProvidersByPid.containsKey(pid)) {
402 if (uiProvidersByPid.get(pid).equals(new RankedObject<CmsUiProvider>(uiProvider, properties))) {
403 uiProvidersByPid.remove(pid);
404 }
405 }
406 }
407 if (properties.containsKey(EntityConstants.TYPE)) {
408 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
409 for (String type : types) {
410 if (uiProvidersByType.containsKey(type)) {
411 if (uiProvidersByType.get(type).equals(new RankedObject<CmsUiProvider>(uiProvider, properties))) {
412 uiProvidersByType.remove(type);
413 }
414 }
415 }
416 }
417 }
418
419 public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
420 if (properties.containsKey(Constants.SERVICE_PID)) {
421 String pid = (String) properties.get(Constants.SERVICE_PID);
422 RankedObject.putIfHigherRank(layersByPid, pid, layer, properties);
423 }
424 if (properties.containsKey(EntityConstants.TYPE)) {
425 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
426 for (String type : types)
427 RankedObject.putIfHigherRank(layersByType, type, layer, properties);
428 }
429 }
430
431 public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
432 if (properties.containsKey(Constants.SERVICE_PID)) {
433 String pid = (String) properties.get(Constants.SERVICE_PID);
434 if (layersByPid.containsKey(pid)) {
435 if (layersByPid.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
436 layersByPid.remove(pid);
437 }
438 }
439 }
440 if (properties.containsKey(EntityConstants.TYPE)) {
441 List<String> types = LangUtils.toStringList(properties.get(EntityConstants.TYPE));
442 for (String type : types) {
443 if (layersByType.containsKey(type)) {
444 if (layersByType.get(type).equals(new RankedObject<CmsUiProvider>(layer, properties))) {
445 layersByType.remove(type);
446 }
447 }
448 }
449 }
450 }
451
452 public void setCmsUserManager(CmsUserManager cmsUserManager) {
453 this.cmsUserManager = cmsUserManager;
454 }
455
456 }