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;
029
030import org.opencms.ade.galleries.CmsSiteSelectorOptionBuilder;
031import org.opencms.ade.galleries.shared.CmsSiteSelectorOption;
032import org.opencms.configuration.preferences.CmsLanguagePreference;
033import org.opencms.db.CmsUserSettings;
034import org.opencms.file.CmsGroup;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProject;
037import org.opencms.file.CmsUser;
038import org.opencms.file.types.A_CmsResourceTypeFolderBase;
039import org.opencms.file.types.CmsResourceTypeXmlContent;
040import org.opencms.file.types.I_CmsResourceType;
041import org.opencms.i18n.CmsEncoder;
042import org.opencms.i18n.CmsMessages;
043import org.opencms.i18n.I_CmsMessageBundle;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.OpenCms;
047import org.opencms.security.CmsOrganizationalUnit;
048import org.opencms.security.CmsRole;
049import org.opencms.security.I_CmsPrincipal;
050import org.opencms.ui.apps.CmsAppWorkplaceUi;
051import org.opencms.ui.apps.Messages;
052import org.opencms.ui.apps.user.CmsOUHandler;
053import org.opencms.ui.components.OpenCmsTheme;
054import org.opencms.ui.contextmenu.CmsContextMenu;
055import org.opencms.ui.contextmenu.I_CmsSimpleContextMenuEntry;
056import org.opencms.util.CmsFileUtil;
057import org.opencms.util.CmsMacroResolver;
058import org.opencms.util.CmsStringUtil;
059import org.opencms.util.CmsUUID;
060import org.opencms.workplace.CmsWorkplace;
061import org.opencms.workplace.CmsWorkplaceMessages;
062import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
063import org.opencms.workplace.explorer.CmsResourceUtil;
064
065import java.io.ByteArrayInputStream;
066import java.io.IOException;
067import java.io.InputStream;
068import java.io.UnsupportedEncodingException;
069import java.util.ArrayList;
070import java.util.Arrays;
071import java.util.Collection;
072import java.util.Collections;
073import java.util.HashSet;
074import java.util.Iterator;
075import java.util.LinkedHashMap;
076import java.util.List;
077import java.util.Locale;
078import java.util.Map;
079import java.util.Map.Entry;
080
081import javax.servlet.http.HttpServletRequest;
082
083import org.apache.commons.lang3.ClassUtils;
084import org.apache.commons.logging.Log;
085
086import com.google.common.base.Function;
087import com.google.common.base.Predicate;
088import com.google.common.collect.Lists;
089import com.vaadin.server.ErrorMessage;
090import com.vaadin.server.ExternalResource;
091import com.vaadin.server.FontIcon;
092import com.vaadin.server.Resource;
093import com.vaadin.server.VaadinService;
094import com.vaadin.shared.MouseEventDetails.MouseButton;
095import com.vaadin.shared.Version;
096import com.vaadin.ui.AbstractComponent;
097import com.vaadin.ui.Alignment;
098import com.vaadin.ui.Button;
099import com.vaadin.ui.Button.ClickEvent;
100import com.vaadin.ui.Button.ClickListener;
101import com.vaadin.ui.Component;
102import com.vaadin.ui.ComponentContainer;
103import com.vaadin.ui.HasComponents;
104import com.vaadin.ui.JavaScript;
105import com.vaadin.ui.Panel;
106import com.vaadin.ui.SingleComponentContainer;
107import com.vaadin.ui.TextField;
108import com.vaadin.ui.UI;
109import com.vaadin.ui.Window;
110import com.vaadin.ui.declarative.Design;
111import com.vaadin.ui.themes.ValoTheme;
112import com.vaadin.v7.data.Container;
113import com.vaadin.v7.data.Container.Filter;
114import com.vaadin.v7.data.Item;
115import com.vaadin.v7.data.util.IndexedContainer;
116import com.vaadin.v7.event.ItemClickEvent;
117import com.vaadin.v7.shared.ui.combobox.FilteringMode;
118import com.vaadin.v7.ui.AbstractField;
119import com.vaadin.v7.ui.ComboBox;
120import com.vaadin.v7.ui.Label;
121import com.vaadin.v7.ui.OptionGroup;
122import com.vaadin.v7.ui.Table;
123import com.vaadin.v7.ui.VerticalLayout;
124
125/**
126 * Vaadin utility functions.<p>
127 *
128 */
129@SuppressWarnings("deprecation")
130public final class CmsVaadinUtils {
131
132    /**
133     * Helper class for building option groups.<p>
134     */
135    public static class OptionGroupBuilder {
136
137        /** The option group being built. */
138        private OptionGroup m_optionGroup = new OptionGroup();
139
140        /**
141         * Adds an option.<p>
142         *
143         * @param key the option key
144         * @param text the option text
145         *
146         * @return this instance
147         */
148        public OptionGroupBuilder add(String key, String text) {
149
150            m_optionGroup.addItem(key);
151            m_optionGroup.setItemCaption(key, text);
152            return this;
153        }
154
155        /**
156         * Returns the option group.<p>
157         *
158         * @return the option group
159         */
160        public OptionGroup build() {
161
162            return m_optionGroup;
163        }
164
165        /**
166         * Adds horizontal style to option group.<p>
167         *
168         * @return this instance
169         */
170        public OptionGroupBuilder horizontal() {
171
172            m_optionGroup.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL);
173            return this;
174        }
175    }
176
177    /** Container property ids. */
178    public static enum PropertyId {
179        /** The caption id. */
180        caption,
181        /** The icon id. */
182        icon,
183        /** The is folder id. */
184        isFolder,
185        /** The is XML content id. */
186        isXmlContent
187    }
188
189    /** Container filter for the resource type container to show not folder types only. */
190    public static final Filter FILTER_NO_FOLDERS = new Filter() {
191
192        private static final long serialVersionUID = 1L;
193
194        public boolean appliesToProperty(Object propertyId) {
195
196            return PropertyId.isFolder.equals(propertyId);
197        }
198
199        public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException {
200
201            return !((Boolean)item.getItemProperty(PropertyId.isFolder).getValue()).booleanValue();
202        }
203    };
204
205    /** Container filter for the resource type container to show XML content types only. */
206    public static final Filter FILTER_XML_CONTENTS = new Filter() {
207
208        private static final long serialVersionUID = 1L;
209
210        public boolean appliesToProperty(Object propertyId) {
211
212            return PropertyId.isXmlContent.equals(propertyId);
213        }
214
215        public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException {
216
217            return ((Boolean)item.getItemProperty(PropertyId.isXmlContent).getValue()).booleanValue();
218        }
219    };
220
221    /** The combo box label item property id. */
222    public static final String PROPERTY_LABEL = "label";
223
224    /** The combo box value item property id. */
225    public static final String PROPERTY_VALUE = "value";
226
227    /** The Vaadin bootstrap script, with some macros to be dynamically replaced later. */
228    protected static final String BOOTSTRAP_SCRIPT = "vaadin.initApplication(\"%(elementId)\", {\n"
229        + "        \"browserDetailsUrl\": \"%(vaadinServlet)\",\n"
230        + "        \"serviceUrl\": \"%(vaadinServlet)\",\n"
231        + "        \"widgetset\": \"org.opencms.ui.WidgetSet\",\n"
232        + "        \"theme\": \"opencms\",\n"
233        + "        \"versionInfo\": {\"vaadinVersion\": \"%(vaadinVersion)\"},\n"
234        + "        \"vaadinDir\": \"%(vaadinDir)\",\n"
235        + "        \"heartbeatInterval\": 30,\n"
236        + "        \"debug\": false,\n"
237        + "        \"standalone\": false,\n"
238        + "        \"authErrMsg\": {\n"
239        + "            \"message\": \"Take note of any unsaved data, \"+\n"
240        + "                       \"and <u>click here<\\/u> to continue.\",\n"
241        + "            \"caption\": \"Authentication problem\"\n"
242        + "        },\n"
243        + "        \"comErrMsg\": {\n"
244        + "            \"message\": \"Take note of any unsaved data, \"+\n"
245        + "                       \"and <u>click here<\\/u> to continue.\",\n"
246        + "            \"caption\": \"Communication problem\"\n"
247        + "        },\n"
248        + "        \"sessExpMsg\": {\n"
249        + "            \"message\": \"Take note of any unsaved data, \"+\n"
250        + "                       \"and <u>click here<\\/u> to continue.\",\n"
251        + "            \"caption\": \"Session Expired\"\n"
252        + "        }\n"
253        + "    });";
254
255    /** The logger of this class. */
256    private static final Log LOG = CmsLog.getLog(CmsVaadinUtils.class);
257
258    /**
259     * Hidden default constructor for utility class.<p>
260     */
261    private CmsVaadinUtils() {
262
263    }
264
265    /**
266     * Builds a container for use in combo boxes from a map of key/value pairs, where the keys are options and the values are captions.<p>
267     *
268     * @param captionProperty the property name to use for captions
269     * @param map the map
270     * @return the new container
271     */
272    public static IndexedContainer buildContainerFromMap(String captionProperty, Map<String, String> map) {
273
274        IndexedContainer container = new IndexedContainer();
275        for (Map.Entry<String, String> entry : map.entrySet()) {
276            container.addItem(entry.getKey()).getItemProperty(captionProperty).setValue(entry.getValue());
277        }
278        return container;
279    }
280
281    /**
282     * Centers the parent window of given component.<p>
283     *
284     * @param component Component as child of window
285     */
286    public static void centerWindow(Component component) {
287
288        Window window = getWindow(component);
289        if (window != null) {
290            window.center();
291        }
292    }
293
294    /**
295     * Closes the window containing the given component.
296     *
297     * @param component a component
298     */
299    public static void closeWindow(Component component) {
300
301        Window window = getWindow(component);
302        if (window != null) {
303            window.close();
304        }
305    }
306
307    /**
308     * Creates a click listener which calls a Runnable when activated.<p>
309     *
310     * @param action the Runnable to execute on a click
311     *
312     * @return the click listener
313     */
314    public static Button.ClickListener createClickListener(final Runnable action) {
315
316        return new Button.ClickListener() {
317
318            /** Serial version id. */
319            private static final long serialVersionUID = 1L;
320
321            public void buttonClick(ClickEvent event) {
322
323                action.run();
324            }
325        };
326    }
327
328    /**
329     * Simple context menu handler for multi-select tables.
330     *
331     * @param table the table
332     * @param menu the table's context menu
333     * @param event the click event
334     * @param entries the context menu entries
335     */
336    @SuppressWarnings("unchecked")
337    public static <T> void defaultHandleContextMenuForMultiselect(
338        Table table,
339        CmsContextMenu menu,
340        ItemClickEvent event,
341        List<I_CmsSimpleContextMenuEntry<Collection<T>>> entries) {
342
343        if (!event.isCtrlKey() && !event.isShiftKey()) {
344            if (event.getButton().equals(MouseButton.RIGHT)) {
345                Collection<T> oldValue = ((Collection<T>)table.getValue());
346                if (oldValue.isEmpty() || !oldValue.contains(event.getItemId())) {
347                    table.setValue(new HashSet<Object>(Arrays.asList(event.getItemId())));
348                }
349                Collection<T> selection = (Collection<T>)table.getValue();
350                menu.setEntries(entries, selection);
351                menu.openForTable(event, table);
352            }
353        }
354
355    }
356
357    /**
358     * Reads the content of an input stream into a string (using UTF-8 encoding), performs a function on the string, and returns the result
359     * again as an input stream.<p>
360     *
361     * @param stream the stream producing the input data
362     * @param transformation the function to apply to the input
363     *
364     * @return the stream producing the transformed input data
365     */
366    public static InputStream filterUtf8ResourceStream(InputStream stream, Function<String, String> transformation) {
367
368        try {
369            byte[] streamData = CmsFileUtil.readFully(stream);
370            String dataAsString = new String(streamData, "UTF-8");
371            byte[] transformedData = transformation.apply(dataAsString).getBytes("UTF-8");
372            return new ByteArrayInputStream(transformedData);
373        } catch (UnsupportedEncodingException e) {
374            LOG.error(e.getLocalizedMessage(), e);
375            return null;
376        } catch (IOException e) {
377            LOG.error(e.getLocalizedMessage(), e);
378            throw new RuntimeException(e);
379        }
380    }
381
382    /**
383     * Get all groups with blacklist.<p>
384     *
385     * @param cms CmsObject
386     * @param ouFqn ou name
387     * @param propCaption property
388     * @param propIcon property for icon
389     * @param propOu organizational unit
390     * @param blackList blacklist
391     * @param iconProvider the icon provider
392     * @return indexed container
393     */
394    public static IndexedContainer getAvailableGroupsContainerWithout(
395        CmsObject cms,
396        String ouFqn,
397        String propCaption,
398        String propIcon,
399        String propOu,
400        List<CmsGroup> blackList,
401        java.util.function.Function<CmsGroup, CmsCssIcon> iconProvider) {
402
403        if (blackList == null) {
404            blackList = new ArrayList<CmsGroup>();
405        }
406        IndexedContainer res = new IndexedContainer();
407        res.addContainerProperty(propCaption, String.class, "");
408        res.addContainerProperty(propOu, String.class, "");
409        if (propIcon != null) {
410            res.addContainerProperty(propIcon, CmsCssIcon.class, null);
411        }
412        try {
413            for (CmsGroup group : OpenCms.getRoleManager().getManageableGroups(cms, ouFqn, true)) {
414                if (!blackList.contains(group)) {
415                    Item item = res.addItem(group);
416                    if (item == null) {
417                        continue;
418                    }
419                    if (iconProvider != null) {
420                        item.getItemProperty(propIcon).setValue(iconProvider.apply(group));
421                    }
422                    item.getItemProperty(propCaption).setValue(group.getSimpleName());
423                    item.getItemProperty(propOu).setValue(group.getOuFqn());
424                }
425            }
426
427        } catch (CmsException e) {
428            LOG.error("Unable to read groups", e);
429        }
430        return res;
431    }
432
433    /**
434     * Returns the available projects.<p>
435     *
436     * @param cms the CMS context
437     *
438     * @return the available projects
439     */
440    public static List<CmsProject> getAvailableProjects(CmsObject cms) {
441
442        // get all project information
443        List<CmsProject> allProjects;
444        try {
445            String ouFqn = "";
446            CmsUserSettings settings = new CmsUserSettings(cms);
447            if (!settings.getListAllProjects()) {
448                ouFqn = cms.getRequestContext().getCurrentUser().getOuFqn();
449            }
450            allProjects = new ArrayList<CmsProject>(
451                OpenCms.getOrgUnitManager().getAllAccessibleProjects(cms, ouFqn, settings.getListAllProjects()));
452            Iterator<CmsProject> itProjects = allProjects.iterator();
453            while (itProjects.hasNext()) {
454                CmsProject prj = itProjects.next();
455                if (prj.isHiddenFromSelector()) {
456                    itProjects.remove();
457                }
458            }
459        } catch (CmsException e) {
460            // should usually never happen
461            LOG.error(e.getLocalizedMessage(), e);
462            allProjects = Collections.emptyList();
463        }
464        return allProjects;
465    }
466
467    /**
468     * Builds an IndexedContainer containing the sites selectable by the current user.<p>
469     *
470     * @param cms the CMS context
471     * @param captionPropertyName the name of the property used to store captions
472     *
473     * @return the container with the available sites
474     */
475    public static IndexedContainer getAvailableSitesContainer(CmsObject cms, String captionPropertyName) {
476
477        IndexedContainer availableSites = new IndexedContainer();
478        availableSites.addContainerProperty(captionPropertyName, String.class, null);
479        for (Map.Entry<String, String> entry : getAvailableSitesMap(cms).entrySet()) {
480            Item siteItem = availableSites.addItem(entry.getKey());
481            siteItem.getItemProperty(captionPropertyName).setValue(entry.getValue());
482        }
483        return availableSites;
484    }
485
486    /**
487     * Gets available sites as a LinkedHashMap, with site roots as keys and site labels as values.
488     *
489     * @param cms the current CMS context
490     * @return the map of available sites
491     */
492    public static LinkedHashMap<String, String> getAvailableSitesMap(CmsObject cms) {
493
494        CmsSiteSelectorOptionBuilder optBuilder = new CmsSiteSelectorOptionBuilder(cms);
495        optBuilder.addNormalSites(true, (new CmsUserSettings(cms)).getStartFolder());
496        optBuilder.addSharedSite();
497        LinkedHashMap<String, String> result = new LinkedHashMap<String, String>();
498        for (CmsSiteSelectorOption option : optBuilder.getOptions()) {
499            result.put(option.getSiteRoot(), option.getMessage());
500        }
501        String currentSiteRoot = cms.getRequestContext().getSiteRoot();
502        if (!result.containsKey(currentSiteRoot)) {
503            result.put(currentSiteRoot, currentSiteRoot);
504        }
505        return result;
506    }
507
508    /**
509     * Returns the Javascript code to use for initializing a Vaadin UI.<p>
510     *
511     * @param cms the CMS context
512     * @param elementId the id of the DOM element in which to initialize the UI
513     * @param servicePath the UI servlet path
514     * @return the Javascript code to initialize Vaadin
515     *
516     * @throws Exception if something goes wrong
517     */
518    public static String getBootstrapScript(CmsObject cms, String elementId, String servicePath) throws Exception {
519
520        String script = BOOTSTRAP_SCRIPT;
521        CmsMacroResolver resolver = new CmsMacroResolver();
522        String context = OpenCms.getSystemInfo().getContextPath();
523        String vaadinDir = CmsStringUtil.joinPaths(context, "VAADIN/");
524        String vaadinVersion = Version.getFullVersion();
525        String vaadinServlet = CmsStringUtil.joinPaths(context, servicePath);
526        String vaadinBootstrap = CmsStringUtil.joinPaths(context, "VAADIN/vaadinBootstrap.js");
527        resolver.addMacro("vaadinDir", vaadinDir);
528        resolver.addMacro("vaadinVersion", vaadinVersion);
529        resolver.addMacro("elementId", elementId);
530        resolver.addMacro("vaadinServlet", vaadinServlet);
531        resolver.addMacro("vaadinBootstrap", vaadinBootstrap);
532        script = resolver.resolveMacros(script);
533        return script;
534
535    }
536
537    /**
538     * Returns the path to the design template file of the given component.<p>
539     *
540     * @param component the component
541     *
542     * @return the path
543     */
544    public static String getDefaultDesignPath(Component component) {
545
546        String className = component.getClass().getName();
547        String designPath = className.replace(".", "/") + ".html";
548        return designPath;
549    }
550
551    /**
552     * Gets container with alls groups of a certain user.
553     *
554     * @param cms cmsobject
555     * @param user to find groups for
556     * @param caption caption property
557     * @param iconProp property
558     * @param ou ou
559     * @param propStatus status property
560     * @param iconProvider the icon provider
561     * @return Indexed Container
562     */
563    public static IndexedContainer getGroupsOfUser(
564        CmsObject cms,
565        CmsUser user,
566        String caption,
567        String iconProp,
568        String ou,
569        String propStatus,
570        Function<CmsGroup, CmsCssIcon> iconProvider) {
571
572        IndexedContainer container = new IndexedContainer();
573        container.addContainerProperty(caption, String.class, "");
574        container.addContainerProperty(ou, String.class, "");
575        container.addContainerProperty(propStatus, Boolean.class, Boolean.valueOf(true));
576        if (iconProvider != null) {
577            container.addContainerProperty(iconProp, CmsCssIcon.class, null);
578        }
579        try {
580            for (CmsGroup group : cms.getGroupsOfUser(user.getName(), true)) {
581                Item item = container.addItem(group);
582                item.getItemProperty(caption).setValue(group.getSimpleName());
583                item.getItemProperty(ou).setValue(group.getOuFqn());
584                if (iconProvider != null) {
585                    item.getItemProperty(iconProp).setValue(iconProvider.apply(group));
586                }
587            }
588        } catch (CmsException e) {
589            LOG.error("Unable to read groups from user", e);
590        }
591        return container;
592    }
593
594    /**
595     * Creates a layout with info panel.<p>
596     *
597     * @param messageString Message to be displayed
598     * @return layout
599     */
600    public static VerticalLayout getInfoLayout(String messageString) {
601
602        VerticalLayout ret = new VerticalLayout();
603        ret.setMargin(true);
604        ret.addStyleName("o-center");
605        ret.setWidth("100%");
606        VerticalLayout inner = new VerticalLayout();
607        inner.addStyleName("o-workplace-maxwidth");
608        Panel panel = new Panel();
609        panel.setWidth("100%");
610
611        Label label = new Label(CmsVaadinUtils.getMessageText(messageString));
612        label.addStyleName("o-report");
613        panel.setContent(label);
614
615        inner.addComponent(panel);
616        ret.addComponent(inner);
617        return ret;
618    }
619
620    /**
621     * Get container with languages.<p>
622     *
623     * @param captionPropertyName name
624     * @return indexed container
625     */
626    public static IndexedContainer getLanguageContainer(String captionPropertyName) {
627
628        IndexedContainer result = new IndexedContainer();
629        result.addContainerProperty(captionPropertyName, String.class, "");
630
631        Iterator<Locale> itLocales = OpenCms.getLocaleManager().getAvailableLocales().iterator();
632        while (itLocales.hasNext()) {
633            Locale locale = itLocales.next();
634            Item item = result.addItem(locale);
635            item.getItemProperty(captionPropertyName).setValue(locale.getDisplayName(A_CmsUI.get().getLocale()));
636        }
637
638        return result;
639
640    }
641
642    /**
643     * Gets the message for the current locale and the given key and arguments.<p>
644     *
645     * @param messages the messages instance
646     * @param key the message key
647     * @param args the message arguments
648     *
649     * @return the message text for the current locale
650     */
651    public static String getMessageText(I_CmsMessageBundle messages, String key, Object... args) {
652
653        return messages.getBundle(A_CmsUI.get().getLocale()).key(key, args);
654    }
655
656    /**
657     * Gets the workplace message for the current locale and the given key and arguments.<p>
658     *
659     * @param key the message key
660     * @param args the message arguments
661     *
662     * @return the message text for the current locale
663     */
664    public static String getMessageText(String key, Object... args) {
665
666        return getWpMessagesForCurrentLocale().key(key, args);
667    }
668
669    /**
670     * Creates the ComboBox for OU selection.<p>
671     * @param cms CmsObject
672     * @param baseOu OU
673     * @param log Logger object
674     *
675     * @return ComboBox
676     */
677    public static ComboBox getOUComboBox(CmsObject cms, String baseOu, Log log) {
678
679        return getOUComboBox(cms, baseOu, log, true);
680    }
681
682    /**
683     * Creates the ComboBox for OU selection.<p>
684     * @param cms CmsObject
685     * @param baseOu OU
686     * @param log Logger object
687     * @param includeWebOU include webou?
688     *
689     * @return ComboBox
690     */
691    public static ComboBox getOUComboBox(CmsObject cms, String baseOu, Log log, boolean includeWebOU) {
692
693        ComboBox combo = null;
694        try {
695            IndexedContainer container = new IndexedContainer();
696            container.addContainerProperty("desc", String.class, "");
697            for (String ou : CmsOUHandler.getManagableOUs(cms)) {
698                if (includeWebOU | !OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, ou).hasFlagWebuser()) {
699                    Item item = container.addItem(ou);
700                    if (ou == "") {
701                        CmsOrganizationalUnit root = OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, "");
702                        item.getItemProperty("desc").setValue(root.getDisplayName(A_CmsUI.get().getLocale()));
703                    } else {
704                        item.getItemProperty("desc").setValue(
705                            OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, ou).getDisplayName(
706                                A_CmsUI.get().getLocale()));
707                    }
708                }
709            }
710            combo = new ComboBox(null, container);
711            combo.setTextInputAllowed(true);
712            combo.setNullSelectionAllowed(false);
713            combo.setWidth("379px");
714            combo.setInputPrompt(
715                Messages.get().getBundle(UI.getCurrent().getLocale()).key(Messages.GUI_EXPLORER_CLICK_TO_EDIT_0));
716            combo.setItemCaptionPropertyId("desc");
717
718            combo.setFilteringMode(FilteringMode.CONTAINS);
719
720            combo.select(baseOu);
721
722        } catch (CmsException e) {
723            if (log != null) {
724                log.error("Unable to read OU", e);
725            }
726        }
727        return combo;
728    }
729
730    /**
731     * Gives item id from path.<p>
732     *
733     * @param cnt to be used
734     * @param path to obtain item id from
735     * @return item id
736     */
737    public static String getPathItemId(Container cnt, String path) {
738
739        for (String id : Arrays.asList(path, CmsFileUtil.toggleTrailingSeparator(path))) {
740            if (cnt.containsId(id)) {
741                return id;
742            }
743        }
744        return null;
745    }
746
747    /**
748     * Get container for principal.
749     *
750     * @param cms cmsobject
751     * @param list of principals
752     * @param captionID caption id
753     * @param descID description id
754     * @param iconID icon id
755     * @param ouID ou id
756     * @param icon icon
757     * @param iconList iconlist
758     * @return indexedcontainer
759     */
760    public static IndexedContainer getPrincipalContainer(
761        CmsObject cms,
762        List<? extends I_CmsPrincipal> list,
763        String captionID,
764        String descID,
765        String iconID,
766        String ouID,
767        String icon,
768        List<FontIcon> iconList) {
769
770        IndexedContainer res = new IndexedContainer();
771
772        res.addContainerProperty(captionID, String.class, "");
773        res.addContainerProperty(ouID, String.class, "");
774        res.addContainerProperty(iconID, FontIcon.class, new CmsCssIcon(icon));
775        if (descID != null) {
776            res.addContainerProperty(descID, String.class, "");
777        }
778
779        for (I_CmsPrincipal group : list) {
780
781            Item item = res.addItem(group);
782            item.getItemProperty(captionID).setValue(group.getSimpleName());
783            item.getItemProperty(ouID).setValue(group.getOuFqn());
784            if (descID != null) {
785                item.getItemProperty(descID).setValue(group.getDescription(A_CmsUI.get().getLocale()));
786            }
787        }
788
789        for (int i = 0; i < iconList.size(); i++) {
790            res.getItem(res.getIdByIndex(i)).getItemProperty(iconID).setValue(iconList.get(i));
791        }
792
793        return res;
794    }
795
796    /**
797     * Returns the selectable projects container.<p>
798     *
799     * @param cms the CMS context
800     * @param captionPropertyName the name of the property used to store captions
801     *
802     * @return the projects container
803     */
804    public static IndexedContainer getProjectsContainer(CmsObject cms, String captionPropertyName) {
805
806        IndexedContainer result = new IndexedContainer();
807        result.addContainerProperty(captionPropertyName, String.class, null);
808        for (Map.Entry<CmsUUID, String> entry : getProjectsMap(cms).entrySet()) {
809            Item projectItem = result.addItem(entry.getKey());
810            projectItem.getItemProperty(captionPropertyName).setValue(entry.getValue());
811        }
812        return result;
813    }
814
815    /**
816     * Gets the available projects for the current user as a map, wth project ids as keys and project names as values.
817     *
818     * @param cms the current CMS context
819     * @return the map of projects
820     */
821    public static LinkedHashMap<CmsUUID, String> getProjectsMap(CmsObject cms) {
822
823        Locale locale = A_CmsUI.get().getLocale();
824        List<CmsProject> projects = getAvailableProjects(cms);
825        boolean isSingleOu = isSingleOu(projects);
826        LinkedHashMap<CmsUUID, String> result = new LinkedHashMap<>();
827        for (CmsProject project : projects) {
828            String projectName = project.getSimpleName();
829            if (!isSingleOu && !project.isOnlineProject()) {
830                try {
831                    projectName = projectName
832                        + " - "
833                        + OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, project.getOuFqn()).getDisplayName(
834                            locale);
835                } catch (CmsException e) {
836                    LOG.debug("Error reading project OU.", e);
837                    projectName = projectName + " - " + project.getOuFqn();
838                }
839            }
840            result.put(project.getUuid(), projectName);
841        }
842        return result;
843
844    }
845
846    /**
847     * Gets the current Vaadin request, cast to a HttpServletRequest.<p>
848     *
849     * @return the current request
850     */
851    public static HttpServletRequest getRequest() {
852
853        return (HttpServletRequest)VaadinService.getCurrentRequest();
854    }
855
856    /**
857     * Gets list of resource types.<p>
858     *
859     * @return List
860     */
861    public static List<I_CmsResourceType> getResourceTypes() {
862
863        List<I_CmsResourceType> res = new ArrayList<I_CmsResourceType>();
864        for (I_CmsResourceType type : OpenCms.getResourceManager().getResourceTypes()) {
865            CmsExplorerTypeSettings typeSetting = OpenCms.getWorkplaceManager().getExplorerTypeSetting(
866                type.getTypeName());
867            if (typeSetting != null) {
868                res.add(type);
869            }
870        }
871        return res;
872    }
873
874    /**
875     * Returns the available resource types container.<p>
876     *
877     * @return the resource types container
878     */
879    public static IndexedContainer getResourceTypesContainer() {
880
881        IndexedContainer container = new IndexedContainer();
882        container.addContainerProperty(PropertyId.caption, String.class, null);
883        container.addContainerProperty(PropertyId.icon, Resource.class, null);
884        container.addContainerProperty(PropertyId.isFolder, Boolean.class, null);
885        container.addContainerProperty(PropertyId.isXmlContent, Boolean.class, null);
886        List<I_CmsResourceType> types = getResourceTypes();
887        sortResourceTypes(types);
888        for (I_CmsResourceType type : types) {
889            CmsExplorerTypeSettings typeSetting = OpenCms.getWorkplaceManager().getExplorerTypeSetting(
890                type.getTypeName());
891            Item typeItem = container.addItem(type);
892            String caption = CmsVaadinUtils.getMessageText(typeSetting.getKey()) + " (" + type.getTypeName() + ")";
893            typeItem.getItemProperty(PropertyId.caption).setValue(caption);
894            typeItem.getItemProperty(PropertyId.icon).setValue(CmsResourceUtil.getSmallIconResource(typeSetting, null));
895            typeItem.getItemProperty(PropertyId.isXmlContent).setValue(
896                Boolean.valueOf(type instanceof CmsResourceTypeXmlContent));
897            typeItem.getItemProperty(PropertyId.isFolder).setValue(
898                Boolean.valueOf(type instanceof A_CmsResourceTypeFolderBase));
899        }
900
901        return container;
902    }
903
904    /**
905     * Returns the roles available for a given user.<p>
906     *
907     * @param cms CmsObject
908     * @param user to get available roles for
909     * @param captionPropertyName name of caption property
910     * @return indexed container
911     */
912    public static IndexedContainer getRoleContainerForUser(CmsObject cms, CmsUser user, String captionPropertyName) {
913
914        IndexedContainer result = new IndexedContainer();
915        result.addContainerProperty(captionPropertyName, String.class, "");
916        try {
917            List<CmsRole> roles = OpenCms.getRoleManager().getRoles(cms, user.getOuFqn(), false);
918            CmsRole.applySystemRoleOrder(roles);
919            for (CmsRole role : roles) {
920                Item item = result.addItem(role);
921                item.getItemProperty(captionPropertyName).setValue(role.getDisplayName(cms, A_CmsUI.get().getLocale()));
922            }
923        } catch (CmsException e) {
924            LOG.error("Unabel to read roles for user", e);
925        }
926        return result;
927    }
928
929    /**
930     * Gets the window which contains a given component.<p>
931     *
932     * @param component the component
933     * @return the window containing the component, or null if no component is found
934     */
935    public static Window getWindow(Component component) {
936
937        if (component == null) {
938            return null;
939        } else if (component instanceof Window) {
940            return (Window)component;
941        } else {
942            return getWindow(component.getParent());
943        }
944
945    }
946
947    /**
948     * Get container with workpalce languages.<p>
949     *
950     * @param captionPropertyName name
951     * @return indexed container
952     */
953    public static IndexedContainer getWorkplaceLanguageContainer(String captionPropertyName) {
954
955        IndexedContainer result = new IndexedContainer();
956        result.addContainerProperty(captionPropertyName, String.class, "");
957        CmsLanguagePreference.getOptionMapForLanguage().forEach((locale, title) -> {
958            Item item = result.addItem(locale);
959            item.getItemProperty(captionPropertyName).setValue(title);
960
961        });
962
963        return result;
964    }
965
966    /**
967     * Gets the link to the (new) workplace.<p>
968     *
969     * @return the link to the workplace
970     */
971    public static String getWorkplaceLink() {
972
973        return OpenCms.getSystemInfo().getWorkplaceContext();
974    }
975
976    /**
977     * Returns the workplace link for the given app.<p>
978     *
979     * @param appId the app id
980     *
981     * @return the workplace link
982     */
983    public static String getWorkplaceLink(String appId) {
984
985        return getWorkplaceLink() + CmsAppWorkplaceUi.WORKPLACE_APP_ID_SEPARATOR + appId;
986    }
987
988    /**
989     * Returns the workplace link to the given app with the given state.<p>
990     *
991     * @param appId the app id
992     * @param appState the app state
993     *
994     * @return the workplace link
995     */
996    public static String getWorkplaceLink(String appId, String appState) {
997
998        return getWorkplaceLink(appId) + CmsAppWorkplaceUi.WORKPLACE_STATE_SEPARATOR + appState;
999    }
1000
1001    /**
1002     * Returns the workplace link to the given app with the given state including the given request parameters.<p>
1003     *
1004     * @param appId the app id
1005     * @param appState the app state
1006     * @param requestParameters the request parameters
1007     *
1008     * @return the workplace link
1009     */
1010    public static String getWorkplaceLink(String appId, String appState, Map<String, String[]> requestParameters) {
1011
1012        String result = getWorkplaceLink();
1013        if ((requestParameters != null) && !requestParameters.isEmpty()) {
1014            boolean first = true;
1015            for (Entry<String, String[]> param : requestParameters.entrySet()) {
1016                for (String value : param.getValue()) {
1017                    if (first) {
1018                        result += "?";
1019                    } else {
1020                        result += "&";
1021                    }
1022                    result += param.getKey() + "=" + value;
1023                    first = false;
1024                }
1025            }
1026        }
1027
1028        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(appId)) {
1029            result += CmsAppWorkplaceUi.WORKPLACE_APP_ID_SEPARATOR + appId;
1030        }
1031        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(appState)) {
1032            result += CmsAppWorkplaceUi.WORKPLACE_STATE_SEPARATOR + appState;
1033        }
1034        return result;
1035    }
1036
1037    /**
1038     * Gets external resource from workplace resource folder.<p>
1039     *
1040     * @param subPath path relative to workplace resource folder
1041     *
1042     * @return the external resource
1043     */
1044    public static ExternalResource getWorkplaceResource(String subPath) {
1045
1046        return new ExternalResource(CmsWorkplace.getResourceUri(subPath));
1047
1048    }
1049
1050    /**
1051     * Gets the workplace messages for the current locale.<p>
1052     *
1053     * @return the workplace messages
1054     */
1055    public static CmsMessages getWpMessagesForCurrentLocale() {
1056
1057        Locale locale;
1058        if (A_CmsUI.get() != null) {
1059            locale = A_CmsUI.get().getLocale();
1060        } else {
1061            if (LOG.isWarnEnabled()) {
1062                Exception e = new Exception("getWpMessagesForCurrentLocale called from non-Vaadin context");
1063                LOG.warn(e.getLocalizedMessage(), e);
1064            }
1065            locale = Locale.ENGLISH;
1066        }
1067        return OpenCms.getWorkplaceManager().getMessages(locale);
1068    }
1069
1070    /**
1071     * Checks if path is itemid in container.<p>
1072     *
1073     * @param cnt to be checked
1074     * @param path as itemid
1075     * @return true id path is itemid in container
1076     */
1077    public static boolean hasPathAsItemId(Container cnt, String path) {
1078
1079        return cnt.containsId(path) || cnt.containsId(CmsFileUtil.toggleTrailingSeparator(path));
1080    }
1081
1082    /**
1083     * Checks if a button is pressed.<p>
1084     *
1085     * @param button the button
1086     *
1087     * @return true if the button is pressed
1088     */
1089    public static boolean isButtonPressed(Button button) {
1090
1091        if (button == null) {
1092            return false;
1093        }
1094        List<String> styles = Arrays.asList(button.getStyleName().split(" "));
1095
1096        return styles.contains(OpenCmsTheme.BUTTON_PRESSED);
1097    }
1098
1099    /**
1100     * Uses the currently set locale to resolve localization macros in the input string using workplace message bundles.<p>
1101     *
1102     * @param baseString the string to localize
1103     *
1104     * @return the localized string
1105     */
1106    public static String localizeString(String baseString) {
1107
1108        if (baseString == null) {
1109            return null;
1110        }
1111        CmsWorkplaceMessages wpMessages = OpenCms.getWorkplaceManager().getMessages(A_CmsUI.get().getLocale());
1112        CmsMacroResolver resolver = new CmsMacroResolver();
1113        resolver.setMessages(wpMessages);
1114        String result = resolver.resolveMacros(baseString);
1115        return result;
1116    }
1117
1118    /**
1119     * Message accessior function.<p>
1120     *
1121     * @return the message for Cancel buttons
1122     */
1123    public static String messageCancel() {
1124
1125        return getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0);
1126    }
1127
1128    /**
1129     * Message accessior function.<p>
1130     *
1131     * @return the message for Cancel buttons
1132     */
1133    public static String messageClose() {
1134
1135        return getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CLOSE_0);
1136    }
1137
1138    /**
1139     * Message accessor function.<p>
1140     *
1141     * @return the message for OK buttons
1142     */
1143    public static String messageOk() {
1144
1145        return getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0);
1146    }
1147
1148    /**
1149     * Generates the options items for the combo box using the map entry keys as values and the values as labels.<p>
1150     *
1151     * @param box the combo box to prepare
1152     * @param options the box options
1153     */
1154    public static void prepareComboBox(ComboBox box, Map<?, String> options) {
1155
1156        IndexedContainer container = new IndexedContainer();
1157        container.addContainerProperty(PROPERTY_VALUE, Object.class, null);
1158        container.addContainerProperty(PROPERTY_LABEL, String.class, "");
1159        for (Entry<?, String> entry : options.entrySet()) {
1160            Item item = container.addItem(entry.getKey());
1161            item.getItemProperty(PROPERTY_VALUE).setValue(entry.getKey());
1162            item.getItemProperty(PROPERTY_LABEL).setValue(entry.getValue());
1163        }
1164        box.setContainerDataSource(container);
1165        box.setItemCaptionPropertyId(PROPERTY_LABEL);
1166    }
1167
1168    /**
1169     * Reads the declarative design for a component and localizes it using a messages object.<p>
1170     *
1171     * The design will need to be located in the same directory as the component's class and have '.html' as a file extension.
1172     *
1173     * @param component the component for which to read the design
1174     * @param messages the message bundle to use for localization
1175     * @param macros the macros to use on the HTML template
1176     */
1177    @SuppressWarnings("resource")
1178    public static void readAndLocalizeDesign(Component component, CmsMessages messages, Map<String, String> macros) {
1179
1180        Class<?> componentClass = component.getClass();
1181        List<Class<?>> classes = Lists.newArrayList();
1182        classes.add(componentClass);
1183        classes.addAll(ClassUtils.getAllSuperclasses(componentClass));
1184        InputStream designStream = null;
1185        for (Class<?> cls : classes) {
1186            if (cls.getName().startsWith("com.vaadin")) {
1187                break;
1188            }
1189            String filename = cls.getSimpleName() + ".html";
1190            designStream = cls.getResourceAsStream(filename);
1191            if (designStream != null) {
1192                break;
1193            }
1194
1195        }
1196        if (designStream == null) {
1197            throw new IllegalArgumentException("Design not found for : " + component.getClass());
1198        }
1199        readAndLocalizeDesign(component, designStream, messages, macros);
1200    }
1201
1202    /**
1203     * Reads a layout from a resource, applies basic i18n macro substitution on the contained text, and returns a stream of the transformed
1204     * data.<p>
1205     *
1206     * @param layoutClass the class relative to which the layout resource will be looked up
1207     * @param relativeName the file name of the layout file
1208     *
1209     * @return an input stream which produces the transformed layout resource html
1210     */
1211    public static InputStream readCustomLayout(Class<? extends Component> layoutClass, String relativeName) {
1212
1213        CmsMacroResolver resolver = new CmsMacroResolver() {
1214
1215            @Override
1216            public String getMacroValue(String macro) {
1217
1218                return CmsEncoder.escapeXml(super.getMacroValue(macro));
1219            }
1220        };
1221        resolver.setMessages(CmsVaadinUtils.getWpMessagesForCurrentLocale());
1222        InputStream layoutStream = CmsVaadinUtils.filterUtf8ResourceStream(
1223            layoutClass.getResourceAsStream(relativeName),
1224            resolver.toFunction());
1225        return layoutStream;
1226    }
1227
1228    /**
1229     * Replaces component with new component.<p>
1230     *
1231     * @param component to be replaced
1232     * @param replacement new component
1233     */
1234    public static void replaceComponent(Component component, Component replacement) {
1235
1236        if (!component.isAttached()) {
1237            throw new IllegalArgumentException("Component must be attached");
1238        }
1239        HasComponents parent = component.getParent();
1240        if (parent instanceof ComponentContainer) {
1241            ((ComponentContainer)parent).replaceComponent(component, replacement);
1242        } else if (parent instanceof SingleComponentContainer) {
1243            ((SingleComponentContainer)parent).setContent(replacement);
1244        } else {
1245            throw new IllegalArgumentException("Illegal class for parent: " + parent.getClass());
1246        }
1247    }
1248
1249    /**
1250     * Configures a text field to look like a filter box for a table.
1251     *
1252     * @param searchBox the text field to configure
1253     */
1254    public static void setFilterBoxStyle(TextField searchBox) {
1255
1256        searchBox.setIcon(FontOpenCms.FILTER);
1257
1258        searchBox.setPlaceholder(
1259            org.opencms.ui.apps.Messages.get().getBundle(UI.getCurrent().getLocale()).key(
1260                org.opencms.ui.apps.Messages.GUI_EXPLORER_FILTER_0));
1261        searchBox.addStyleName(ValoTheme.TEXTFIELD_INLINE_ICON);
1262    }
1263
1264    /**
1265     * Sets the value of a text field which may be set to read-only mode.<p>
1266     *
1267     * When setting a Vaadin field to read-only, you also can't set its value programmatically anymore.
1268     * So we need to temporarily disable read-only mode, set the value, and then switch back to read-only mode.
1269     *
1270     * @param field the field
1271     * @param value the value to set
1272     */
1273    public static <T> void setReadonlyValue(AbstractField<T> field, T value) {
1274
1275        boolean readonly = field.isReadOnly();
1276        try {
1277            field.setReadOnly(false);
1278            field.setValue(value);
1279        } finally {
1280            field.setReadOnly(readonly);
1281        }
1282    }
1283
1284    /**
1285     * Shows an alert box to the user with the given information, which will perform the given action after the user clicks on OK.<p>
1286     *
1287     * @param title the title
1288     * @param message the message
1289     *
1290     * @param callback the callback to execute after clicking OK
1291     */
1292    public static void showAlert(String title, String message, final Runnable callback) {
1293
1294        final Window window = new Window();
1295        window.setModal(true);
1296        Panel panel = new Panel();
1297        panel.setCaption(title);
1298        panel.setWidth("500px");
1299        VerticalLayout layout = new VerticalLayout();
1300        layout.setMargin(true);
1301        panel.setContent(layout);
1302        layout.addComponent(new Label(message));
1303        Button okButton = new Button();
1304        okButton.addClickListener(new ClickListener() {
1305
1306            /** The serial version id. */
1307            private static final long serialVersionUID = 1L;
1308
1309            public void buttonClick(ClickEvent event) {
1310
1311                window.close();
1312                if (callback != null) {
1313                    callback.run();
1314                }
1315            }
1316        });
1317        layout.addComponent(okButton);
1318        layout.setComponentAlignment(okButton, Alignment.BOTTOM_RIGHT);
1319        okButton.setCaption(
1320            org.opencms.workplace.Messages.get().getBundle(A_CmsUI.get().getLocale()).key(
1321                org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0));
1322        window.setContent(panel);
1323        window.setClosable(false);
1324        window.setResizable(false);
1325        A_CmsUI.get().addWindow(window);
1326
1327    }
1328
1329    /**
1330     * Sorts a list of resource types by their localized explorer type name.
1331     * @param resourceTypes the resource types
1332     */
1333    public static void sortResourceTypes(List<I_CmsResourceType> resourceTypes) {
1334
1335        Collections.sort(resourceTypes, (type, other) -> {
1336            CmsExplorerTypeSettings typeSetting = OpenCms.getWorkplaceManager().getExplorerTypeSetting(
1337                type.getTypeName());
1338            CmsExplorerTypeSettings otherSetting = OpenCms.getWorkplaceManager().getExplorerTypeSetting(
1339                other.getTypeName());
1340            if ((typeSetting != null) && (otherSetting != null)) {
1341                String typeName = CmsVaadinUtils.getMessageText(typeSetting.getKey());
1342                String otherName = CmsVaadinUtils.getMessageText(otherSetting.getKey());
1343                return typeName.compareTo(otherName);
1344            } else {
1345                return -1;
1346            }
1347        });
1348    }
1349
1350    /**
1351     * Creates a new option group builder.<p>
1352     *
1353     * @return a new option group builder
1354     */
1355    public static OptionGroupBuilder startOptionGroup() {
1356
1357        return new OptionGroupBuilder();
1358    }
1359
1360    /**
1361     * Sets style of a toggle button depending on its current state.<p>
1362     *
1363     * @param button the button to update
1364     */
1365    public static void toggleButton(Button button) {
1366
1367        if (isButtonPressed(button)) {
1368            button.removeStyleName(OpenCmsTheme.BUTTON_PRESSED);
1369        } else {
1370            button.addStyleName(OpenCmsTheme.BUTTON_PRESSED);
1371        }
1372    }
1373
1374    /**
1375     * Updates the component error of a component, but only if it differs from the currently set
1376     * error.<p>
1377     *
1378     * @param component the component
1379     * @param error the error
1380     *
1381     * @return true if the error was changed
1382     */
1383    public static boolean updateComponentError(AbstractComponent component, ErrorMessage error) {
1384
1385        if (component.getComponentError() != error) {
1386            component.setComponentError(error);
1387            return true;
1388        }
1389        return false;
1390    }
1391
1392    /**
1393     * Visits all descendants of a given component (including the component itself) and applies a predicate
1394     * to each.<p>
1395     *
1396     * If the predicate returns false for a component, no further descendants will be processed.<p>
1397     *
1398     * @param component the component
1399     * @param handler the predicate
1400     */
1401    public static void visitDescendants(Component component, Predicate<Component> handler) {
1402
1403        List<Component> stack = Lists.newArrayList();
1404        stack.add(component);
1405        while (!stack.isEmpty()) {
1406            Component currentComponent = stack.get(stack.size() - 1);
1407            stack.remove(stack.size() - 1);
1408            if (!handler.apply(currentComponent)) {
1409                return;
1410            }
1411            if (currentComponent instanceof HasComponents) {
1412                List<Component> children = Lists.newArrayList((HasComponents)currentComponent);
1413                Collections.reverse(children);
1414                stack.addAll(children);
1415            }
1416        }
1417    }
1418
1419    /**
1420     * Waggle the component.<p>
1421     *
1422     * @param component to be waggled
1423     */
1424    public static void waggleMeOnce(Component component) {
1425
1426        //TODO Until now, the component gets a waggler class which can not be removed again here..
1427        component.addStyleName("waggler");
1428        //Add JavaScript code, which adds the waggle class and removes it after a short time.
1429        JavaScript.getCurrent().execute(
1430            "waggler=document.querySelectorAll(\".waggler\")[0];"
1431                + "waggler.className=waggler.className + \" waggle\";"
1432                + "setTimeout(function () {\n"
1433                + "waggler.className=waggler.className.replace(/\\bwaggle\\b/g, \"\");"
1434                + "    }, 1500);");
1435    }
1436
1437    /**
1438     * Reads the given design and resolves the given macros and localizations.<p>
1439    
1440     * @param component the component whose design to read
1441     * @param designStream stream to read the design from
1442     * @param messages the message bundle to use for localization in the design (may be null)
1443     * @param macros other macros to substitute in the macro design (may be null)
1444     */
1445    protected static void readAndLocalizeDesign(
1446        Component component,
1447        InputStream designStream,
1448        CmsMessages messages,
1449        Map<String, String> macros) {
1450
1451        try {
1452            byte[] designBytes = CmsFileUtil.readFully(designStream, true);
1453            final String encoding = "UTF-8";
1454            String design = new String(designBytes, encoding);
1455            CmsMacroResolver resolver = new CmsMacroResolver() {
1456
1457                @Override
1458                public String getMacroValue(String macro) {
1459
1460                    String result = super.getMacroValue(macro);
1461                    // The macro may contain quotes or angle brackets, so we need to escape the values for insertion into the design file
1462                    return CmsEncoder.escapeXml(result);
1463
1464                }
1465            };
1466
1467            if (macros != null) {
1468                for (Map.Entry<String, String> entry : macros.entrySet()) {
1469                    resolver.addMacro(entry.getKey(), entry.getValue());
1470                }
1471            }
1472            if (messages != null) {
1473                resolver.setMessages(messages);
1474            }
1475            String resolvedDesign = resolver.resolveMacros(design);
1476            Design.read(new ByteArrayInputStream(resolvedDesign.getBytes(encoding)), component);
1477        } catch (IOException e) {
1478            throw new RuntimeException("Could not read design", e);
1479        } finally {
1480            try {
1481                designStream.close();
1482            } catch (IOException e) {
1483                LOG.warn(e.getLocalizedMessage(), e);
1484            }
1485        }
1486    }
1487
1488    /**
1489     * Returns whether only a single OU is visible to the current user.<p>
1490     *
1491     * @param projects the selectable projects
1492     *
1493     * @return <code>true</code> if only a single OU is visible to the current user
1494     */
1495    private static boolean isSingleOu(List<CmsProject> projects) {
1496
1497        String ouFqn = null;
1498        for (CmsProject project : projects) {
1499            if (project.isOnlineProject()) {
1500                // skip the online project
1501                continue;
1502            }
1503            if (ouFqn == null) {
1504                // set the first ou
1505                ouFqn = project.getOuFqn();
1506            } else if (!ouFqn.equals(project.getOuFqn())) {
1507                // break if one different ou is found
1508                return false;
1509            }
1510        }
1511        return true;
1512    }
1513
1514}