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 }