1 package org
.argeo
.suite
.ui
;
3 import static org
.argeo
.cms
.ui
.CmsView
.CMS_VIEW_UID_PROPERTY
;
5 import java
.util
.Collections
;
6 import java
.util
.HashMap
;
7 import java
.util
.HashSet
;
11 import java
.util
.TreeMap
;
12 import java
.util
.TreeSet
;
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
;
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
;
47 /** The Argeo Suite App. */
48 public class SuiteApp
extends AbstractCmsApp
implements EventHandler
{
49 private final static Log log
= LogFactory
.getLog(SuiteApp
.class);
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";
58 private final static String DEFAULT_UI_NAME
= "app";
59 private final static String DEFAULT_THEME_ID
= "org.argeo.suite.theme.default";
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
<>());
66 private CmsUserManager cmsUserManager
;
68 // TODO make more optimal or via CmsSession/CmsView
69 private Map
<String
, SuiteUi
> managedUis
= new HashMap
<>();
71 // private CmsUiProvider headerPart = null;
73 public void init(Map
<String
, String
> properties
) {
74 if (log
.isDebugEnabled())
75 log
.info("Argeo Suite App started");
78 public void destroy(Map
<String
, String
> properties
) {
79 for (SuiteUi ui
: managedUis
.values())
82 if (log
.isDebugEnabled())
83 log
.info("Argeo Suite App stopped");
88 public Set
<String
> getUiNames() {
89 HashSet
<String
> uiNames
= new HashSet
<>();
90 uiNames
.add(DEFAULT_UI_NAME
);
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
);
99 throw new IllegalStateException("No CMS view is registered.");
100 CmsTheme theme
= getTheme(uiName
);
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.");
111 refreshUi(argeoSuiteUi
, null);
116 public String
getThemeId(String uiName
) {
117 // TODO make it configurable
118 return DEFAULT_THEME_ID
;
122 public void refreshUi(Composite parent
, String state
) {
125 SuiteUi ui
= (SuiteUi
) parent
;
126 refreshPart(findUiProvider(HEADER_PID
), ui
.getHeader(), context
);
127 CmsView cmsView
= CmsView
.getCmsView(parent
);
128 if (cmsView
.isAnonymous()) {
130 ui
.refreshBelowHeader(false);
131 refreshPart(findUiProvider(LOGIN_SCREEN_PID
), ui
.getBelowHeader(), context
);
132 ui
.layout(true, true);
134 if (ui
.getUserDir() == null) {
135 CmsSession cmsSession
= cmsView
.getCmsSession();
136 Session adminSession
= null;
138 adminSession
= NodeUtils
.openDataAdminSession(getRepository(), null);
139 Node userDir
= SuiteUtils
.getOrCreateSessionDir(adminSession
, cmsSession
);
140 ui
.initSessions(getRepository(), userDir
.getPath());
142 Jcr
.logout(adminSession
);
145 context
= stateToNode(ui
, state
);
147 context
= ui
.getUserDir();
149 ui
.refreshBelowHeader(true);
150 for (String key
: layersByPid
.keySet()) {
151 SuiteLayer layer
= layersByPid
.get(key
).get();
152 ui
.addLayer(key
, layer
);
154 refreshPart(findUiProvider(LEAD_PANE_PID
), ui
.getLeadPane(), context
);
155 ui
.layout(true, true);
156 setState(parent
, state
);
158 } catch (Exception e
) {
159 CmsFeedback
.show("Unexpected exception", e
);
163 private void refreshPart(CmsUiProvider uiProvider
, Composite part
, Node context
) {
164 CmsUiUtils
.clear(part
);
165 uiProvider
.createUiPart(part
, context
);
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();
174 private <T
> T
findByType(Map
<String
, RankedObject
<T
>> byType
, Node context
) {
177 Set
<String
> types
= new TreeSet
<>();
178 for (NodeType nodeType
: context
.getMixinNodeTypes()) {
179 String typeName
= nodeType
.getName();
180 if (byType
.containsKey(typeName
)) {
186 NodeType nodeType
= context
.getPrimaryNodeType();
187 String typeName
= nodeType
.getName();
188 if (byType
.containsKey(typeName
)) {
191 for (NodeType mixin
: nodeType
.getDeclaredSupertypes()) {
192 if (byType
.containsKey(mixin
.getName())) {
193 types
.add(mixin
.getName());
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
)) {
207 // if (context.getPath().equals("/")) {// root node
208 // types.add("nt:folder");
210 if (NodeUtils
.isUserHome(context
) && byType
.containsKey("nt:folder")) {// home node
211 types
.add("nt:folder");
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
);
226 public void setState(Composite parent
, String state
) {
227 if (state
== null || !state
.startsWith("/"))
229 SuiteUi suiteUi
= (SuiteUi
) parent
;
230 Node node
= stateToNode(suiteUi
, state
);
232 suiteUi
.getCmsView().navigateTo("~");
234 suiteUi
.getCmsView().sendEvent(SuiteEvent
.switchLayer
.topic(), SuiteEvent
.eventProperties(node
));
235 suiteUi
.getCmsView().sendEvent(SuiteEvent
.refreshPart
.topic(), SuiteEvent
.eventProperties(node
));
238 // CmsView cmsView = CmsView.getCmsView(parent);
239 // if (cmsView.isAnonymous())
241 // Session session = null;
243 // if (state != null && state.startsWith("/")) {
244 // String path = state.substring(1);
246 // if (path.equals("")) {
250 // int index = path.indexOf('/');
252 // log.error("Cannot interpret " + state);
253 // cmsView.navigateTo("~");
255 // } else if (index > 0) {
256 // workspace = path.substring(0, index);
257 // path = path.substring(index);
258 // } else {// index<0, assuming root node
263 // session = cmsView.doAs(() -> Jcr.login(getRepository(), workspace));
265 // Node node = session.getNode(path);
267 // cmsView.sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
268 // cmsView.sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
270 // } catch (RepositoryException e) {
271 // log.error("Cannot load state " + state, e);
272 // cmsView.navigateTo("~");
274 // JcrUtils.logoutQuietly(session);
278 private String
nodeToState(Node node
) {
279 return '/' + Jcr
.getWorkspaceName(node
) + Jcr
.getPath(node
);
282 private Node
stateToNode(SuiteUi suiteUi
, String state
) {
288 String path
= state
.substring(1);
290 if (path
.equals("")) {
294 int index
= path
.indexOf('/');
296 log
.error("Cannot interpret " + state
);
297 // cmsView.navigateTo("~");
299 } else if (index
> 0) {
300 workspace
= path
.substring(0, index
);
301 path
= path
.substring(index
);
302 } else {// index<0, assuming root node
307 // session = cmsView.doAs(() -> Jcr.login(getRepository(), workspace));
309 Session session
= suiteUi
.getSession(workspace
);
312 Node node
= Jcr
.getNode(session
, path
);
321 public void handleEvent(Event event
) {
323 // Specific UI related events
324 SuiteUi ui
= getRelatedUi(event
);
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)));
343 Node node
= getNode(ui
, event
);
345 SuiteLayer layer
= findByType(layersByType
, node
);
346 ui
.switchToLayer(layer
, node
);
350 } catch (Exception e
) {
351 log
.error("Cannot handle event " + event
, e
);
352 // CmsView.getCmsView(ui).exception(e);
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
);
362 if (nodePath
== null) {
364 String username
= get(event
, SuiteEvent
.USERNAME
);
365 if (username
== null)
367 User user
= cmsUserManager
.getUser(username
);
372 userDn
= new LdapName(user
.getName());
373 } catch (InvalidNameException e
) {
374 throw new IllegalArgumentException("Badly formatted username", e
);
376 String userNodePath
= SuiteUtils
.getUserNodePath(userDn
);
377 if (Jcr
.itemExists(session
, userNodePath
))
378 node
= Jcr
.getNode(session
, userNodePath
);
380 Session adminSession
= null;
382 adminSession
= NodeUtils
.openDataAdminSession(getRepository(), workspaceName
);
383 SuiteUtils
.getOrCreateUserNode(adminSession
, userDn
);
385 Jcr
.logout(adminSession
);
387 node
= Jcr
.getNode(session
, userNodePath
);
390 node
= Jcr
.getNode(session
, nodePath
);
395 private SuiteUi
getRelatedUi(Event event
) {
396 return managedUis
.get(get(event
, CMS_VIEW_UID_PROPERTY
));
399 private static boolean isTopic(Event event
, CmsEvent cmsEvent
) {
400 return event
.getTopic().equals(cmsEvent
.topic());
403 private static String
get(Event event
, String key
) {
404 Object value
= event
.getProperty(key
);
407 // throw new IllegalArgumentException("Property " + key + " must be set");
408 return value
.toString();
413 * Dependency injection.
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
);
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
);
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
);
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
);
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
);
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
);
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
);
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
);
482 public void setCmsUserManager(CmsUserManager cmsUserManager
) {
483 this.cmsUserManager
= cmsUserManager
;