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