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
;
9 import java
.util
.Locale
;
12 import java
.util
.TreeMap
;
13 import java
.util
.TreeSet
;
15 import javax
.jcr
.Node
;
16 import javax
.jcr
.RepositoryException
;
17 import javax
.jcr
.Session
;
18 import javax
.jcr
.nodetype
.NodeType
;
19 import javax
.naming
.InvalidNameException
;
20 import javax
.naming
.ldap
.LdapName
;
22 import org
.apache
.commons
.logging
.Log
;
23 import org
.apache
.commons
.logging
.LogFactory
;
24 import org
.argeo
.api
.NodeUtils
;
25 import org
.argeo
.cms
.CmsUserManager
;
26 import org
.argeo
.cms
.LocaleUtils
;
27 import org
.argeo
.cms
.Localized
;
28 import org
.argeo
.cms
.auth
.CmsSession
;
29 import org
.argeo
.cms
.ui
.AbstractCmsApp
;
30 import org
.argeo
.cms
.ui
.CmsTheme
;
31 import org
.argeo
.cms
.ui
.CmsUiProvider
;
32 import org
.argeo
.cms
.ui
.CmsView
;
33 import org
.argeo
.cms
.ui
.dialogs
.CmsFeedback
;
34 import org
.argeo
.cms
.ui
.util
.CmsUiUtils
;
35 import org
.argeo
.eclipse
.ui
.specific
.UiContext
;
36 import org
.argeo
.entity
.EntityConstants
;
37 import org
.argeo
.entity
.EntityNames
;
38 import org
.argeo
.entity
.EntityType
;
39 import org
.argeo
.jcr
.Jcr
;
40 import org
.argeo
.suite
.RankedObject
;
41 import org
.argeo
.suite
.SuiteUtils
;
42 import org
.argeo
.util
.LangUtils
;
43 import org
.eclipse
.swt
.SWT
;
44 import org
.eclipse
.swt
.widgets
.Composite
;
45 import org
.osgi
.framework
.Constants
;
46 import org
.osgi
.service
.event
.Event
;
47 import org
.osgi
.service
.event
.EventHandler
;
48 import org
.osgi
.service
.useradmin
.User
;
50 /** The Argeo Suite App. */
51 public class SuiteApp
extends AbstractCmsApp
implements EventHandler
{
52 private final static Log log
= LogFactory
.getLog(SuiteApp
.class);
54 public final static String PUBLIC_BASE_PATH_PROPERTY
= "publicBasePath";
55 public final static String DEFAULT_UI_NAME_PROPERTY
= "defaultUiName";
56 public final static String DEFAULT_THEME_ID_PROPERTY
= "defaultThemeId";
57 private final static String LOGIN
= "login";
59 private String publicBasePath
= null;
61 private String pidPrefix
;
62 private String headerPid
;
63 private String footerPid
;
64 private String leadPanePid
;
65 private String loginScreenPid
;
67 private String defaultLayerPid
= "argeo.suite.ui.dashboardLayer";
69 private String defaultUiName
= "app";
70 private String defaultThemeId
= "org.argeo.suite.theme.default";
72 private Map
<String
, RankedObject
<CmsUiProvider
>> uiProvidersByPid
= Collections
.synchronizedMap(new HashMap
<>());
73 private Map
<String
, RankedObject
<CmsUiProvider
>> uiProvidersByType
= Collections
.synchronizedMap(new HashMap
<>());
74 private Map
<String
, RankedObject
<SuiteLayer
>> layersByPid
= Collections
.synchronizedSortedMap(new TreeMap
<>());
75 private Map
<String
, RankedObject
<SuiteLayer
>> layersByType
= Collections
.synchronizedSortedMap(new TreeMap
<>());
77 private CmsUserManager cmsUserManager
;
79 // TODO make more optimal or via CmsSession/CmsView
80 private Map
<String
, SuiteUi
> managedUis
= new HashMap
<>();
82 public void init(Map
<String
, Object
> properties
) {
83 if (log
.isDebugEnabled())
84 log
.info("Argeo Suite App started");
86 if (properties
.containsKey(DEFAULT_UI_NAME_PROPERTY
))
87 defaultUiName
= LangUtils
.get(properties
, DEFAULT_UI_NAME_PROPERTY
);
88 if (properties
.containsKey(DEFAULT_THEME_ID_PROPERTY
))
89 defaultThemeId
= LangUtils
.get(properties
, DEFAULT_THEME_ID_PROPERTY
);
90 publicBasePath
= LangUtils
.get(properties
, PUBLIC_BASE_PATH_PROPERTY
);
92 if (properties
.containsKey(Constants
.SERVICE_PID
)) {
93 String servicePid
= properties
.get(Constants
.SERVICE_PID
).toString();
94 if (servicePid
.endsWith(".app")) {
95 pidPrefix
= servicePid
.substring(0, servicePid
.length() - "app".length());
99 if (pidPrefix
== null)
100 throw new IllegalArgumentException("PID prefix must be set.");
102 headerPid
= pidPrefix
+ "header";
103 footerPid
= pidPrefix
+ "footer";
104 leadPanePid
= pidPrefix
+ "leadPane";
105 loginScreenPid
= pidPrefix
+ "loginScreen";
108 public void destroy(Map
<String
, Object
> properties
) {
109 for (SuiteUi ui
: managedUis
.values())
110 if (!ui
.isDisposed())
112 if (log
.isDebugEnabled())
113 log
.info("Argeo Suite App stopped");
118 public Set
<String
> getUiNames() {
119 HashSet
<String
> uiNames
= new HashSet
<>();
120 uiNames
.add(defaultUiName
);
125 public Composite
initUi(Composite parent
) {
126 String uiName
= parent
.getData(UI_NAME_PROPERTY
) != null ? parent
.getData(UI_NAME_PROPERTY
).toString() : null;
127 CmsView cmsView
= CmsView
.getCmsView(parent
);
129 throw new IllegalStateException("No CMS view is registered.");
130 CmsTheme theme
= getTheme(uiName
);
132 CmsTheme
.registerCmsTheme(parent
.getShell(), theme
);
133 SuiteUi argeoSuiteUi
= new SuiteUi(parent
, SWT
.INHERIT_DEFAULT
);
134 String uid
= cmsView
.getUid();
135 managedUis
.put(uid
, argeoSuiteUi
);
136 argeoSuiteUi
.addDisposeListener((e
) -> {
137 managedUis
.remove(uid
);
138 if (log
.isDebugEnabled())
139 log
.debug("Suite UI " + uid
+ " has been disposed.");
145 public String
getThemeId(String uiName
) {
146 return defaultThemeId
;
150 public void refreshUi(Composite parent
, String state
) {
153 SuiteUi ui
= (SuiteUi
) parent
;
154 CmsView cmsView
= CmsView
.getCmsView(parent
);
155 CmsUiProvider headerUiProvider
= findUiProvider(headerPid
);
156 CmsUiProvider footerUiProvider
= findUiProvider(footerPid
);
157 Localized appTitle
= null;
158 if (headerUiProvider
instanceof DefaultHeader
) {
159 appTitle
= ((DefaultHeader
) headerUiProvider
).getTitle();
161 ui
.setTitle(appTitle
);
163 if (cmsView
.isAnonymous() && publicBasePath
== null) {// internal app, must login
165 if (headerUiProvider
!= null)
166 refreshPart(headerUiProvider
, ui
.getHeader(), context
);
167 ui
.refreshBelowHeader(false);
168 refreshPart(findUiProvider(loginScreenPid
), ui
.getBelowHeader(), context
);
169 if (footerUiProvider
!= null)
170 refreshPart(footerUiProvider
, ui
.getFooter(), context
);
171 ui
.layout(true, true);
174 if (LOGIN
.equals(state
))
176 CmsSession cmsSession
= cmsView
.getCmsSession();
177 if (ui
.getUserDir() == null) {
178 // FIXME NPE on CMSSession when logging in from anonymous
179 if (cmsSession
==null || cmsView
.isAnonymous()) {
180 assert publicBasePath
!= null;
181 ui
.initSessions(getRepository(), publicBasePath
);
183 Session adminSession
= null;
185 adminSession
= NodeUtils
.openDataAdminSession(getRepository(), null);
186 Node userDir
= SuiteUtils
.getOrCreateCmsSessionNode(adminSession
, cmsSession
);
187 ui
.initSessions(getRepository(), userDir
.getPath());
189 Jcr
.logout(adminSession
);
193 initLocale(cmsSession
);
194 context
= stateToNode(ui
, state
);
196 context
= ui
.getUserDir();
198 if (headerUiProvider
!= null)
199 refreshPart(headerUiProvider
, ui
.getHeader(), context
);
200 ui
.refreshBelowHeader(true);
201 for (String key
: layersByPid
.keySet()) {
202 SuiteLayer layer
= layersByPid
.get(key
).get();
203 ui
.addLayer(key
, layer
);
205 refreshPart(findUiProvider(leadPanePid
), ui
.getLeadPane(), context
);
206 if (footerUiProvider
!= null)
207 refreshPart(footerUiProvider
, ui
.getFooter(), context
);
208 ui
.layout(true, true);
209 setState(parent
, state
!= null ? state
: defaultLayerPid
);
211 } catch (Exception e
) {
212 CmsFeedback
.show("Unexpected exception", e
);
216 private void initLocale(CmsSession cmsSession
) {
217 if (cmsSession
== null)
219 Locale locale
= cmsSession
.getLocale();
220 UiContext
.setLocale(locale
);
221 LocaleUtils
.setThreadLocale(locale
);
225 private void refreshPart(CmsUiProvider uiProvider
, Composite part
, Node context
) {
226 CmsUiUtils
.clear(part
);
227 uiProvider
.createUiPart(part
, context
);
230 private CmsUiProvider
findUiProvider(String pid
) {
231 if (!uiProvidersByPid
.containsKey(pid
))
233 return uiProvidersByPid
.get(pid
).get();
236 private SuiteLayer
findLayer(String pid
) {
237 if (!layersByPid
.containsKey(pid
))
239 return layersByPid
.get(pid
).get();
242 private <T
> T
findByType(Map
<String
, RankedObject
<T
>> byType
, Node context
) {
244 throw new IllegalArgumentException("A node should be provided");
247 Set
<String
> types
= new TreeSet
<>();
248 for (NodeType nodeType
: context
.getMixinNodeTypes()) {
249 String typeName
= nodeType
.getName();
250 if (byType
.containsKey(typeName
)) {
256 NodeType nodeType
= context
.getPrimaryNodeType();
257 String typeName
= nodeType
.getName();
258 if (byType
.containsKey(typeName
)) {
261 for (NodeType mixin
: nodeType
.getDeclaredSupertypes()) {
262 if (byType
.containsKey(mixin
.getName())) {
263 types
.add(mixin
.getName());
268 if (context
.isNodeType(EntityType
.entity
.get())) {
269 if (context
.hasProperty(EntityNames
.ENTITY_TYPE
)) {
270 String typeName
= context
.getProperty(EntityNames
.ENTITY_TYPE
).getString();
271 if (byType
.containsKey(typeName
)) {
277 // if (context.getPath().equals("/")) {// root node
278 // types.add("nt:folder");
280 if (NodeUtils
.isUserHome(context
) && byType
.containsKey("nt:folder")) {// home node
281 types
.add("nt:folder");
284 if (types
.size() == 0)
285 throw new IllegalArgumentException("No type found for " + context
);
286 String type
= types
.iterator().next();
287 if (!byType
.containsKey(type
))
288 throw new IllegalArgumentException("No component found for " + context
+ " with type " + type
);
289 return byType
.get(type
).get();
290 } catch (RepositoryException e
) {
291 throw new IllegalStateException(e
);
296 public void setState(Composite parent
, String state
) {
299 if (!state
.startsWith("/")) {
300 if (parent
instanceof SuiteUi
) {
301 SuiteUi ui
= (SuiteUi
) parent
;
302 if (LOGIN
.equals(state
) || state
.equals("~")) {
303 String appTitle
= "";
304 if (ui
.getTitle() != null)
305 appTitle
= ui
.getTitle().lead();
306 ui
.getCmsView().stateChanged(state
, appTitle
);
309 String currentLayerId
= ui
.getCurrentLayerId();
310 if (state
.equals(currentLayerId
))
311 return; // does nothing
313 Map
<String
, Object
> properties
= new HashMap
<>();
314 properties
.put(SuiteEvent
.LAYER
, state
);
315 ui
.getCmsView().sendEvent(SuiteEvent
.switchLayer
.topic(), properties
);
320 SuiteUi suiteUi
= (SuiteUi
) parent
;
321 Node node
= stateToNode(suiteUi
, state
);
323 suiteUi
.getCmsView().navigateTo("~");
325 suiteUi
.getCmsView().sendEvent(SuiteEvent
.switchLayer
.topic(), SuiteEvent
.eventProperties(node
));
326 suiteUi
.getCmsView().sendEvent(SuiteEvent
.refreshPart
.topic(), SuiteEvent
.eventProperties(node
));
330 private String
nodeToState(Node node
) {
331 return '/' + Jcr
.getWorkspaceName(node
) + Jcr
.getPath(node
);
334 private Node
stateToNode(SuiteUi suiteUi
, String state
) {
337 if (state
== null || !state
.startsWith("/"))
340 String path
= state
.substring(1);
342 if (path
.equals("")) {
346 int index
= path
.indexOf('/');
348 log
.error("Cannot interpret " + state
);
349 // cmsView.navigateTo("~");
351 } else if (index
> 0) {
352 workspace
= path
.substring(0, index
);
353 path
= path
.substring(index
);
354 } else {// index<0, assuming root node
359 Session session
= suiteUi
.getSession(workspace
);
362 Node node
= Jcr
.getNode(session
, path
);
371 public void handleEvent(Event event
) {
373 // Specific UI related events
374 SuiteUi ui
= getRelatedUi(event
);
378 String appTitle
= "";
379 if (ui
.getTitle() != null)
380 appTitle
= ui
.getTitle().lead() + " - ";
382 // String currentLayerId = ui.getCurrentLayerId();
383 // SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null;
384 if (SuiteUiUtils
.isTopic(event
, SuiteEvent
.refreshPart
)) {
385 Node node
= getNode(ui
, event
);
388 CmsUiProvider uiProvider
= findByType(uiProvidersByType
, node
);
389 SuiteLayer layer
= findByType(layersByType
, node
);
390 ui
.switchToLayer(layer
, node
);
391 ui
.getCmsView().runAs(() -> layer
.view(uiProvider
, ui
.getCurrentWorkArea(), node
));
392 ui
.getCmsView().stateChanged(nodeToState(node
), appTitle
+ Jcr
.getTitle(node
));
393 } else if (SuiteUiUtils
.isTopic(event
, SuiteEvent
.openNewPart
)) {
394 Node node
= getNode(ui
, event
);
397 CmsUiProvider uiProvider
= findByType(uiProvidersByType
, node
);
398 SuiteLayer layer
= findByType(layersByType
, node
);
399 ui
.switchToLayer(layer
, node
);
400 ui
.getCmsView().runAs(() -> layer
.open(uiProvider
, ui
.getCurrentWorkArea(), node
));
401 ui
.getCmsView().stateChanged(nodeToState(node
), appTitle
+ Jcr
.getTitle(node
));
402 } else if (SuiteUiUtils
.isTopic(event
, SuiteEvent
.switchLayer
)) {
403 String layerId
= get(event
, SuiteEvent
.LAYER
);
404 if (layerId
!= null) {
405 // ui.switchToLayer(layerId, ui.getUserDir());
406 SuiteLayer suiteLayer
= findLayer(layerId
);
407 Localized layerTitle
= suiteLayer
.getTitle();
408 ui
.getCmsView().runAs(() -> ui
.switchToLayer(layerId
, ui
.getUserDir()));
410 if (layerTitle
!= null)
411 title
= layerTitle
.lead();
412 ui
.getCmsView().stateChanged(layerId
, appTitle
+ title
);
414 Node node
= getNode(ui
, event
);
416 SuiteLayer layer
= findByType(layersByType
, node
);
417 ui
.getCmsView().runAs(() -> ui
.switchToLayer(layer
, node
));
421 } catch (Exception e
) {
422 log
.error("Cannot handle event " + event
, e
);
423 // CmsView.getCmsView(ui).exception(e);
428 private Node
getNode(SuiteUi ui
, Event event
) {
429 String nodePath
= get(event
, SuiteEvent
.NODE_PATH
);
430 String workspaceName
= get(event
, SuiteEvent
.WORKSPACE
);
431 Session session
= ui
.getSession(workspaceName
);
433 if (nodePath
== null) {
435 String username
= get(event
, SuiteEvent
.USERNAME
);
436 if (username
== null)
438 User user
= cmsUserManager
.getUser(username
);
443 userDn
= new LdapName(user
.getName());
444 } catch (InvalidNameException e
) {
445 throw new IllegalArgumentException("Badly formatted username", e
);
447 String userNodePath
= SuiteUtils
.getUserNodePath(userDn
);
448 if (Jcr
.itemExists(session
, userNodePath
))
449 node
= Jcr
.getNode(session
, userNodePath
);
451 Session adminSession
= null;
453 adminSession
= NodeUtils
.openDataAdminSession(getRepository(), workspaceName
);
454 SuiteUtils
.getOrCreateUserNode(adminSession
, userDn
);
456 Jcr
.logout(adminSession
);
458 node
= Jcr
.getNode(session
, userNodePath
);
461 node
= Jcr
.getNode(session
, nodePath
);
466 private SuiteUi
getRelatedUi(Event event
) {
467 return managedUis
.get(get(event
, CMS_VIEW_UID_PROPERTY
));
470 public static String
get(Event event
, String key
) {
471 Object value
= event
.getProperty(key
);
474 // throw new IllegalArgumentException("Property " + key + " must be set");
475 return value
.toString();
480 * Dependency injection.
483 public void addUiProvider(CmsUiProvider uiProvider
, Map
<String
, Object
> properties
) {
484 if (properties
.containsKey(Constants
.SERVICE_PID
)) {
485 String pid
= (String
) properties
.get(Constants
.SERVICE_PID
);
486 RankedObject
.putIfHigherRank(uiProvidersByPid
, pid
, uiProvider
, properties
);
488 if (properties
.containsKey(EntityConstants
.TYPE
)) {
489 List
<String
> types
= LangUtils
.toStringList(properties
.get(EntityConstants
.TYPE
));
490 for (String type
: types
)
491 RankedObject
.putIfHigherRank(uiProvidersByType
, type
, uiProvider
, properties
);
495 public void removeUiProvider(CmsUiProvider uiProvider
, Map
<String
, Object
> properties
) {
496 if (properties
.containsKey(Constants
.SERVICE_PID
)) {
497 String pid
= (String
) properties
.get(Constants
.SERVICE_PID
);
498 if (uiProvidersByPid
.containsKey(pid
)) {
499 if (uiProvidersByPid
.get(pid
).equals(new RankedObject
<CmsUiProvider
>(uiProvider
, properties
))) {
500 uiProvidersByPid
.remove(pid
);
504 if (properties
.containsKey(EntityConstants
.TYPE
)) {
505 List
<String
> types
= LangUtils
.toStringList(properties
.get(EntityConstants
.TYPE
));
506 for (String type
: types
) {
507 if (uiProvidersByType
.containsKey(type
)) {
508 if (uiProvidersByType
.get(type
).equals(new RankedObject
<CmsUiProvider
>(uiProvider
, properties
))) {
509 uiProvidersByType
.remove(type
);
516 public void addLayer(SuiteLayer layer
, Map
<String
, Object
> properties
) {
517 if (properties
.containsKey(Constants
.SERVICE_PID
)) {
518 String pid
= (String
) properties
.get(Constants
.SERVICE_PID
);
519 RankedObject
.putIfHigherRank(layersByPid
, pid
, layer
, properties
);
521 if (properties
.containsKey(EntityConstants
.TYPE
)) {
522 List
<String
> types
= LangUtils
.toStringList(properties
.get(EntityConstants
.TYPE
));
523 for (String type
: types
)
524 RankedObject
.putIfHigherRank(layersByType
, type
, layer
, properties
);
528 public void removeLayer(SuiteLayer layer
, Map
<String
, Object
> properties
) {
529 if (properties
.containsKey(Constants
.SERVICE_PID
)) {
530 String pid
= (String
) properties
.get(Constants
.SERVICE_PID
);
531 if (layersByPid
.containsKey(pid
)) {
532 if (layersByPid
.get(pid
).equals(new RankedObject
<SuiteLayer
>(layer
, properties
))) {
533 layersByPid
.remove(pid
);
537 if (properties
.containsKey(EntityConstants
.TYPE
)) {
538 List
<String
> types
= LangUtils
.toStringList(properties
.get(EntityConstants
.TYPE
));
539 for (String type
: types
) {
540 if (layersByType
.containsKey(type
)) {
541 if (layersByType
.get(type
).equals(new RankedObject
<CmsUiProvider
>(layer
, properties
))) {
542 layersByType
.remove(type
);
549 public void setCmsUserManager(CmsUserManager cmsUserManager
) {
550 this.cmsUserManager
= cmsUserManager
;