001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui.apps;
029
030import org.opencms.db.CmsUserSettings;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsUser;
034import org.opencms.file.types.I_CmsResourceType;
035import org.opencms.json.JSONArray;
036import org.opencms.json.JSONException;
037import org.opencms.main.CmsException;
038import org.opencms.main.CmsLog;
039import org.opencms.main.OpenCms;
040import org.opencms.module.CmsModule;
041import org.opencms.module.CmsModuleManager;
042import org.opencms.ui.CmsUserIconHelper;
043import org.opencms.ui.I_CmsDialogContext;
044import org.opencms.ui.actions.CmsContextMenuActionItem;
045import org.opencms.ui.actions.I_CmsDefaultAction;
046import org.opencms.ui.apps.cacheadmin.CmsCacheAdminConfiguration;
047import org.opencms.ui.apps.cacheadmin.CmsCacheFolder;
048import org.opencms.ui.apps.cacheadmin.CmsCacheViewFlexConfiguration;
049import org.opencms.ui.apps.cacheadmin.CmsCacheViewImageConfiguration;
050import org.opencms.ui.apps.datesearch.CmsDateSearchConfiguration;
051import org.opencms.ui.apps.dbmanager.CmsDbExportConfiguration;
052import org.opencms.ui.apps.dbmanager.CmsDbImportHTTPConfiguration;
053import org.opencms.ui.apps.dbmanager.CmsDbImportServerConfiguration;
054import org.opencms.ui.apps.dbmanager.CmsDbManagerConfiguration;
055import org.opencms.ui.apps.dbmanager.CmsDbManagerFolder;
056import org.opencms.ui.apps.dbmanager.CmsDbPropertiesAppConfiguration;
057import org.opencms.ui.apps.dbmanager.CmsDbRemovePubLocksConfiguration;
058import org.opencms.ui.apps.dbmanager.CmsDbStaticExportConfiguration;
059import org.opencms.ui.apps.dbmanager.CmsDbSynchronizationConfiguration;
060import org.opencms.ui.apps.dbmanager.sqlconsole.CmsSqlConsoleAppConfiguration;
061import org.opencms.ui.apps.filehistory.CmsFileHistoryConfiguration;
062import org.opencms.ui.apps.git.CmsGitAppConfiguration;
063import org.opencms.ui.apps.linkvalidation.CmsLinkInFolderValidationConfiguration;
064import org.opencms.ui.apps.linkvalidation.CmsLinkValidationConfiguration;
065import org.opencms.ui.apps.linkvalidation.CmsLinkValidationExternalConfiguration;
066import org.opencms.ui.apps.linkvalidation.CmsLinkValidationFolder;
067import org.opencms.ui.apps.lists.CmsListManagerConfiguration;
068import org.opencms.ui.apps.logfile.CmsLogFileConfiguration;
069import org.opencms.ui.apps.modules.CmsModuleAppConfiguration;
070import org.opencms.ui.apps.projects.CmsProjectManagerConfiguration;
071import org.opencms.ui.apps.projects.CmsProjectOverviewConfiguration;
072import org.opencms.ui.apps.publishqueue.CmsPublishQueueConfiguration;
073import org.opencms.ui.apps.resourcetypes.CmsResourceTypeAppConfiguration;
074import org.opencms.ui.apps.scheduler.CmsScheduledJobsAppConfig;
075import org.opencms.ui.apps.search.CmsSourceSearchAppConfiguration;
076import org.opencms.ui.apps.searchindex.CmsSearchindexAppConfiguration;
077import org.opencms.ui.apps.sessions.CmsBroadCastConfigurtion;
078import org.opencms.ui.apps.shell.CmsShellAppConfiguration;
079import org.opencms.ui.apps.sitemanager.CmsSiteManagerConfiguration;
080import org.opencms.ui.apps.unusedcontentfinder.CmsUnusedContentFinderConfiguration;
081import org.opencms.ui.apps.user.CmsAccountsAppConfiguration;
082import org.opencms.ui.apps.userdata.CmsUserDataAppConfiguration;
083import org.opencms.ui.contextmenu.CmsContextMenuItemProviderGroup;
084import org.opencms.ui.contextmenu.I_CmsContextMenuItem;
085import org.opencms.ui.contextmenu.I_CmsContextMenuItemProvider;
086import org.opencms.ui.editors.CmsAcaciaEditor;
087import org.opencms.ui.editors.CmsSourceEditor;
088import org.opencms.ui.editors.CmsXmlContentEditor;
089import org.opencms.ui.editors.CmsXmlPageEditor;
090import org.opencms.ui.editors.I_CmsEditor;
091import org.opencms.ui.editors.messagebundle.CmsMessageBundleEditor;
092import org.opencms.util.CmsStringUtil;
093import org.opencms.workplace.tools.CmsTool;
094import org.opencms.workplace.tools.CmsToolManager;
095import org.opencms.workplace.tools.I_CmsToolHandler;
096
097import java.io.File;
098import java.util.ArrayList;
099import java.util.Arrays;
100import java.util.Collection;
101import java.util.Collections;
102import java.util.Comparator;
103import java.util.HashMap;
104import java.util.HashSet;
105import java.util.Iterator;
106import java.util.LinkedHashSet;
107import java.util.List;
108import java.util.Map;
109import java.util.ServiceLoader;
110import java.util.Set;
111
112import org.apache.commons.logging.Log;
113
114import com.google.common.collect.ComparisonChain;
115import com.google.common.collect.Lists;
116import com.google.common.collect.Maps;
117import com.google.common.collect.Sets;
118
119/**
120 * The workplace app manager.<p>
121 */
122public class CmsWorkplaceAppManager {
123
124    /**
125     * Comparator for configuration objects implementing I_CmsHasOrder.<p>
126     *
127     * @param <T> the type to compare
128     */
129    public static class ConfigurationComparator<T extends I_CmsHasOrder> implements Comparator<T> {
130
131        /**
132         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
133         */
134        public int compare(I_CmsHasOrder o1, I_CmsHasOrder o2) {
135
136            return ComparisonChain.start().compare(o1.getOrder(), o2.getOrder()).result();
137        }
138    }
139
140    /**
141     * Wrapper for the navigation state.<p>
142     */
143    public static class NavigationState {
144
145        /** The parameter separator. */
146        public static final String PARAM_SEPARATOR = "/";
147
148        /** The state parameters. */
149        private String m_params = "";
150
151        /** The view/app name. */
152        private String m_viewName = "";
153
154        /**
155         * Constructor.<p>
156         *
157         * @param stateString the state string to parse
158         */
159        public NavigationState(String stateString) {
160
161            if (stateString.startsWith("!")) {
162                stateString = stateString.substring(1);
163            }
164            int separatorPos = stateString.indexOf(PARAM_SEPARATOR);
165            if (separatorPos > 0) {
166                m_viewName = stateString.substring(0, separatorPos);
167                m_params = stateString.substring(separatorPos + 1);
168            } else {
169                m_viewName = stateString;
170            }
171            if (m_viewName.endsWith("/")) {
172                m_viewName = m_viewName.substring(0, m_viewName.length() - 1);
173            }
174        }
175
176        /**
177         * Returns the parameter part of the state.<p>
178         *
179         * @return the parameters
180         */
181        String getParams() {
182
183            return m_params;
184        }
185
186        /**
187         * Returns the view name.<p>
188         *
189         * @return the view name
190         */
191        String getViewName() {
192
193            return m_viewName;
194
195        }
196    }
197
198    /** The administration category id. */
199    public static final String ADMINISTRATION_CATEGORY_ID = "Administration";
200
201    /** The legacy category id. */
202    public static final String LEGACY_CATEGORY_ID = "Legacy";
203
204    /** The main category id. */
205    public static final String MAIN_CATEGORY_ID = "Main";
206
207    /** The toolbar.css resource name. */
208    public static final String TOOLBAR_CSS = "css/toolbar.css";
209
210    /** The workplace app settings additional info key. */
211    public static String WORKPLACE_APP_SETTINGS_KEY = "WORKPLACE_APP_SETTINGS";
212
213    /** The workplace CSS module parameter name. */
214    public static String WORKPLACE_CSS_PARAM = "workplace-css";
215
216    /** The logger for this class. */
217    protected static Log LOG = CmsLog.getLog(CmsWorkplaceAppManager.class.getName());
218
219    /** The default quick launch apps, these can be overridden by the user. */
220    private static final String[] DEFAULT_USER_APPS = new String[] {
221        CmsAccountsAppConfiguration.APP_ID,
222        CmsModuleAppConfiguration.APP_ID};
223
224    /** The available editors. */
225    private static final I_CmsEditor[] EDITORS = new I_CmsEditor[] {
226        new CmsAcaciaEditor(),
227        new CmsSourceEditor(),
228        new CmsXmlContentEditor(),
229        new CmsXmlPageEditor(),
230        new CmsMessageBundleEditor()};
231
232    /** Legacy apps explicitly hidden from new workplace. */
233    private static final Set<String> LEGACY_BLACKLIST = Sets.newConcurrentHashSet(
234        Arrays.asList(
235            "/accounts",
236            "/contenttools",
237            "/git",
238            "/scheduler",
239            "/galleryoverview",
240            "/projects",
241            "/project_overview",
242            "/history",
243            "/sites",
244            "/cache",
245            "/publishqueue",
246            "/database",
247            "/linkvalidation",
248            "/workplace",
249            "/modules",
250            "/searchindex"));
251
252    /** The additional info key for the user quick launch apps. */
253    private static final String QUICK_LAUCH_APPS_KEY = "quick_launch_apps";
254
255    /** The standard quick launch apps. */
256    private static final String[] STANDARD_APPS = new String[] {
257        CmsPageEditorConfiguration.APP_ID,
258        CmsSitemapEditorConfiguration.APP_ID,
259        CmsFileExplorerConfiguration.APP_ID,
260        CmsAppHierarchyConfiguration.APP_ID};
261
262    /** The additional style sheets. */
263    private Collection<String> m_additionalStyleSheets;
264
265    /** The admin cms context. */
266    private CmsObject m_adminCms;
267
268    /** The app categories. */
269    private Map<String, I_CmsAppCategory> m_appCategories;
270
271    /** The configured apps. */
272    private Map<String, I_CmsWorkplaceAppConfiguration> m_appsById = Maps.newHashMap();
273
274    /** The user icon helper. */
275    private CmsUserIconHelper m_iconHelper;
276
277    /** The standard quick launch apps. */
278    private List<I_CmsWorkplaceAppConfiguration> m_standardQuickLaunchApps;
279
280    /** The additional workplace CSS URIs. */
281    private Set<String> m_workplaceCssUris;
282
283    /** Menu item manager. */
284    private CmsContextMenuItemProviderGroup m_workplaceMenuItemProvider;
285
286    /**
287     * Constructor.<p>
288     *
289     * @param adminCms the admin cms context
290     *
291     * @throws CmsException in case initializing the cms object fails
292     */
293    public CmsWorkplaceAppManager(CmsObject adminCms)
294    throws CmsException {
295
296        m_adminCms = adminCms;
297        m_iconHelper = new CmsUserIconHelper(OpenCms.initCmsObject(m_adminCms));
298        m_workplaceMenuItemProvider = new CmsContextMenuItemProviderGroup();
299        m_workplaceMenuItemProvider.addProvider(CmsDefaultMenuItemProvider.class);
300        m_workplaceMenuItemProvider.initialize();
301    }
302
303    /**
304     * Constructor for testing only.<p>
305     */
306    protected CmsWorkplaceAppManager() {
307
308        // nothing to do
309    }
310
311    /**
312     * Returns the additional style sheets provided by I_CmsWorkplaceStylesheetProvider services.<p>
313     *
314     * @return the additional style sheets
315     */
316    public Collection<String> getAdditionalStyleSheets() {
317
318        if (m_additionalStyleSheets == null) {
319            Set<String> stylesheets = new LinkedHashSet<>();
320            for (I_CmsWorkplaceStylesheetProvider provider : ServiceLoader.load(
321                I_CmsWorkplaceStylesheetProvider.class)) {
322                stylesheets.addAll(provider.getStylesheets());
323            }
324            m_additionalStyleSheets = Collections.unmodifiableSet(stylesheets);
325        }
326        return m_additionalStyleSheets;
327    }
328
329    /**
330     * Returns the app configuration with the given id.<p>
331     *
332     * @param appId the app id
333     *
334     * @return the app configuration
335     */
336    public I_CmsWorkplaceAppConfiguration getAppConfiguration(String appId) {
337
338        return m_appsById.get(appId);
339    }
340
341    /**
342     * Returns the app configuration instances for the given ids.<p>
343     *
344     * @param appIds the app ids
345     *
346     * @return the app configurations
347     */
348    public List<I_CmsWorkplaceAppConfiguration> getAppConfigurations(String... appIds) {
349
350        List<I_CmsWorkplaceAppConfiguration> result = new ArrayList<I_CmsWorkplaceAppConfiguration>();
351        for (int i = 0; i < appIds.length; i++) {
352            I_CmsWorkplaceAppConfiguration config = getAppConfiguration(appIds[i]);
353            if (config != null) {
354                result.add(config);
355            }
356        }
357        return result;
358    }
359
360    /**
361     * Returns the user app setting of the given type.<p>
362     *
363     * @param cms the cms context
364     * @param type the app setting type
365     *
366     * @return the app setting
367     *
368     * @throws InstantiationException in case instantiating the settings type fails
369     * @throws IllegalAccessException in case the settings default constructor is not accessible
370     */
371    public <T extends I_CmsAppSettings> T getAppSettings(CmsObject cms, Class<T> type)
372    throws InstantiationException, IllegalAccessException {
373
374        CmsUser user = cms.getRequestContext().getCurrentUser();
375        CmsUserSettings settings = new CmsUserSettings(user);
376        String settingsString = settings.getAdditionalPreference(type.getName(), true);
377        T result = type.newInstance();
378
379        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(settingsString)) {
380
381            result.restoreSettings(settingsString);
382
383        }
384
385        return result;
386    }
387
388    /**
389     * Returns the configured categories.<p>
390     *
391     * @return the app categories
392     */
393    public Collection<I_CmsAppCategory> getCategories() {
394
395        return Collections.unmodifiableCollection(m_appCategories.values());
396    }
397
398    /**
399     * Returns the default action for the given context if available.<p>
400     *
401     * @param context the dialog context
402     *
403     * @return the default action
404     */
405    public I_CmsDefaultAction getDefaultAction(I_CmsDialogContext context) {
406
407        return getDefaultAction(context, getMenuItemProvider());
408    }
409
410    /**
411     * Returns the default action for the given context if available.<p>
412     *
413     * @param context the dialog context
414     * @param menuItemProvider the menu item provider
415     *
416     * @return the default action
417     */
418    public I_CmsDefaultAction getDefaultAction(
419        I_CmsDialogContext context,
420        I_CmsContextMenuItemProvider menuItemProvider) {
421
422        I_CmsDefaultAction result = null;
423        int resultRank = -1;
424        if (context.getResources().size() == 1) {
425            for (I_CmsContextMenuItem menuItem : menuItemProvider.getMenuItems()) {
426                if ((menuItem instanceof CmsContextMenuActionItem)
427                    && (((CmsContextMenuActionItem)menuItem).getWorkplaceAction() instanceof I_CmsDefaultAction)) {
428                    I_CmsDefaultAction action = (I_CmsDefaultAction)((CmsContextMenuActionItem)menuItem).getWorkplaceAction();
429                    if (action.getVisibility(context).isActive()) {
430                        if (result == null) {
431                            result = action;
432                            resultRank = action.getDefaultActionRank(context);
433                        } else {
434                            int rank = action.getDefaultActionRank(context);
435                            if (rank > resultRank) {
436                                result = action;
437                                resultRank = rank;
438                            }
439                        }
440                    }
441                }
442            }
443        }
444        return result;
445    }
446
447    /**
448     * Gets all configured quick launch apps, independent of the current user.<p>
449     *
450     * @return the quick launch apps
451     */
452    public List<I_CmsWorkplaceAppConfiguration> getDefaultQuickLaunchConfigurations() {
453
454        if (m_standardQuickLaunchApps == null) {
455
456            m_standardQuickLaunchApps = Collections.unmodifiableList(getAppConfigurations(STANDARD_APPS));
457        }
458        return m_standardQuickLaunchApps;
459    }
460
461    /**
462     * Returns the editor for the given resource.<p>
463     *
464     * @param cms the CMS context
465     * @param resource the resource to edit
466     * @param plainText if plain text editing is required
467     *
468     * @return the editor
469     */
470    public I_CmsEditor getEditorForResource(CmsObject cms, CmsResource resource, boolean plainText) {
471
472        List<I_CmsEditor> editors = new ArrayList<I_CmsEditor>();
473        for (int i = 0; i < EDITORS.length; i++) {
474            try {
475                if (EDITORS[i].matchesResource(cms, resource, plainText)) {
476                    editors.add(EDITORS[i]);
477                }
478            } catch (Exception e) {
479                LOG.error(e.getLocalizedMessage(), e);
480            }
481        }
482        I_CmsEditor result = null;
483        if (editors.size() == 1) {
484            result = editors.get(0);
485        } else if (editors.size() > 1) {
486            Collections.sort(editors, new Comparator<I_CmsEditor>() {
487
488                public int compare(I_CmsEditor o1, I_CmsEditor o2) {
489
490                    return o1.getPriority() > o2.getPriority() ? -1 : 1;
491                }
492            });
493            result = editors.get(0);
494        }
495        return result;
496    }
497
498    /**
499     * Returns the editor for the given resource type.<p>
500     *
501     * @param type the resource type to edit
502     * @param plainText if plain text editing is required
503     *
504     * @return the editor
505     */
506    public I_CmsEditor getEditorForType(I_CmsResourceType type, boolean plainText) {
507
508        List<I_CmsEditor> editors = new ArrayList<I_CmsEditor>();
509        for (int i = 0; i < EDITORS.length; i++) {
510            if (EDITORS[i].matchesType(type, plainText)) {
511                editors.add(EDITORS[i]);
512            }
513        }
514        I_CmsEditor result = null;
515        if (editors.size() == 1) {
516            result = editors.get(0);
517        } else if (editors.size() > 1) {
518            Collections.sort(editors, new Comparator<I_CmsEditor>() {
519
520                public int compare(I_CmsEditor o1, I_CmsEditor o2) {
521
522                    return o1.getPriority() > o2.getPriority() ? -1 : 1;
523                }
524            });
525            result = editors.get(0);
526        }
527        return result;
528    }
529
530    /**
531     * Gets the menu item provider for the workplace.<p>
532     *
533     * @return the menu item provider
534     */
535    public I_CmsContextMenuItemProvider getMenuItemProvider() {
536
537        return m_workplaceMenuItemProvider;
538    }
539
540    /**
541     * Gets the configured quick launch apps which are visible for the current user.<p>
542     *
543     * @param cms the current CMS context
544     * @return the list of available quick launch apps
545     */
546    public List<I_CmsWorkplaceAppConfiguration> getQuickLaunchConfigurations(CmsObject cms) {
547
548        List<I_CmsWorkplaceAppConfiguration> result = new ArrayList<I_CmsWorkplaceAppConfiguration>();
549        result.addAll(getDefaultQuickLaunchConfigurations());
550        result.addAll(getUserQuickLauchConfigurations(cms));
551        Iterator<I_CmsWorkplaceAppConfiguration> it = result.iterator();
552        while (it.hasNext()) {
553            I_CmsWorkplaceAppConfiguration appConfig = it.next();
554            CmsAppVisibilityStatus visibility = appConfig.getVisibility(cms);
555            if (!visibility.isVisible()) {
556                it.remove();
557            }
558        }
559        return result;
560    }
561
562    /**
563     * Returns the user icon helper.<p>
564     *
565     * @return the user icon helper
566     */
567    public CmsUserIconHelper getUserIconHelper() {
568
569        return m_iconHelper;
570    }
571
572    /**
573     * Returns all available workplace apps.<p>
574     *
575     * @return the available workpllace apps
576     */
577    public Collection<I_CmsWorkplaceAppConfiguration> getWorkplaceApps() {
578
579        return m_appsById.values();
580    }
581
582    /**
583     * Returns the additional workplace CSS URIs.<p>
584     *
585     * @return the additional workplace CSS URIs
586     */
587    public Collection<String> getWorkplaceCssUris() {
588
589        return m_workplaceCssUris;
590    }
591
592    /**
593     * Initializes the additional workplace CSS URIs.<p>
594     * They will be taken from the module parameter 'workplace-css' if present in any module.<p>
595     *
596     * @param moduleManager the module manager instance
597     */
598    public void initWorkplaceCssUris(CmsModuleManager moduleManager) {
599
600        Set<String> cssUris = new HashSet<String>();
601        for (CmsModule module : moduleManager.getAllInstalledModules()) {
602            String param = module.getParameter(WORKPLACE_CSS_PARAM);
603            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(param)) {
604                cssUris.add(param);
605            }
606        }
607        File cssFile = new File(
608            OpenCms.getSystemInfo().getAbsoluteRfsPathRelativeToWebApplication(
609                CmsStringUtil.joinPaths("resources", TOOLBAR_CSS)));
610        if (cssFile.exists()) {
611            cssUris.add(TOOLBAR_CSS);
612        }
613        m_workplaceCssUris = Collections.unmodifiableSet(cssUris);
614    }
615
616    /**
617     * Loads the workplace apps.<p>
618     */
619    public void loadApps() {
620
621        m_appsById.clear();
622        m_appCategories = loadCategories();
623        addAppConfigurations(loadDefaultApps());
624        addAppConfigurations(loadAppsUsingServiceLoader());
625        addAppConfigurations(loadLegacyApps());
626    }
627
628    /**
629     * Stores the given app setting within the users additional info.<p>
630     *
631     * @param cms the cms context
632     * @param type the app setting type, used as the settings key
633     * @param appSettings the settings to store
634     */
635    public void storeAppSettings(CmsObject cms, Class<? extends I_CmsAppSettings> type, I_CmsAppSettings appSettings) {
636
637        CmsUser user = cms.getRequestContext().getCurrentUser();
638        CmsUserSettings settings = new CmsUserSettings(user);
639
640        String currentSetting = settings.getAdditionalPreference(type.getName(), true);
641        String state = appSettings.getSettingsString();
642        if (((state == null) && (currentSetting == null)) || ((state != null) && state.equals(currentSetting))) {
643            // nothing changed
644            return;
645        }
646
647        settings.setAdditionalPreference(type.getName(), state);
648        try {
649            settings.save(cms);
650        } catch (CmsException e) {
651            LOG.error("Failed to store workplace app settings for type " + type.getName(), e);
652        }
653    }
654
655    /**
656     * Returns the quick launch apps set for the current user.<p>
657     *
658     * @param cms the cms context
659     *
660     * @return the quick launch app configurations
661     */
662    protected List<I_CmsWorkplaceAppConfiguration> getUserQuickLauchConfigurations(CmsObject cms) {
663
664        String apps_info = (String)cms.getRequestContext().getCurrentUser().getAdditionalInfo(QUICK_LAUCH_APPS_KEY);
665        String[] appIds = null;
666        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(apps_info)) {
667            try {
668                JSONArray ids = new JSONArray(apps_info);
669                appIds = new String[ids.length()];
670                for (int i = 0; i < appIds.length; i++) {
671                    appIds[i] = ids.getString(i);
672                }
673            } catch (JSONException e) {
674                LOG.error("Error parsing user quick launch apps setting.", e);
675                appIds = null;
676            }
677        }
678        return getAppConfigurations(appIds != null ? appIds : DEFAULT_USER_APPS);
679    }
680
681    /**
682     * Writes the user quick launch apps setting to the user additional info.<p>
683     *
684     * @param cms the cms context
685     * @param apps the app ids
686     *
687     * @throws Exception in case writing the user fails
688     */
689    protected void setUserQuickLaunchApps(CmsObject cms, List<String> apps) throws Exception {
690
691        JSONArray appIds = new JSONArray(apps);
692        CmsUser user = cms.getRequestContext().getCurrentUser();
693        String infoValue = appIds.toString();
694        String previousApps = (String)user.getAdditionalInfo(QUICK_LAUCH_APPS_KEY);
695        // remove the additional info value to use default setting, in case the selected apps match the default apps
696        if (new JSONArray(DEFAULT_USER_APPS).toString().equals(infoValue)) {
697            infoValue = null;
698        }
699        // check if the additional info value needs to be changed
700        if ((infoValue == previousApps) || ((infoValue != null) && infoValue.equals(previousApps))) {
701
702            return;
703        }
704        if (infoValue == null) {
705            user.deleteAdditionalInfo(QUICK_LAUCH_APPS_KEY);
706        } else {
707            user.setAdditionalInfo(QUICK_LAUCH_APPS_KEY, infoValue);
708        }
709        cms.writeUser(user);
710    }
711
712    /**
713     * Adds the given app configuration.<p>
714     *
715     * @param appConfigs the app configuration
716     */
717    private void addAppConfigurations(Collection<I_CmsWorkplaceAppConfiguration> appConfigs) {
718
719        for (I_CmsWorkplaceAppConfiguration appConfig : appConfigs) {
720            I_CmsWorkplaceAppConfiguration old = m_appsById.get(appConfig.getId());
721            if ((old == null) || (old.getPriority() < appConfig.getPriority())) {
722                m_appsById.put(appConfig.getId(), appConfig);
723            }
724        }
725    }
726
727    /**
728     * Loads the App Folder.<p>
729     *
730     * @return list of all app folder
731     */
732    private List<I_CmsFolderAppCategory> loadAppFolder() {
733
734        List<I_CmsFolderAppCategory> result = new ArrayList<I_CmsFolderAppCategory>();
735        result.addAll(
736            Arrays.<I_CmsFolderAppCategory> asList(
737                new CmsLinkValidationFolder(),
738                new CmsDbManagerFolder(),
739                new CmsCacheFolder()));
740        return result;
741    }
742
743    /**
744     * Returns the configured apps using the service loader.<p>
745     *
746     * @return tthe configured apps
747     */
748    private List<I_CmsWorkplaceAppConfiguration> loadAppsUsingServiceLoader() {
749
750        List<I_CmsWorkplaceAppConfiguration> appConfigurations = new ArrayList<I_CmsWorkplaceAppConfiguration>();
751        Iterator<I_CmsWorkplaceAppConfiguration> configs = ServiceLoader.load(
752            I_CmsWorkplaceAppConfiguration.class).iterator();
753        while (configs.hasNext()) {
754            try {
755                I_CmsWorkplaceAppConfiguration config = configs.next();
756                appConfigurations.add(config);
757            } catch (Throwable t) {
758                LOG.error("Error loading workplace app configuration from classpath.", t);
759            }
760        }
761        return appConfigurations;
762    }
763
764    /**
765     * Loads the app categories.<p>
766     *
767     * @return the app categories
768     */
769    private Map<String, I_CmsAppCategory> loadCategories() {
770
771        Map<String, I_CmsAppCategory> appCategories = new HashMap<String, I_CmsAppCategory>();
772        CmsAppCategory main = new CmsAppCategory(MAIN_CATEGORY_ID, null, 0, 0);
773        appCategories.put(main.getId(), main);
774        CmsAppCategory admin = new CmsAppCategory(ADMINISTRATION_CATEGORY_ID, null, 5, 0);
775        appCategories.put(admin.getId(), admin);
776        CmsAppCategory legacy = new CmsAppCategory(LEGACY_CATEGORY_ID, null, 10, 0);
777        appCategories.put(legacy.getId(), legacy);
778        List<I_CmsFolderAppCategory> folder = loadAppFolder();
779        for (I_CmsFolderAppCategory appFolder : folder) {
780            appCategories.put(appFolder.getId(), appFolder);
781        }
782        Iterator<I_CmsAppCategory> categoryIt = ServiceLoader.load(I_CmsAppCategory.class).iterator();
783        while (categoryIt.hasNext()) {
784            try {
785                I_CmsAppCategory cat = categoryIt.next();
786                if (!appCategories.containsKey(cat.getId())
787                    || (appCategories.get(cat.getId()).getPriority() < cat.getPriority())) {
788                    appCategories.put(cat.getId(), cat);
789                }
790            } catch (Throwable t) {
791                LOG.error("Error loading workplace app category from classpath.", t);
792            }
793        }
794        return appCategories;
795    }
796
797    /**
798     * Loads the default apps.<p>
799     *
800     * @return the default apps
801     */
802    private Collection<I_CmsWorkplaceAppConfiguration> loadDefaultApps() {
803
804        List<I_CmsWorkplaceAppConfiguration> result = Lists.newArrayList();
805        result.addAll(
806            Arrays.<I_CmsWorkplaceAppConfiguration> asList(
807                new CmsSitemapEditorConfiguration(),
808                new CmsPageEditorConfiguration(),
809                new CmsFileExplorerConfiguration(),
810                new CmsScheduledJobsAppConfig(),
811                new CmsAppHierarchyConfiguration(),
812                new CmsEditorConfiguration(),
813                new CmsQuickLaunchEditorConfiguration(),
814                new CmsProjectManagerConfiguration(),
815                new CmsProjectOverviewConfiguration(),
816                new CmsCacheAdminConfiguration(),
817                new CmsCacheViewFlexConfiguration(),
818                new CmsCacheViewImageConfiguration(),
819                new CmsFileHistoryConfiguration(),
820                new CmsLinkValidationConfiguration(),
821                new CmsLinkValidationExternalConfiguration(),
822                new CmsLinkInFolderValidationConfiguration(),
823                new CmsDbManagerConfiguration(),
824                new CmsDbImportHTTPConfiguration(),
825                new CmsDbImportServerConfiguration(),
826                new CmsDbExportConfiguration(),
827                new CmsDbStaticExportConfiguration(),
828                new CmsSqlConsoleAppConfiguration(),
829                new CmsDbRemovePubLocksConfiguration(),
830                new CmsDbSynchronizationConfiguration(),
831                new CmsDbPropertiesAppConfiguration(),
832                new CmsSearchindexAppConfiguration(),
833                new CmsLogFileConfiguration(),
834                new CmsSourceSearchAppConfiguration(),
835                new CmsListManagerConfiguration(),
836                new CmsSiteManagerConfiguration(),
837                new CmsPublishQueueConfiguration(),
838                new CmsGitAppConfiguration(),
839                new CmsBroadCastConfigurtion(),
840                new CmsModuleAppConfiguration(),
841                new CmsAccountsAppConfiguration(),
842                new CmsShellAppConfiguration(),
843                new CmsResourceTypeAppConfiguration(),
844                new CmsUserDataAppConfiguration(),
845                new CmsUnusedContentFinderConfiguration(),
846                new CmsDateSearchConfiguration()));
847
848        return result;
849    }
850
851    /**
852     * Loads the legacy apps.<p>
853     *
854     * @return the legacy apps
855     */
856    private Collection<I_CmsWorkplaceAppConfiguration> loadLegacyApps() {
857
858        List<I_CmsWorkplaceAppConfiguration> configs = new ArrayList<I_CmsWorkplaceAppConfiguration>();
859        // avoid accessing the workplace manager during test case
860        if (OpenCms.getRunLevel() >= OpenCms.RUNLEVEL_2_INITIALIZING) {
861            List<CmsTool> tools = OpenCms.getWorkplaceManager().getToolManager().getToolHandlers();
862            for (CmsTool tool : tools) {
863
864                I_CmsToolHandler handler = tool.getHandler();
865                String path = handler.getPath();
866
867                // only collecting first path level tools
868                if ((path.length() > 1) && (path.indexOf(CmsToolManager.TOOLPATH_SEPARATOR, 1) < 0)) {
869                    if (!LEGACY_BLACKLIST.contains(path)) {
870                        configs.add(new CmsLegacyAppConfiguration(handler));
871                    }
872                }
873
874            }
875        }
876        return configs;
877    }
878
879}