1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.ui.api.app.launcherlayout;
35
36 import info.magnolia.cms.security.User;
37 import info.magnolia.cms.security.operations.AccessDefinition;
38 import info.magnolia.config.registry.DefinitionMetadata;
39 import info.magnolia.config.registry.DefinitionProvider;
40 import info.magnolia.config.registry.Registry;
41 import info.magnolia.context.MgnlContext;
42 import info.magnolia.event.EventBus;
43 import info.magnolia.event.SystemEventBus;
44 import info.magnolia.i18nsystem.I18nizer;
45 import info.magnolia.ui.api.app.AppDescriptor;
46 import info.magnolia.ui.api.app.registry.AppDescriptorRegistry;
47 import info.magnolia.ui.api.app.registry.AppRegistryEvent;
48 import info.magnolia.ui.api.app.registry.AppRegistryEventHandler;
49
50 import java.util.ArrayList;
51 import java.util.Collection;
52 import java.util.List;
53 import java.util.Optional;
54 import java.util.concurrent.ExecutionException;
55 import java.util.concurrent.atomic.AtomicReference;
56 import java.util.stream.Collectors;
57
58 import javax.inject.Inject;
59 import javax.inject.Named;
60 import javax.inject.Provider;
61 import javax.inject.Singleton;
62
63 import org.apache.commons.lang3.StringUtils;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67 import com.google.common.cache.CacheBuilder;
68 import com.google.common.cache.CacheLoader;
69 import com.google.common.cache.LoadingCache;
70
71
72
73
74 @Singleton
75 public class AppLauncherLayoutManagerImpl implements AppLauncherLayoutManager {
76
77 private final Logger log = LoggerFactory.getLogger(AppLauncherLayoutManagerImpl.class);
78
79 private final AppDescriptorRegistry appDescriptorRegistry;
80
81 private final EventBus systemEventBus;
82
83 private final AtomicReference<AppLauncherLayoutDefinition> layoutDefinitionReference = new AtomicReference<AppLauncherLayoutDefinition>();
84
85 private final Provider<I18nizer> i18nizerProvider;
86
87 private final LoadingCache<User, AppLauncherLayout> appPermissionsCache = CacheBuilder.newBuilder()
88 .initialCapacity(1)
89 .maximumSize(50)
90 .build(new CacheLoader<User, AppLauncherLayout>() {
91 public AppLauncherLayout load(User user) {
92 log.debug("Computing app-launcher layout permissions for user " + user.getName());
93 return doGetLayoutForUser(user);
94 }
95 });
96
97 @Inject
98 public AppLauncherLayoutManagerImpl(AppDescriptorRegistry appDescriptorRegistry, @Named(SystemEventBus.NAME) EventBus systemEventBus, Provider<I18nizer> i18nizerProvider) {
99 this.appDescriptorRegistry = appDescriptorRegistry;
100 this.systemEventBus = systemEventBus;
101 this.i18nizerProvider = i18nizerProvider;
102
103
104
105
106
107 systemEventBus.addHandler(AppRegistryEvent.class, new AppRegistryEventHandler() {
108
109 @Override
110 public void onAppRegistered(AppRegistryEvent event) {
111 logAndSendChangedEvent(event);
112 }
113
114 @Override
115 public void onAppReregistered(AppRegistryEvent event) {
116 logAndSendChangedEvent(event);
117 }
118
119 @Override
120 public void onAppUnregistered(AppRegistryEvent event) {
121 logAndSendChangedEvent(event);
122 }
123
124
125
126
127 private void logAndSendChangedEvent(AppRegistryEvent event) {
128 String name = event.getAppDescriptorMetadata().getName();
129 log.debug("Got AppRegistryEvent." + event.getEventType() + " for app: " + name);
130 appPermissionsCache.invalidateAll();
131 systemEventBus.fireEvent(new AppLauncherLayoutChangedEvent());
132 log.debug("Sending AppLauncherLayoutChangedEvent on the system bus");
133 }
134 });
135 }
136
137 @Deprecated
138 @Override
139 public AppLauncherLayout getLayoutForCurrentUser() {
140 return getLayoutForUser(MgnlContext.getUser());
141 }
142
143 @Override
144 public AppLauncherLayout getLayoutForUser(User user) {
145 try {
146 return appPermissionsCache.get(user);
147 } catch (ExecutionException e) {
148 log.error("Could not resolve app permissions for user {}", user, e);
149 }
150 return new AppLauncherLayout();
151 }
152
153 private AppLauncherLayout doGetLayoutForUser(User user) {
154 AppLauncherLayoutDefinition layoutDefinition = layoutDefinitionReference.get();
155 if (layoutDefinition == null) {
156 return new AppLauncherLayout();
157 }
158
159 Optional<AppLauncherGroup> defaultGroup = Optional.empty();
160 Collection<String> processedApps = new ArrayList<>();
161
162 AppLauncherLayoutlayout/AppLauncherLayout.html#AppLauncherLayout">AppLauncherLayout layout = new AppLauncherLayout();
163 for (AppLauncherGroupDefinition groupDefinition : layoutDefinition.getGroups()) {
164 if (!isGroupVisibleForUser(groupDefinition, user)) {
165 processedApps.addAll(groupDefinition.getApps().stream().map(AppLauncherGroupEntryDefinition::getName).collect(Collectors.toList()));
166 continue;
167 }
168
169 List<AppLauncherGroupEntry> entries = new ArrayList<>();
170 for (AppLauncherGroupEntryDefinition entryDefinition : groupDefinition.getApps()) {
171 processedApps.add(entryDefinition.getName());
172 final Optional<AppDescriptor> appDescriptor = getAppDescriptor(entryDefinition.getName());
173 if (!appDescriptor.isPresent() || layoutDefinition.getHiddenApps().contains(appDescriptor.get().getName()) || !isAppVisibleForUser(entryDefinition, appDescriptor.get(), user)) {
174 continue;
175 }
176 entries.add(createAppEntry(entryDefinition, appDescriptor.get()));
177 }
178
179 if (!entries.isEmpty() || groupDefinition.getName().equals(layoutDefinition.getDefaultGroup())) {
180 AppLauncherGrouperlayout/AppLauncherGroup.html#AppLauncherGroup">AppLauncherGroup group = new AppLauncherGroup();
181 group.setName(groupDefinition.getName());
182 group.setLabel(groupDefinition.getLabel());
183 group.setColor(groupDefinition.getColor());
184 group.setPermanent(groupDefinition.isPermanent());
185 group.setClientGroup(groupDefinition.isClientGroup());
186 group.setApps(entries);
187 layout.addGroup(group);
188
189 if (groupDefinition.getName().equals(layoutDefinition.getDefaultGroup())) {
190 defaultGroup = Optional.of(group);
191 }
192 }
193 }
194
195 final List<String> unprocessedApps = appDescriptorRegistry.getAllMetadata().stream()
196 .map(DefinitionMetadata::getName)
197 .filter(entry -> !processedApps.contains(entry) && !layoutDefinition.getHiddenApps().contains(entry))
198 .collect(Collectors.toList());
199
200 if (layoutDefinition.getDefaultGroup() != null && !defaultGroup.isPresent()) {
201 log.debug("Configured default group ({}={}) doesn't exist or user doesn't have permission for it.", "config:/modules/ui-admincentral/config/appLauncherLayout@defaultGroup", layoutDefinition.getDefaultGroup());
202 }
203
204 if (defaultGroup.isPresent()) {
205 for (String appName : unprocessedApps) {
206 final Optional<AppDescriptor> appDescriptor = getAppDescriptor(appName);
207 if (appDescriptor.isPresent()) {
208 final ConfiguredAppLauncherGroupEntryDefinitionhtml#ConfiguredAppLauncherGroupEntryDefinition">ConfiguredAppLauncherGroupEntryDefinition appLauncherGroupEntryDefinition = new ConfiguredAppLauncherGroupEntryDefinition();
209 appLauncherGroupEntryDefinition.setName(appName);
210 if (isAppVisibleForUser(appLauncherGroupEntryDefinition, appDescriptor.get(), user)) {
211 defaultGroup.get().getApps().add( createAppEntry(appLauncherGroupEntryDefinition, appDescriptor.get()));
212 log.debug("App {{}} is not registered in the applauncher. Adding it to the default applauncher group {{}}", appName, defaultGroup.get().getName());
213 }
214 }
215 }
216 } else if (!unprocessedApps.isEmpty()) {
217 log.debug("Apps {} are not registered in the applauncher. Configure a default group (at {}) where to add unregistered apps automatically.", unprocessedApps, "config:/modules/ui-admincentral/config/appLauncherLayout@defaultGroup");
218 }
219 return layout;
220 }
221
222 @Override
223 public void setLayout(AppLauncherLayoutDefinition layout) {
224 this.layoutDefinitionReference.set(i18nizerProvider.get().decorate(layout));
225 log.debug("Sending AppLauncherLayoutChangedEvent on the system bus");
226 appPermissionsCache.invalidateAll();
227 systemEventBus.fireEvent(new AppLauncherLayoutChangedEvent());
228 }
229
230 private AppLauncherGroupEntry createAppEntry(AppLauncherGroupEntryDefinition entryDefinition, AppDescriptor appDescriptor) {
231 AppLauncherGroupEntryout/AppLauncherGroupEntry.html#AppLauncherGroupEntry">AppLauncherGroupEntry entry = new AppLauncherGroupEntry();
232 entry.setName(entryDefinition.getName());
233 entry.setEnabled(entryDefinition.isEnabled());
234 entry.setAppDescriptor(appDescriptor);
235 return entry;
236 }
237
238 private Optional<AppDescriptor> getAppDescriptor(String appName) {
239 try {
240 final DefinitionProvider<AppDescriptor> definitionProvider = appDescriptorRegistry.getProvider(appName);
241 if (definitionProvider.isValid()) {
242 AppDescriptor appDescriptor = i18nizerProvider.get().decorate(definitionProvider.get());
243 if (StringUtils.isBlank(appDescriptor.getLabel()) || StringUtils.isBlank(appDescriptor.getIcon())) {
244 log.warn("Label and/or icon for app [{}] are blank. App won't be displayed in the app launcher. Please either define them in the configuration tree or in the app's i18n properties file.", appName);
245 } else {
246 return Optional.of(appDescriptor);
247 }
248 }
249 } catch (Registry.NoSuchDefinitionException e) {
250 log.warn("Definition not found: {}", e.getMessage());
251 } catch (IllegalStateException e) {
252 log.warn("Encountered IllegalStateException while getting appDescriptor: ", e.getMessage(), e);
253 }
254 return Optional.empty();
255 }
256
257 private boolean isAppVisibleForUser(AppLauncherGroupEntryDefinition entry, AppDescriptor appDescriptor, User user) {
258 AccessDefinition permissions = appDescriptor.getPermissions();
259 return entry.isEnabled() && appDescriptor.isEnabled() && (permissions == null || permissions.hasAccess(user));
260 }
261
262 private boolean isGroupVisibleForUser(AppLauncherGroupDefinition group, User user) {
263 AccessDefinition permissions = group.getPermissions();
264 return (permissions == null || permissions.hasAccess(user));
265 }
266 }