All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencms.ui.CmsVaadinUtils Maven / Gradle / Ivy

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.ui;

import org.opencms.ade.galleries.CmsSiteSelectorOptionBuilder;
import org.opencms.ade.galleries.shared.CmsSiteSelectorOption;
import org.opencms.db.CmsUserSettings;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.types.A_CmsResourceTypeFolderBase;
import org.opencms.file.types.CmsResourceTypeXmlContent;
import org.opencms.file.types.I_CmsResourceType;
import org.opencms.i18n.CmsEncoder;
import org.opencms.i18n.CmsMessages;
import org.opencms.main.CmsException;
import org.opencms.main.OpenCms;
import org.opencms.util.CmsFileUtil;
import org.opencms.util.CmsMacroResolver;
import org.opencms.util.CmsStringUtil;
import org.opencms.workplace.CmsWorkplace;
import org.opencms.workplace.CmsWorkplaceMessages;
import org.opencms.workplace.explorer.CmsExplorerTypeSettings;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.ClassUtils;
import org.apache.log4j.Logger;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.vaadin.data.Container.Filter;
import com.vaadin.data.Item;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.server.ExternalResource;
import com.vaadin.server.Resource;
import com.vaadin.server.VaadinService;
import com.vaadin.shared.Version;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.HasComponents;
import com.vaadin.ui.Label;
import com.vaadin.ui.OptionGroup;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.declarative.Design;
import com.vaadin.ui.themes.ValoTheme;

/**
 * Vaadin utility functions.

* */ public final class CmsVaadinUtils { /** * Helper class for building option groups.

*/ public static class OptionGroupBuilder { /** The option group being built. */ private OptionGroup m_optionGroup = new OptionGroup(); /** * Adds an option.

* * @param key the option key * @param text the option text * * @return this instance */ public OptionGroupBuilder add(String key, String text) { m_optionGroup.addItem(key); m_optionGroup.setItemCaption(key, text); return this; } /** * Returns the option group.

* * @return the option group */ public OptionGroup build() { return m_optionGroup; } /** * Adds horizontal style to option group.

* * @return this instance */ public OptionGroupBuilder horizontal() { m_optionGroup.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL); return this; } } /** Container property ids. */ public static enum PropertyId { /** The caption id. */ caption, /** The icon id. */ icon, /** The is folder id. */ isFolder, /** The is XML content id. */ isXmlContent } /** Container filter for the resource type container to show not folder types only. */ public static final Filter FILTER_NO_FOLDERS = new Filter() { private static final long serialVersionUID = 1L; public boolean appliesToProperty(Object propertyId) { return PropertyId.isFolder.equals(propertyId); } public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException { return !((Boolean)item.getItemProperty(PropertyId.isFolder).getValue()).booleanValue(); } }; /** Container filter for the resource type container to show XML content types only. */ public static final Filter FILTER_XML_CONTENTS = new Filter() { private static final long serialVersionUID = 1L; public boolean appliesToProperty(Object propertyId) { return PropertyId.isXmlContent.equals(propertyId); } public boolean passesFilter(Object itemId, Item item) throws UnsupportedOperationException { return ((Boolean)item.getItemProperty(PropertyId.isXmlContent).getValue()).booleanValue(); } }; /** The combo box label item property id. */ public static final String PROPERTY_LABEL = "label"; /** The combo box value item property id. */ public static final String PROPERTY_VALUE = "value"; /** The Vaadin bootstrap script, with some macros to be dynamically replaced later. */ protected static final String BOOTSTRAP_SCRIPT = "vaadin.initApplication(\"%(elementId)\", {\n" + " \"browserDetailsUrl\": \"%(vaadinServlet)\",\n" + " \"serviceUrl\": \"%(vaadinServlet)\",\n" + " \"widgetset\": \"org.opencms.ui.WidgetSet\",\n" + " \"theme\": \"opencms\",\n" + " \"versionInfo\": {\"vaadinVersion\": \"%(vaadinVersion)\"},\n" + " \"vaadinDir\": \"%(vaadinDir)\",\n" + " \"heartbeatInterval\": 30,\n" + " \"debug\": false,\n" + " \"standalone\": false,\n" + " \"authErrMsg\": {\n" + " \"message\": \"Take note of any unsaved data, \"+\n" + " \"and click here<\\/u> to continue.\",\n" + " \"caption\": \"Authentication problem\"\n" + " },\n" + " \"comErrMsg\": {\n" + " \"message\": \"Take note of any unsaved data, \"+\n" + " \"and click here<\\/u> to continue.\",\n" + " \"caption\": \"Communication problem\"\n" + " },\n" + " \"sessExpMsg\": {\n" + " \"message\": \"Take note of any unsaved data, \"+\n" + " \"and click here<\\/u> to continue.\",\n" + " \"caption\": \"Session Expired\"\n" + " }\n" + " });"; /** The logger of this class. */ private static final Logger LOG = Logger.getLogger(CmsVaadinUtils.class); /** * Hidden default constructor for utility class.

*/ private CmsVaadinUtils() { } /** * 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.

* * @param captionProperty the property name to use for captions * @param map the map * @return the new container */ public static IndexedContainer buildContainerFromMap(String captionProperty, Map map) { IndexedContainer container = new IndexedContainer(); for (Map.Entry entry : map.entrySet()) { container.addItem(entry.getKey()).getItemProperty(captionProperty).setValue(entry.getValue()); } return container; } /** * Creates a click listener which calls a Runnable when activated.

* * @param action the Runnable to execute on a click * * @return the click listener */ public static Button.ClickListener createClickListener(final Runnable action) { return new Button.ClickListener() { /** Serial version id. */ private static final long serialVersionUID = 1L; public void buttonClick(ClickEvent event) { action.run(); } }; } /** * Reads the content of an input stream into a string (using UTF-8 encoding), performs a function on the string, and returns the result * again as an input stream.

* * @param stream the stream producing the input data * @param transformation the function to apply to the input * * @return the stream producing the transformed input data */ public static InputStream filterUtf8ResourceStream(InputStream stream, Function transformation) { try { byte[] streamData = CmsFileUtil.readFully(stream); String dataAsString = new String(streamData, "UTF-8"); byte[] transformedData = transformation.apply(dataAsString).getBytes("UTF-8"); return new ByteArrayInputStream(transformedData); } catch (UnsupportedEncodingException e) { LOG.error(e.getLocalizedMessage(), e); return null; } catch (IOException e) { LOG.error(e.getLocalizedMessage(), e); throw new RuntimeException(e); } } /** * Returns the available projects.

* * @param cms the CMS context * * @return the available projects */ public static List getAvailableProjects(CmsObject cms) { // get all project information List allProjects; try { String ouFqn = ""; CmsUserSettings settings = new CmsUserSettings(cms); if (!settings.getListAllProjects()) { ouFqn = cms.getRequestContext().getCurrentUser().getOuFqn(); } allProjects = new ArrayList( OpenCms.getOrgUnitManager().getAllAccessibleProjects(cms, ouFqn, settings.getListAllProjects())); Iterator itProjects = allProjects.iterator(); while (itProjects.hasNext()) { CmsProject prj = itProjects.next(); if (prj.isHiddenFromSelector()) { itProjects.remove(); } } } catch (CmsException e) { // should usually never happen LOG.error(e.getLocalizedMessage(), e); allProjects = Collections.emptyList(); } return allProjects; } /** * Builds an IndexedContainer containing the sites selectable by the current user.

* * @param cms the CMS context * @param captionPropertyName the name of the property used to store captions * * @return the container with the available sites */ public static IndexedContainer getAvailableSitesContainer(CmsObject cms, String captionPropertyName) { CmsSiteSelectorOptionBuilder optBuilder = new CmsSiteSelectorOptionBuilder(cms); optBuilder.addNormalSites(true, (new CmsUserSettings(cms)).getStartFolder()); optBuilder.addSharedSite(); IndexedContainer availableSites = new IndexedContainer(); availableSites.addContainerProperty(captionPropertyName, String.class, null); for (CmsSiteSelectorOption option : optBuilder.getOptions()) { Item siteItem = availableSites.addItem(option.getSiteRoot()); siteItem.getItemProperty(captionPropertyName).setValue(option.getMessage()); } String currentSiteRoot = cms.getRequestContext().getSiteRoot(); if (!availableSites.containsId(currentSiteRoot)) { availableSites.addItem(currentSiteRoot).getItemProperty(captionPropertyName).setValue(currentSiteRoot); } return availableSites; } /** * Returns the Javascript code to use for initializing a Vaadin UI.

* * @param cms the CMS context * @param elementId the id of the DOM element in which to initialize the UI * @param servicePath the UI servlet path * @return the Javascript code to initialize Vaadin * * @throws Exception if something goes wrong */ public static String getBootstrapScript(CmsObject cms, String elementId, String servicePath) throws Exception { String script = BOOTSTRAP_SCRIPT; CmsMacroResolver resolver = new CmsMacroResolver(); String context = OpenCms.getSystemInfo().getContextPath(); String vaadinDir = CmsStringUtil.joinPaths(context, "VAADIN/"); String vaadinVersion = Version.getFullVersion(); String vaadinServlet = CmsStringUtil.joinPaths(context, servicePath); String vaadinBootstrap = CmsStringUtil.joinPaths(context, "VAADIN/vaadinBootstrap.js"); resolver.addMacro("vaadinDir", vaadinDir); resolver.addMacro("vaadinVersion", vaadinVersion); resolver.addMacro("elementId", elementId); resolver.addMacro("vaadinServlet", vaadinServlet); resolver.addMacro("vaadinBootstrap", vaadinBootstrap); script = resolver.resolveMacros(script); return script; } /** * Returns the path to the design template file of the given component.

* * @param component the component * * @return the path */ public static String getDefaultDesignPath(Component component) { String className = component.getClass().getName(); String designPath = className.replace(".", "/") + ".html"; return designPath; } /** * Gets the workplace message for the current locale and the given key and arguments.

* * @param key the message key * @param args the message arguments * * @return the message text for the current locale */ public static String getMessageText(String key, Object... args) { return getWpMessagesForCurrentLocale().key(key, args); } /** * Returns the selectable projects container.

* * @param cms the CMS context * @param captionPropertyName the name of the property used to store captions * * @return the projects container */ public static IndexedContainer getProjectsContainer(CmsObject cms, String captionPropertyName) { IndexedContainer result = new IndexedContainer(); result.addContainerProperty(captionPropertyName, String.class, null); Locale locale = A_CmsUI.get().getLocale(); List projects = getAvailableProjects(cms); boolean isSingleOu = isSingleOu(projects); for (CmsProject project : projects) { String projectName = project.getSimpleName(); if (!isSingleOu && !project.isOnlineProject()) { try { projectName = projectName + " - " + OpenCms.getOrgUnitManager().readOrganizationalUnit(cms, project.getOuFqn()).getDisplayName( locale); } catch (CmsException e) { LOG.debug("Error reading project OU.", e); projectName = projectName + " - " + project.getOuFqn(); } } Item projectItem = result.addItem(project.getUuid()); projectItem.getItemProperty(captionPropertyName).setValue(projectName); } return result; } /** * Gets the current Vaadin request, cast to a HttpServletRequest.

* * @return the current request */ public static HttpServletRequest getRequest() { return (HttpServletRequest)VaadinService.getCurrentRequest(); } /** * Returns the available resource types container.

* * @return the resource types container */ public static IndexedContainer getResourceTypesContainer() { IndexedContainer types = new IndexedContainer(); types.addContainerProperty(PropertyId.caption, String.class, null); types.addContainerProperty(PropertyId.icon, Resource.class, null); types.addContainerProperty(PropertyId.isFolder, Boolean.class, null); types.addContainerProperty(PropertyId.isXmlContent, Boolean.class, null); for (I_CmsResourceType type : OpenCms.getResourceManager().getResourceTypes()) { CmsExplorerTypeSettings typeSetting = OpenCms.getWorkplaceManager().getExplorerTypeSetting( type.getTypeName()); if (typeSetting != null) { Item typeItem = types.addItem(type); typeItem.getItemProperty(PropertyId.caption).setValue( CmsVaadinUtils.getMessageText(typeSetting.getKey())); typeItem.getItemProperty(PropertyId.icon).setValue( new ExternalResource( CmsWorkplace.getResourceUri(CmsWorkplace.RES_PATH_FILETYPES + typeSetting.getIcon()))); typeItem.getItemProperty(PropertyId.isXmlContent).setValue( Boolean.valueOf(type instanceof CmsResourceTypeXmlContent)); typeItem.getItemProperty(PropertyId.isFolder).setValue( Boolean.valueOf(type instanceof A_CmsResourceTypeFolderBase)); } } return types; } /** * Gets the window which contains a given component.

* * @param component the component * @return the window containing the component, or null if no component is found */ public static Window getWindow(Component component) { if (component == null) { return null; } else if (component instanceof Window) { return (Window)component; } else { return getWindow(component.getParent()); } } /** * Gets the link to the (new) workplace.

* * @return the link to the workplace */ public static String getWorkplaceLink() { return CmsStringUtil.joinPaths("/", OpenCms.getSystemInfo().getContextPath(), "workplace"); } /** * Gets external resource from workplace resource folder.

* * @param subPath path relative to workplace resource folder * * @return the external resource */ public static ExternalResource getWorkplaceResource(String subPath) { return new ExternalResource(CmsWorkplace.getResourceUri(subPath)); } /** * Gets the workplace messages for the current locale.

* * @return the workplace messages */ public static CmsMessages getWpMessagesForCurrentLocale() { return OpenCms.getWorkplaceManager().getMessages(A_CmsUI.get().getLocale()); } /** * Uses the currently set locale to resolve localization macros in the input string using workplace message bundles.

* * @param baseString the string to localize * * @return the localized string */ public static String localizeString(String baseString) { if (baseString == null) { return null; } CmsWorkplaceMessages wpMessages = OpenCms.getWorkplaceManager().getMessages(A_CmsUI.get().getLocale()); CmsMacroResolver resolver = new CmsMacroResolver(); resolver.setMessages(wpMessages); String result = resolver.resolveMacros(baseString); return result; } /** * Message accessior function.

* * @return the message for Cancel buttons */ public static String messageCancel() { return getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0); } /** * Message accessor function.

* * @return the message for OK buttons */ public static String messageOk() { return getMessageText(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0); } /** * Generates the options items for the combo box using the map entry keys as values and the values as labels.

* * @param box the combo box to prepare * @param options the box options */ public static void prepareComboBox(ComboBox box, Map options) { IndexedContainer container = new IndexedContainer(); container.addContainerProperty(PROPERTY_VALUE, Object.class, null); container.addContainerProperty(PROPERTY_LABEL, String.class, ""); for (Entry entry : options.entrySet()) { Item item = container.addItem(entry.getKey()); item.getItemProperty(PROPERTY_VALUE).setValue(entry.getKey()); item.getItemProperty(PROPERTY_LABEL).setValue(entry.getValue()); } box.setContainerDataSource(container); box.setItemCaptionPropertyId(PROPERTY_LABEL); } /** * Reads the declarative design for a component and localizes it using a messages object.

* * The design will need to be located in the same directory as the component's class and have '.html' as a file extension. * * @param component the component for which to read the design * @param messages the message bundle to use for localization * @param macros the macros to use on the HTML template */ @SuppressWarnings("resource") public static void readAndLocalizeDesign(Component component, CmsMessages messages, Map macros) { Class componentClass = component.getClass(); List> classes = Lists.newArrayList(); classes.add(componentClass); classes.addAll(ClassUtils.getAllSuperclasses(componentClass)); InputStream designStream = null; for (Class cls : classes) { if (cls.getName().startsWith("com.vaadin")) { break; } String filename = cls.getSimpleName() + ".html"; designStream = cls.getResourceAsStream(filename); if (designStream != null) { break; } } if (designStream == null) { throw new IllegalArgumentException("Design not found for : " + component.getClass()); } readAndLocalizeDesign(component, designStream, messages, macros); } /** * Reads a layout from a resource, applies basic i18n macro substitution on the contained text, and returns a stream of the transformed * data.

* * @param layoutClass the class relative to which the layout resource will be looked up * @param relativeName the file name of the layout file * * @return an input stream which produces the transformed layout resource html */ public static InputStream readCustomLayout(Class layoutClass, String relativeName) { CmsMacroResolver resolver = new CmsMacroResolver() { @Override public String getMacroValue(String macro) { return CmsEncoder.escapeXml(super.getMacroValue(macro)); } }; resolver.setMessages(CmsVaadinUtils.getWpMessagesForCurrentLocale()); InputStream layoutStream = CmsVaadinUtils.filterUtf8ResourceStream( layoutClass.getResourceAsStream(relativeName), resolver.toFunction()); return layoutStream; } /** * Sets the value of a text field which may be set to read-only mode.

* * When setting a Vaadin field to read-only, you also can't set its value programmatically anymore. * So we need to temporarily disable read-only mode, set the value, and then switch back to read-only mode. * * @param field the field * @param value the value to set */ public static void setReadonlyValue(AbstractField field, T value) { boolean readonly = field.isReadOnly(); try { field.setReadOnly(false); field.setValue(value); } finally { field.setReadOnly(readonly); } } /** * Shows an alert box to the user with the given information, which will perform the given action after the user clicks on OK.

* * @param title the title * @param message the message * * @param callback the callback to execute after clicking OK */ public static void showAlert(String title, String message, final Runnable callback) { final Window window = new Window(); window.setModal(true); Panel panel = new Panel(); panel.setCaption(title); panel.setWidth("500px"); VerticalLayout layout = new VerticalLayout(); layout.setMargin(true); panel.setContent(layout); layout.addComponent(new Label(message)); Button okButton = new Button(); okButton.addClickListener(new ClickListener() { /** The serial version id. */ private static final long serialVersionUID = 1L; public void buttonClick(ClickEvent event) { window.close(); if (callback != null) { callback.run(); } } }); layout.addComponent(okButton); layout.setComponentAlignment(okButton, Alignment.BOTTOM_RIGHT); okButton.setCaption( org.opencms.workplace.Messages.get().getBundle(A_CmsUI.get().getLocale()).key( org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_OK_0)); window.setContent(panel); window.setClosable(false); window.setResizable(false); A_CmsUI.get().addWindow(window); } /** * Creates a new option group builder.

* * @return a new option group builder */ public static OptionGroupBuilder startOptionGroup() { return new OptionGroupBuilder(); } /** * Visits all descendants of a given component (including the component itself) and applies a predicate * to each.

* * If the predicate returns false for a component, no further descendants will be processed.

* * @param component the component * @param handler the predicate */ public static void visitDescendants(Component component, Predicate handler) { List stack = Lists.newArrayList(); stack.add(component); while (!stack.isEmpty()) { Component currentComponent = stack.get(stack.size() - 1); stack.remove(stack.size() - 1); if (!handler.apply(currentComponent)) { return; } if (currentComponent instanceof HasComponents) { List children = Lists.newArrayList((HasComponents)currentComponent); Collections.reverse(children); stack.addAll(children); } } } /** * Reads the given design and resolves the given macros and localizations.

* @param component the component whose design to read * @param designStream stream to read the design from * @param messages the message bundle to use for localization in the design (may be null) * @param macros other macros to substitute in the macro design (may be null) */ protected static void readAndLocalizeDesign( Component component, InputStream designStream, CmsMessages messages, Map macros) { try { byte[] designBytes = CmsFileUtil.readFully(designStream, true); final String encoding = "UTF-8"; String design = new String(designBytes, encoding); CmsMacroResolver resolver = new CmsMacroResolver() { @Override public String getMacroValue(String macro) { String result = super.getMacroValue(macro); // The macro may contain quotes or angle brackets, so we need to escape the values for insertion into the design file return CmsEncoder.escapeXml(result); } }; if (macros != null) { for (Map.Entry entry : macros.entrySet()) { resolver.addMacro(entry.getKey(), entry.getValue()); } } if (messages != null) { resolver.setMessages(messages); } String resolvedDesign = resolver.resolveMacros(design); Design.read(new ByteArrayInputStream(resolvedDesign.getBytes(encoding)), component); } catch (IOException e) { throw new RuntimeException("Could not read design", e); } finally { try { designStream.close(); } catch (IOException e) { LOG.warn(e.getLocalizedMessage(), e); } } } /** * Returns whether only a single OU is visible to the current user.

* * @param projects the selectable projects * * @return true if only a single OU is visible to the current user */ private static boolean isSingleOu(List projects) { String ouFqn = null; for (CmsProject project : projects) { if (project.isOnlineProject()) { // skip the online project continue; } if (ouFqn == null) { // set the first ou ouFqn = project.getOuFqn(); } else if (!ouFqn.equals(project.getOuFqn())) { // break if one different ou is found return false; } } return true; } }