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