From 2ae1e1f91e8f72968f9fd63dea634f01b13ab188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Mon, 15 Jun 2020 14:49:17 +0200 Subject: [PATCH 1/7] LPS-115484 Add an API to retrieve configurable CSS variables for a theme --- .../frontend-css-variables-api/bnd.bnd | 4 ++- .../css/variables/CSSVariableDescription.java | 28 +++++++++++++++++ .../css/variables/CSSVariableType.java | 24 +++++++++++++++ .../ThemeCSSVariableDescriptionsRegistry.java | 30 +++++++++++++++++++ .../frontend/css/variables/theme/packageinfo | 1 + 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableDescription.java create mode 100644 modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableType.java create mode 100644 modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/theme/ThemeCSSVariableDescriptionsRegistry.java create mode 100644 modules/apps/frontend-css/frontend-css-variables-api/src/main/resources/com/liferay/frontend/css/variables/theme/packageinfo diff --git a/modules/apps/frontend-css/frontend-css-variables-api/bnd.bnd b/modules/apps/frontend-css/frontend-css-variables-api/bnd.bnd index 731b8df78969b9..f282e75be95fbe 100644 --- a/modules/apps/frontend-css/frontend-css-variables-api/bnd.bnd +++ b/modules/apps/frontend-css/frontend-css-variables-api/bnd.bnd @@ -1,4 +1,6 @@ Bundle-Name: Liferay Frontend CSS Variables API Bundle-SymbolicName: com.liferay.frontend.css.variables.api Bundle-Version: 1.0.0 -Export-Package: com.liferay.frontend.css.variables \ No newline at end of file +Export-Package:\ + com.liferay.frontend.css.variables,\ + com.liferay.frontend.css.variables.theme \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableDescription.java b/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableDescription.java new file mode 100644 index 00000000000000..2c51c770cc02e0 --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableDescription.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables; + +import java.util.Locale; + +/** + * @author Iván Zaera Avellón + */ +public interface CSSVariableDescription { + + public CSSVariableType getCSSVariableType(); + + public String getLabel(Locale locale); + +} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableType.java b/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableType.java new file mode 100644 index 00000000000000..f16065b4f4c2b8 --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableType.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables; + +/** + * @author Iván Zaera Avellón + */ +public enum CSSVariableType { + + COLOR, STRING + +} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/theme/ThemeCSSVariableDescriptionsRegistry.java b/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/theme/ThemeCSSVariableDescriptionsRegistry.java new file mode 100644 index 00000000000000..b1531d507dc8ef --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/theme/ThemeCSSVariableDescriptionsRegistry.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables.theme; + +import com.liferay.frontend.css.variables.CSSVariableDescription; +import com.liferay.portal.kernel.model.Theme; + +import java.util.Map; + +/** + * @author Iván Zaera Avellón + */ +public interface ThemeCSSVariableDescriptionsRegistry { + + public Map getCSSVariableDescriptions( + Theme theme); + +} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-api/src/main/resources/com/liferay/frontend/css/variables/theme/packageinfo b/modules/apps/frontend-css/frontend-css-variables-api/src/main/resources/com/liferay/frontend/css/variables/theme/packageinfo new file mode 100644 index 00000000000000..e2525561ab2e7b --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-api/src/main/resources/com/liferay/frontend/css/variables/theme/packageinfo @@ -0,0 +1 @@ +version 1.0.0 \ No newline at end of file From 4f30d70ce38dbc839e2b51bff8b85a0f975fe757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Mon, 15 Jun 2020 14:51:22 +0200 Subject: [PATCH 2/7] LPS-115484 Implement the API to retrieve configurable CSS variables for a theme The ThemeCSSVariableDescriptionsRegistryImpl class obtains CSS variables from a /WEB-INF/css-variables.json file present inside the theme's WAR file. --- .../internal/CSSVariableDescriptionImpl.java | 74 +++++++ ...meCSSVariableDescriptionsRegistryImpl.java | 73 +++++++ ...eDescriptionsServiceTrackerCustomizer.java | 192 ++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java new file mode 100644 index 00000000000000..4af4182a2f29f1 --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables.web.internal; + +import com.liferay.frontend.css.variables.CSSVariableDescription; +import com.liferay.frontend.css.variables.CSSVariableType; +import com.liferay.petra.string.StringPool; +import com.liferay.portal.kernel.util.Validator; + +import java.util.Locale; +import java.util.Map; + +/** + * @author Iván Zaera Avellón + */ +public class CSSVariableDescriptionImpl implements CSSVariableDescription { + + public CSSVariableDescriptionImpl( + CSSVariableType cssVariableType, Map labelsMap) { + + _cssVariableType = cssVariableType; + _labelsMap = labelsMap; + } + + public void addLabel(String localeKey, String label) { + _labelsMap.put(localeKey, label); + } + + @Override + public CSSVariableType getCSSVariableType() { + return _cssVariableType; + } + + public String getLabel(Locale locale) { + String languageCountryKey = _getLanguageCountryKey(locale); + + if ((languageCountryKey != null) && + _labelsMap.containsKey(languageCountryKey)) { + + return _labelsMap.get(languageCountryKey); + } + + if (_labelsMap.containsKey(locale.getLanguage())) { + return _labelsMap.get(locale.getLanguage()); + } + + return _labelsMap.get(StringPool.BLANK); + } + + private String _getLanguageCountryKey(Locale locale) { + if (Validator.isNull(locale.getCountry())) { + return null; + } + + return locale.getLanguage() + StringPool.UNDERLINE + + locale.getCountry(); + } + + private final CSSVariableType _cssVariableType; + private final Map _labelsMap; + +} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java new file mode 100644 index 00000000000000..5471e08f1a0a88 --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables.web.internal.theme; + +import com.liferay.frontend.css.variables.CSSVariableDescription; +import com.liferay.frontend.css.variables.theme.ThemeCSSVariableDescriptionsRegistry; +import com.liferay.osgi.service.tracker.collections.map.ServiceTrackerMap; +import com.liferay.osgi.service.tracker.collections.map.ServiceTrackerMapFactory; +import com.liferay.portal.kernel.json.JSONFactory; +import com.liferay.portal.kernel.model.Theme; + +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; + +/** + * @author Iván Zaera Avellón + */ +@Component( + immediate = true, service = ThemeCSSVariableDescriptionsRegistry.class +) +public class ThemeCSSVariableDescriptionsRegistryImpl + implements ThemeCSSVariableDescriptionsRegistry { + + @Override + public Map getCSSVariableDescriptions( + Theme theme) { + + return _serviceTrackerMap.getService(theme.getServletContextName()); + } + + @Activate + protected void activate(BundleContext bundleContext) { + _bundleContext = bundleContext; + + _serviceTrackerMap = ServiceTrackerMapFactory.openSingleValueMap( + bundleContext, ServletContext.class, "osgi.web.symbolicname", + new ThemeCSSVariableDescriptionsServiceTrackerCustomizer( + _bundleContext, _jsonFactory)); + } + + @Deactivate + protected void deactivate() { + _serviceTrackerMap.close(); + } + + private BundleContext _bundleContext; + + @Reference + private JSONFactory _jsonFactory; + + private ServiceTrackerMap> + _serviceTrackerMap; + +} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java new file mode 100644 index 00000000000000..52f3bc1d7827cf --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java @@ -0,0 +1,192 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables.web.internal.theme; + +import com.liferay.frontend.css.variables.CSSVariableDescription; +import com.liferay.frontend.css.variables.CSSVariableType; +import com.liferay.frontend.css.variables.web.internal.CSSVariableDescriptionImpl; +import com.liferay.petra.string.StringPool; +import com.liferay.portal.kernel.json.JSONException; +import com.liferay.portal.kernel.json.JSONFactory; +import com.liferay.portal.kernel.json.JSONObject; +import com.liferay.portal.kernel.log.Log; +import com.liferay.portal.kernel.log.LogFactoryUtil; +import com.liferay.portal.kernel.util.StringBundler; +import com.liferay.portal.kernel.util.StringUtil; + +import java.io.IOException; +import java.io.InputStream; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.servlet.ServletContext; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * @author Iván Zaera Avellón + */ +public class ThemeCSSVariableDescriptionsServiceTrackerCustomizer + implements ServiceTrackerCustomizer + > { + + public ThemeCSSVariableDescriptionsServiceTrackerCustomizer( + BundleContext bundleContext, JSONFactory jsonFactory) { + + _bundleContext = bundleContext; + _jsonFactory = jsonFactory; + } + + @Override + public Map addingService( + ServiceReference serviceReference) { + + Map cssVariablesDescriptions = + new ConcurrentHashMap<>(); + + modifiedService(serviceReference, cssVariablesDescriptions); + + return cssVariablesDescriptions; + } + + @Override + public void modifiedService( + ServiceReference serviceReference, + Map cssVariablesDescriptions) { + + ServletContext servletContext = _bundleContext.getService( + serviceReference); + + try { + InputStream is = servletContext.getResourceAsStream( + "/WEB-INF/css-variables.json"); + + if (is == null) { + return; + } + + try { + cssVariablesDescriptions.clear(); + + cssVariablesDescriptions.putAll(_parse(StringUtil.read(is))); + } + catch (IllegalArgumentException illegalArgumentException) { + _log.error( + StringBundler.concat( + "Unable to parse css-variables.json of servlet ", + "context ", servletContext.getServletContextName()), + illegalArgumentException); + } + catch (IOException ioException) { + _log.error( + "Unable to read css-variables.json of servlet context" + + servletContext.getServletContextName(), + ioException); + } + catch (JSONException jsonException) { + _log.error( + "Unable to parse css-variables.json of servlet context" + + servletContext.getServletContextName(), + jsonException); + } + } + finally { + _bundleContext.ungetService(serviceReference); + } + } + + @Override + public void removedService( + ServiceReference serviceReference, + Map cssVariableDescriptions) { + } + + private CSSVariableType _getCSSVariableType(JSONObject jsonObject) { + String type = jsonObject.getString("type"); + + if (type.equals("color")) { + return CSSVariableType.COLOR; + } + + return CSSVariableType.STRING; + } + + private Map _getLabelsMap( + JSONObject cssVariableDefinitionJSONObject, String defaultLabel) { + + Map labelsMap = new HashMap<>(); + + JSONObject labelsMapJSONObject = + cssVariableDefinitionJSONObject.getJSONObject("label"); + + if (labelsMapJSONObject != null) { + for (String localeKey : labelsMapJSONObject.keySet()) { + labelsMap.put( + localeKey, labelsMapJSONObject.getString(localeKey)); + } + } + else { + String label = cssVariableDefinitionJSONObject.getString("label"); + + if (label == null) { + label = defaultLabel; + } + + labelsMap.put(StringPool.BLANK, label); + } + + return labelsMap; + } + + private Map _parse(String json) + throws JSONException { + + JSONObject jsonObject = _jsonFactory.createJSONObject(json); + + JSONObject variablesJSONObject = jsonObject.getJSONObject("variables"); + + if (variablesJSONObject == null) { + throw new IllegalArgumentException( + "Unable to read variables field"); + } + + Map cssVariableDescriptions = + new HashMap<>(); + + for (String name : variablesJSONObject.keySet()) { + JSONObject cssVariableDefinitionJSONObject = + variablesJSONObject.getJSONObject(name); + + cssVariableDescriptions.put( + name, + new CSSVariableDescriptionImpl( + _getCSSVariableType(cssVariableDefinitionJSONObject), + _getLabelsMap(cssVariableDefinitionJSONObject, name))); + } + + return cssVariableDescriptions; + } + + private static final Log _log = LogFactoryUtil.getLog( + ThemeCSSVariableDescriptionsServiceTrackerCustomizer.class); + + private final BundleContext _bundleContext; + private final JSONFactory _jsonFactory; + +} \ No newline at end of file From 51f0e0c38af7767fc7029aa3323adcee874f2d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Mon, 15 Jun 2020 16:53:29 +0200 Subject: [PATCH 3/7] LPS-115484 Add tests --- .../frontend-css-variables-web/build.gradle | 1 + .../internal/CSSVariableDescriptionImpl.java | 2 +- ...criptionsServiceTrackerCustomizerTest.java | 110 ++++++++++++++++++ .../web/internal/theme/css-variables.json | 14 +++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.java create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/css-variables.json diff --git a/modules/apps/frontend-css/frontend-css-variables-web/build.gradle b/modules/apps/frontend-css/frontend-css-variables-web/build.gradle index 83fd59c2f01ae7..9ebb9dd5e8ec0d 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/build.gradle +++ b/modules/apps/frontend-css/frontend-css-variables-web/build.gradle @@ -11,5 +11,6 @@ dependencies { compileOnly project(":core:osgi-service-tracker-collections") compileOnly project(":core:petra:petra-string") + testCompile group: "com.liferay.portal", name: "com.liferay.portal.impl", version: "default" testCompile group: "junit", name: "junit", version: "4.12" } \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java index 4af4182a2f29f1..c7fb6a463cf60f 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java @@ -61,7 +61,7 @@ public String getLabel(Locale locale) { private String _getLanguageCountryKey(Locale locale) { if (Validator.isNull(locale.getCountry())) { - return null; + return locale.getLanguage(); } return locale.getLanguage() + StringPool.UNDERLINE + diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.java b/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.java new file mode 100644 index 00000000000000..1771bc0cbb9287 --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables.web.internal.theme; + +import com.liferay.frontend.css.variables.CSSVariableDescription; +import com.liferay.frontend.css.variables.CSSVariableType; +import com.liferay.portal.json.JSONFactoryImpl; +import com.liferay.portal.kernel.util.LocaleUtil; + +import java.net.URL; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.junit.Assert; +import org.junit.Test; + +import org.mockito.Mockito; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * @author Iván Zaera Avellón + */ +public class ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest { + + @Test + public void testModifiedService() throws Exception { + BundleContext bundleContext = Mockito.mock(BundleContext.class); + + ServletContext servletContext = Mockito.mock(ServletContext.class); + + Mockito.when( + servletContext.getResourceAsStream("/WEB-INF/css-variables.json") + ).thenReturn( + _cssVariablesJsonURL.openStream() + ); + + ServiceReference serviceReference = Mockito.mock( + ServiceReference.class); + + Mockito.when( + bundleContext.getService(serviceReference) + ).thenReturn( + servletContext + ); + + ThemeCSSVariableDescriptionsServiceTrackerCustomizer + themeCSSVariableDescriptionsServiceTrackerCustomizer = + new ThemeCSSVariableDescriptionsServiceTrackerCustomizer( + bundleContext, new JSONFactoryImpl()); + + Map cssVariablesDescriptions = + new HashMap<>(); + + themeCSSVariableDescriptionsServiceTrackerCustomizer.modifiedService( + serviceReference, cssVariablesDescriptions); + + Assert.assertEquals( + cssVariablesDescriptions.toString(), 2, + cssVariablesDescriptions.size()); + + CSSVariableDescription cssVariableDescription = + cssVariablesDescriptions.get("background"); + + Assert.assertEquals( + CSSVariableType.COLOR, cssVariableDescription.getCSSVariableType()); + + Assert.assertEquals( + "Background Color", + cssVariableDescription.getLabel(LocaleUtil.ITALIAN)); + + Assert.assertEquals( + "Background Color", + cssVariableDescription.getLabel(new Locale("es"))); + + cssVariableDescription = cssVariablesDescriptions.get("font"); + + Assert.assertEquals( + CSSVariableType.STRING, + cssVariableDescription.getCSSVariableType()); + + Assert.assertEquals( + "Font", cssVariableDescription.getLabel(LocaleUtil.ITALIAN)); + + Assert.assertEquals( + "Tipo de letra", cssVariableDescription.getLabel(new Locale("es"))); + } + + private static final URL _cssVariablesJsonURL = + ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.class. + getResource("css-variables.json"); + +} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/css-variables.json b/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/css-variables.json new file mode 100644 index 00000000000000..df342730e85d2b --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/css-variables.json @@ -0,0 +1,14 @@ +{ + "variables": { + "background": { + "label": "Background Color", + "type": "color" + }, + "font": { + "label": { + "": "Font", + "es": "Tipo de letra" + } + } + } +} \ No newline at end of file From 6068dcb5ba03f5f11b0d0d09a030a9909d63a723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Tue, 23 Jun 2020 11:39:31 +0200 Subject: [PATCH 4/7] LPS-115484 Read the css-variables.json file path from package.json We will use field liferayTheme.cssVariablesPath to store that value, and 'css-variables.json' will be the default value in case it is missing. --- ...eDescriptionsServiceTrackerCustomizer.java | 142 +++++++++++------- 1 file changed, 91 insertions(+), 51 deletions(-) diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java index 52f3bc1d7827cf..b4108d1f4ff25f 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java @@ -23,8 +23,8 @@ import com.liferay.portal.kernel.json.JSONObject; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; -import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringUtil; +import com.liferay.portal.kernel.util.Validator; import java.io.IOException; import java.io.InputStream; @@ -74,37 +74,21 @@ public void modifiedService( serviceReference); try { - InputStream is = servletContext.getResourceAsStream( - "/WEB-INF/css-variables.json"); + String fileName = _getCSSVariableDescriptionsFileName( + servletContext); - if (is == null) { - return; - } + Map cssVariableDescriptions = + _getCSSVariableDescriptions(servletContext, fileName); - try { - cssVariablesDescriptions.clear(); + cssVariablesDescriptions.clear(); - cssVariablesDescriptions.putAll(_parse(StringUtil.read(is))); - } - catch (IllegalArgumentException illegalArgumentException) { - _log.error( - StringBundler.concat( - "Unable to parse css-variables.json of servlet ", - "context ", servletContext.getServletContextName()), - illegalArgumentException); - } - catch (IOException ioException) { - _log.error( - "Unable to read css-variables.json of servlet context" + - servletContext.getServletContextName(), - ioException); - } - catch (JSONException jsonException) { - _log.error( - "Unable to parse css-variables.json of servlet context" + - servletContext.getServletContextName(), - jsonException); - } + cssVariablesDescriptions.putAll(cssVariableDescriptions); + } + catch (JSONException jsonException) { + _log.error( + "Unable to read css-variables.json of servlet context" + + servletContext.getServletContextName(), + jsonException); } finally { _bundleContext.ungetService(serviceReference); @@ -117,6 +101,75 @@ public void removedService( Map cssVariableDescriptions) { } + private Map _getCSSVariableDescriptions( + ServletContext servletContext, String fileName) + throws JSONException { + + JSONObject jsonObject = _parseJSONObject(servletContext, fileName); + + if (jsonObject == null) { + return null; + } + + JSONObject variablesJSONObject = jsonObject.getJSONObject("variables"); + + if (variablesJSONObject == null) { + throw new JSONException("Unable to read variables field"); + } + + Map cssVariableDescriptions = + new HashMap<>(); + + for (String name : variablesJSONObject.keySet()) { + JSONObject cssVariableDefinitionJSONObject = + variablesJSONObject.getJSONObject(name); + + cssVariableDescriptions.put( + name, + new CSSVariableDescriptionImpl( + _getCSSVariableType(cssVariableDefinitionJSONObject), + _getLabelsMap(cssVariableDefinitionJSONObject, name))); + } + + return cssVariableDescriptions; + } + + private String _getCSSVariableDescriptionsFileName( + ServletContext servletContext) + throws JSONException { + + try (InputStream is = servletContext.getResourceAsStream( + "/package.json")) { + + if (is == null) { + return null; + } + + JSONObject packageJSONObject = _jsonFactory.createJSONObject( + StringUtil.read(is)); + + String cssVariablesPath = StringPool.BLANK; + + JSONObject liferayThemeJSONObject = packageJSONObject.getJSONObject( + "liferayTheme"); + + if (liferayThemeJSONObject != null) { + cssVariablesPath = liferayThemeJSONObject.getString( + "cssVariablesPath"); + } + + if (Validator.isNull(cssVariablesPath)) { + cssVariablesPath = "/WEB-INF/css-variables.json"; + } + + return cssVariablesPath; + } + catch (IOException ioException) { + throw new JSONException( + "Unable to parse package.json", ioException); + } + } + private CSSVariableType _getCSSVariableType(JSONObject jsonObject) { String type = jsonObject.getString("type"); @@ -154,33 +207,20 @@ private Map _getLabelsMap( return labelsMap; } - private Map _parse(String json) + private JSONObject _parseJSONObject( + ServletContext servletContext, String fileName) throws JSONException { - JSONObject jsonObject = _jsonFactory.createJSONObject(json); - - JSONObject variablesJSONObject = jsonObject.getJSONObject("variables"); + try (InputStream is = servletContext.getResourceAsStream(fileName)) { + if (is == null) { + return null; + } - if (variablesJSONObject == null) { - throw new IllegalArgumentException( - "Unable to read variables field"); + return _jsonFactory.createJSONObject(StringUtil.read(is)); } - - Map cssVariableDescriptions = - new HashMap<>(); - - for (String name : variablesJSONObject.keySet()) { - JSONObject cssVariableDefinitionJSONObject = - variablesJSONObject.getJSONObject(name); - - cssVariableDescriptions.put( - name, - new CSSVariableDescriptionImpl( - _getCSSVariableType(cssVariableDefinitionJSONObject), - _getLabelsMap(cssVariableDefinitionJSONObject, name))); + catch (IOException ioException) { + throw new JSONException("Unable to parse " + fileName, ioException); } - - return cssVariableDescriptions; } private static final Log _log = LogFactoryUtil.getLog( From 2066d40dcd8aa2b93edf3a39f78746f6f4f420a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Tue, 23 Jun 2020 12:00:26 +0200 Subject: [PATCH 5/7] LPS-115484 Use an AtomicReference for CSS variable descriptions This is to avoid race conditions in case someone accesses the map in the middle of an update. --- ...meCSSVariableDescriptionsRegistryImpl.java | 11 +++++--- ...eDescriptionsServiceTrackerCustomizer.java | 26 ++++++++----------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java index 5471e08f1a0a88..13b03ef1d0449f 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java @@ -22,6 +22,7 @@ import com.liferay.portal.kernel.model.Theme; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletContext; @@ -44,7 +45,10 @@ public class ThemeCSSVariableDescriptionsRegistryImpl public Map getCSSVariableDescriptions( Theme theme) { - return _serviceTrackerMap.getService(theme.getServletContextName()); + AtomicReference> atomicReference = + _serviceTrackerMap.getService(theme.getServletContextName()); + + return atomicReference.get(); } @Activate @@ -67,7 +71,8 @@ protected void deactivate() { @Reference private JSONFactory _jsonFactory; - private ServiceTrackerMap> - _serviceTrackerMap; + private ServiceTrackerMap + >> + _serviceTrackerMap; } \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java index b4108d1f4ff25f..59e59234d05342 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java @@ -31,7 +31,7 @@ import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletContext; @@ -44,7 +44,7 @@ */ public class ThemeCSSVariableDescriptionsServiceTrackerCustomizer implements ServiceTrackerCustomizer - > { + >> { public ThemeCSSVariableDescriptionsServiceTrackerCustomizer( BundleContext bundleContext, JSONFactory jsonFactory) { @@ -54,21 +54,21 @@ public ThemeCSSVariableDescriptionsServiceTrackerCustomizer( } @Override - public Map addingService( + public AtomicReference> addingService( ServiceReference serviceReference) { - Map cssVariablesDescriptions = - new ConcurrentHashMap<>(); + AtomicReference> atomicReference = + new AtomicReference<>(); - modifiedService(serviceReference, cssVariablesDescriptions); + modifiedService(serviceReference, atomicReference); - return cssVariablesDescriptions; + return atomicReference; } @Override public void modifiedService( ServiceReference serviceReference, - Map cssVariablesDescriptions) { + AtomicReference> atomicReference) { ServletContext servletContext = _bundleContext.getService( serviceReference); @@ -77,12 +77,8 @@ public void modifiedService( String fileName = _getCSSVariableDescriptionsFileName( servletContext); - Map cssVariableDescriptions = - _getCSSVariableDescriptions(servletContext, fileName); - - cssVariablesDescriptions.clear(); - - cssVariablesDescriptions.putAll(cssVariableDescriptions); + atomicReference.set( + _getCSSVariableDescriptions(servletContext, fileName)); } catch (JSONException jsonException) { _log.error( @@ -98,7 +94,7 @@ public void modifiedService( @Override public void removedService( ServiceReference serviceReference, - Map cssVariableDescriptions) { + AtomicReference> atomicReference) { } private Map _getCSSVariableDescriptions( From ff4655d3ed22acc89a1b71b2a8105f7512ce0f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Tue, 23 Jun 2020 12:48:32 +0200 Subject: [PATCH 6/7] LPS-115484 Remove CSS variable localization support Don't support localization in the service itself for now. We will see in the future how we model it, but we don't want to couple it with the service itself, since it is a very specific UI level thing. --- .../css/variables/CSSVariableDescription.java | 4 +- .../internal/CSSVariableDescriptionImpl.java | 41 +++---------------- ...eDescriptionsServiceTrackerCustomizer.java | 29 +------------ 3 files changed, 8 insertions(+), 66 deletions(-) diff --git a/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableDescription.java b/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableDescription.java index 2c51c770cc02e0..8f97061b6ba7e0 100644 --- a/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableDescription.java +++ b/modules/apps/frontend-css/frontend-css-variables-api/src/main/java/com/liferay/frontend/css/variables/CSSVariableDescription.java @@ -14,8 +14,6 @@ package com.liferay.frontend.css.variables; -import java.util.Locale; - /** * @author Iván Zaera Avellón */ @@ -23,6 +21,6 @@ public interface CSSVariableDescription { public CSSVariableType getCSSVariableType(); - public String getLabel(Locale locale); + public String getName(); } \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java index c7fb6a463cf60f..4dd870d9f2cd1d 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/CSSVariableDescriptionImpl.java @@ -16,11 +16,6 @@ import com.liferay.frontend.css.variables.CSSVariableDescription; import com.liferay.frontend.css.variables.CSSVariableType; -import com.liferay.petra.string.StringPool; -import com.liferay.portal.kernel.util.Validator; - -import java.util.Locale; -import java.util.Map; /** * @author Iván Zaera Avellón @@ -28,14 +23,10 @@ public class CSSVariableDescriptionImpl implements CSSVariableDescription { public CSSVariableDescriptionImpl( - CSSVariableType cssVariableType, Map labelsMap) { + CSSVariableType cssVariableType, String name) { _cssVariableType = cssVariableType; - _labelsMap = labelsMap; - } - - public void addLabel(String localeKey, String label) { - _labelsMap.put(localeKey, label); + _name = name; } @Override @@ -43,32 +34,12 @@ public CSSVariableType getCSSVariableType() { return _cssVariableType; } - public String getLabel(Locale locale) { - String languageCountryKey = _getLanguageCountryKey(locale); - - if ((languageCountryKey != null) && - _labelsMap.containsKey(languageCountryKey)) { - - return _labelsMap.get(languageCountryKey); - } - - if (_labelsMap.containsKey(locale.getLanguage())) { - return _labelsMap.get(locale.getLanguage()); - } - - return _labelsMap.get(StringPool.BLANK); - } - - private String _getLanguageCountryKey(Locale locale) { - if (Validator.isNull(locale.getCountry())) { - return locale.getLanguage(); - } - - return locale.getLanguage() + StringPool.UNDERLINE + - locale.getCountry(); + @Override + public String getName() { + return _name; } private final CSSVariableType _cssVariableType; - private final Map _labelsMap; + private final String _name; } \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java index 59e59234d05342..a7d068c7167d61 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java @@ -124,7 +124,7 @@ private Map _getCSSVariableDescriptions( name, new CSSVariableDescriptionImpl( _getCSSVariableType(cssVariableDefinitionJSONObject), - _getLabelsMap(cssVariableDefinitionJSONObject, name))); + name)); } return cssVariableDescriptions; @@ -176,33 +176,6 @@ private CSSVariableType _getCSSVariableType(JSONObject jsonObject) { return CSSVariableType.STRING; } - private Map _getLabelsMap( - JSONObject cssVariableDefinitionJSONObject, String defaultLabel) { - - Map labelsMap = new HashMap<>(); - - JSONObject labelsMapJSONObject = - cssVariableDefinitionJSONObject.getJSONObject("label"); - - if (labelsMapJSONObject != null) { - for (String localeKey : labelsMapJSONObject.keySet()) { - labelsMap.put( - localeKey, labelsMapJSONObject.getString(localeKey)); - } - } - else { - String label = cssVariableDefinitionJSONObject.getString("label"); - - if (label == null) { - label = defaultLabel; - } - - labelsMap.put(StringPool.BLANK, label); - } - - return labelsMap; - } - private JSONObject _parseJSONObject( ServletContext servletContext, String fileName) throws JSONException { From 72464335a0a13ce88405fe8eac717f0e20f6898e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Zaera=20Avell=C3=B3n?= Date: Tue, 23 Jun 2020 15:43:20 +0200 Subject: [PATCH 7/7] LPS-115484 Make path to CSS variables file configurable First, we will use the Tokens-Path bundle header to get the path to the JSON file inside the WAR and will use WEB-INF/tokens.json as default value. Then we will change the format of the JSON file to make it more extensible and maintainable in the future. Note that themes are WAR files, not OSGi bundles, so people need to add the Tokens-Path header to the liferay-plugin-package.properties file to make it appear in the MANIFEST.MF file of the generated OSGi bundle. Also note that we check for the existence of a tag in the liferay-look-and-feel.xml file to distinguish theme WAR files from other type of WAR artifacts. --- .../internal/theme/ThemeBundleInspector.java | 172 ++++++++++++++++++ ...meCSSVariableDescriptionsRegistryImpl.java | 2 +- ...eDescriptionsServiceTrackerCustomizer.java | 137 ++------------ .../theme/ThemeBundleInspectorTest.java | 128 +++++++++++++ ...criptionsServiceTrackerCustomizerTest.java | 88 +++++---- .../web/internal/theme/css-variables.json | 14 -- .../internal/theme/liferay-look-and-feel.xml | 34 ++++ .../variables/web/internal/theme/tokens.json | 11 ++ 8 files changed, 412 insertions(+), 174 deletions(-) create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeBundleInspector.java create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeBundleInspectorTest.java delete mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/css-variables.json create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/liferay-look-and-feel.xml create mode 100644 modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/tokens.json diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeBundleInspector.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeBundleInspector.java new file mode 100644 index 00000000000000..9d5832dbdbb470 --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeBundleInspector.java @@ -0,0 +1,172 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables.web.internal.theme; + +import com.liferay.frontend.css.variables.CSSVariableDescription; +import com.liferay.frontend.css.variables.CSSVariableType; +import com.liferay.frontend.css.variables.web.internal.CSSVariableDescriptionImpl; +import com.liferay.petra.string.StringBundler; +import com.liferay.portal.kernel.json.JSONArray; +import com.liferay.portal.kernel.json.JSONException; +import com.liferay.portal.kernel.json.JSONFactory; +import com.liferay.portal.kernel.json.JSONObject; +import com.liferay.portal.kernel.util.LocaleUtil; +import com.liferay.portal.kernel.util.StringUtil; +import com.liferay.portal.kernel.util.Validator; + +import java.io.IOException; +import java.io.InputStream; + +import java.net.URL; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.osgi.framework.Bundle; + +/** + * @author Iván Zaera Avellón + */ +public class ThemeBundleInspector { + + public static boolean isTheme(Bundle bundle) { + boolean theme = false; + + URL url = bundle.getResource("WEB-INF/liferay-look-and-feel.xml"); + + if (url != null) { + try (InputStream inputStream = url.openStream()) { + String xml = StringUtil.read(inputStream); + + if (xml.contains("")) { + theme = true; + } + } + catch (IOException ioException) { + throw new RuntimeException(ioException); + } + } + + return theme; + } + + /** + * @throws IllegalArgumentException if the bundle is not a theme bundle + */ + public ThemeBundleInspector(Bundle bundle, JSONFactory jsonFactory) + throws IllegalArgumentException { + + _bundle = bundle; + _jsonFactory = jsonFactory; + + if (!isTheme(bundle)) { + throw new IllegalArgumentException( + StringBundler.concat( + "Bundle ", _bundle.getSymbolicName(), " is not a theme")); + } + } + + public Map getCSSVariableDescriptions() + throws JSONException { + + JSONObject jsonObject = _parseJSONObject(_getTokensPath()); + + if (jsonObject == null) { + return null; + } + + JSONArray variablesJSONArray = jsonObject.getJSONArray("variables"); + + if (variablesJSONArray == null) { + return null; + } + + Map cssVariableDescriptions = + new HashMap<>(); + + for (int i = 0; i < variablesJSONArray.length(); i++) { + JSONObject cssVariableDefinitionJSONObject = + variablesJSONArray.getJSONObject(i); + + String name = cssVariableDefinitionJSONObject.getString("name"); + + cssVariableDescriptions.put( + name, + new CSSVariableDescriptionImpl( + _getCSSVariableType(cssVariableDefinitionJSONObject), + name)); + } + + if (cssVariableDescriptions.isEmpty()) { + return null; + } + + return cssVariableDescriptions; + } + + public String getSymbolicName() { + return _bundle.getSymbolicName(); + } + + private CSSVariableType _getCSSVariableType(JSONObject jsonObject) { + String type = jsonObject.getString("type"); + + if (type.equals("color")) { + return CSSVariableType.COLOR; + } + + return CSSVariableType.STRING; + } + + private String _getTokensPath() { + Locale defaultLocale = LocaleUtil.getDefault(); + + Dictionary headers = _bundle.getHeaders( + defaultLocale.toString()); + + String tokensPath = headers.get("Tokens-Path"); + + if (Validator.isNull(tokensPath)) { + tokensPath = "WEB-INF/tokens.json"; + } + + if (tokensPath.charAt(0) == '/') { + tokensPath = tokensPath.substring(1); + } + + return tokensPath; + } + + private JSONObject _parseJSONObject(String fileName) throws JSONException { + URL resource = _bundle.getResource(fileName); + + if (resource == null) { + return null; + } + + try (InputStream is = resource.openStream()) { + return _jsonFactory.createJSONObject(StringUtil.read(is)); + } + catch (IOException ioException) { + throw new JSONException("Unable to parse " + fileName, ioException); + } + } + + private final Bundle _bundle; + private final JSONFactory _jsonFactory; + +} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java index 13b03ef1d0449f..95b329597e5d10 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsRegistryImpl.java @@ -58,7 +58,7 @@ protected void activate(BundleContext bundleContext) { _serviceTrackerMap = ServiceTrackerMapFactory.openSingleValueMap( bundleContext, ServletContext.class, "osgi.web.symbolicname", new ThemeCSSVariableDescriptionsServiceTrackerCustomizer( - _bundleContext, _jsonFactory)); + _jsonFactory)); } @Deactivate diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java index a7d068c7167d61..9136098a1f6a9c 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/main/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizer.java @@ -15,27 +15,17 @@ package com.liferay.frontend.css.variables.web.internal.theme; import com.liferay.frontend.css.variables.CSSVariableDescription; -import com.liferay.frontend.css.variables.CSSVariableType; -import com.liferay.frontend.css.variables.web.internal.CSSVariableDescriptionImpl; -import com.liferay.petra.string.StringPool; import com.liferay.portal.kernel.json.JSONException; import com.liferay.portal.kernel.json.JSONFactory; -import com.liferay.portal.kernel.json.JSONObject; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; -import com.liferay.portal.kernel.util.StringUtil; -import com.liferay.portal.kernel.util.Validator; -import java.io.IOException; -import java.io.InputStream; - -import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletContext; -import org.osgi.framework.BundleContext; +import org.osgi.framework.Bundle; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTrackerCustomizer; @@ -47,9 +37,8 @@ public class ThemeCSSVariableDescriptionsServiceTrackerCustomizer >> { public ThemeCSSVariableDescriptionsServiceTrackerCustomizer( - BundleContext bundleContext, JSONFactory jsonFactory) { + JSONFactory jsonFactory) { - _bundleContext = bundleContext; _jsonFactory = jsonFactory; } @@ -70,25 +59,29 @@ public void modifiedService( ServiceReference serviceReference, AtomicReference> atomicReference) { - ServletContext servletContext = _bundleContext.getService( - serviceReference); + Bundle bundle = serviceReference.getBundle(); + + if (!ThemeBundleInspector.isTheme(bundle)) { + return; + } + + ThemeBundleInspector themeBundleInspector = new ThemeBundleInspector( + bundle, _jsonFactory); try { - String fileName = _getCSSVariableDescriptionsFileName( - servletContext); + Map cssVariableDescriptions = + themeBundleInspector.getCSSVariableDescriptions(); - atomicReference.set( - _getCSSVariableDescriptions(servletContext, fileName)); + if (cssVariableDescriptions != null) { + atomicReference.set(cssVariableDescriptions); + } } catch (JSONException jsonException) { _log.error( - "Unable to read css-variables.json of servlet context" + - servletContext.getServletContextName(), + "Unable to obtain CSS variable descriptions of theme " + + themeBundleInspector.getSymbolicName(), jsonException); } - finally { - _bundleContext.ungetService(serviceReference); - } } @Override @@ -97,105 +90,9 @@ public void removedService( AtomicReference> atomicReference) { } - private Map _getCSSVariableDescriptions( - ServletContext servletContext, String fileName) - throws JSONException { - - JSONObject jsonObject = _parseJSONObject(servletContext, fileName); - - if (jsonObject == null) { - return null; - } - - JSONObject variablesJSONObject = jsonObject.getJSONObject("variables"); - - if (variablesJSONObject == null) { - throw new JSONException("Unable to read variables field"); - } - - Map cssVariableDescriptions = - new HashMap<>(); - - for (String name : variablesJSONObject.keySet()) { - JSONObject cssVariableDefinitionJSONObject = - variablesJSONObject.getJSONObject(name); - - cssVariableDescriptions.put( - name, - new CSSVariableDescriptionImpl( - _getCSSVariableType(cssVariableDefinitionJSONObject), - name)); - } - - return cssVariableDescriptions; - } - - private String _getCSSVariableDescriptionsFileName( - ServletContext servletContext) - throws JSONException { - - try (InputStream is = servletContext.getResourceAsStream( - "/package.json")) { - - if (is == null) { - return null; - } - - JSONObject packageJSONObject = _jsonFactory.createJSONObject( - StringUtil.read(is)); - - String cssVariablesPath = StringPool.BLANK; - - JSONObject liferayThemeJSONObject = packageJSONObject.getJSONObject( - "liferayTheme"); - - if (liferayThemeJSONObject != null) { - cssVariablesPath = liferayThemeJSONObject.getString( - "cssVariablesPath"); - } - - if (Validator.isNull(cssVariablesPath)) { - cssVariablesPath = "/WEB-INF/css-variables.json"; - } - - return cssVariablesPath; - } - catch (IOException ioException) { - throw new JSONException( - "Unable to parse package.json", ioException); - } - } - - private CSSVariableType _getCSSVariableType(JSONObject jsonObject) { - String type = jsonObject.getString("type"); - - if (type.equals("color")) { - return CSSVariableType.COLOR; - } - - return CSSVariableType.STRING; - } - - private JSONObject _parseJSONObject( - ServletContext servletContext, String fileName) - throws JSONException { - - try (InputStream is = servletContext.getResourceAsStream(fileName)) { - if (is == null) { - return null; - } - - return _jsonFactory.createJSONObject(StringUtil.read(is)); - } - catch (IOException ioException) { - throw new JSONException("Unable to parse " + fileName, ioException); - } - } - private static final Log _log = LogFactoryUtil.getLog( ThemeCSSVariableDescriptionsServiceTrackerCustomizer.class); - private final BundleContext _bundleContext; private final JSONFactory _jsonFactory; } \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeBundleInspectorTest.java b/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeBundleInspectorTest.java new file mode 100644 index 00000000000000..a28a0dcbb6e70c --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeBundleInspectorTest.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2000-present Liferay, Inc. All rights reserved. + * + * 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. + */ + +package com.liferay.frontend.css.variables.web.internal.theme; + +import com.liferay.frontend.css.variables.CSSVariableDescription; +import com.liferay.frontend.css.variables.CSSVariableType; +import com.liferay.portal.json.JSONFactoryImpl; +import com.liferay.portal.kernel.json.JSONException; +import com.liferay.portal.kernel.util.HashMapDictionary; + +import java.net.URL; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import org.mockito.Mockito; + +import org.osgi.framework.Bundle; + +/** + * @author Iván Zaera Avellón + */ +public class ThemeBundleInspectorTest { + + @Test(expected = IllegalArgumentException.class) + public void testConstructorThrowsWhenNotAThemeBundle() { + Bundle bundle = Mockito.mock(Bundle.class); + + new ThemeBundleInspector(bundle, new JSONFactoryImpl()); + } + + @Test + public void testGetCSSVariableDescriptions() throws JSONException { + Bundle bundle = Mockito.mock(Bundle.class); + + Mockito.when( + bundle.getHeaders(Mockito.anyString()) + ).thenReturn( + new HashMapDictionary<>() + ); + + Mockito.when( + bundle.getResource("WEB-INF/liferay-look-and-feel.xml") + ).thenReturn( + _liferayLookAndFeelXmlURL + ); + + Mockito.when( + bundle.getResource("WEB-INF/tokens.json") + ).thenReturn( + _tokensJsonURL + ); + + Mockito.when( + bundle.getSymbolicName() + ).thenReturn( + "A theme" + ); + + ThemeBundleInspector themeBundleInspector = new ThemeBundleInspector( + bundle, new JSONFactoryImpl()); + + Map cssVariableDescriptions = + themeBundleInspector.getCSSVariableDescriptions(); + + Assert.assertEquals( + cssVariableDescriptions.toString(), 2, + cssVariableDescriptions.size()); + + CSSVariableDescription cssVariableDescription = + cssVariableDescriptions.get("background"); + + Assert.assertEquals("background", cssVariableDescription.getName()); + + Assert.assertEquals( + CSSVariableType.COLOR, cssVariableDescription.getCSSVariableType()); + + cssVariableDescription = cssVariableDescriptions.get("font"); + + Assert.assertEquals("font", cssVariableDescription.getName()); + + Assert.assertEquals( + CSSVariableType.STRING, + cssVariableDescription.getCSSVariableType()); + } + + @Test + public void testGetSymbolicName() { + Bundle bundle = Mockito.mock(Bundle.class); + + Mockito.when( + bundle.getResource("WEB-INF/liferay-look-and-feel.xml") + ).thenReturn( + _liferayLookAndFeelXmlURL + ); + + Mockito.when( + bundle.getSymbolicName() + ).thenReturn( + "A theme" + ); + + ThemeBundleInspector themeBundleInspector = new ThemeBundleInspector( + bundle, new JSONFactoryImpl()); + + Assert.assertEquals("A theme", themeBundleInspector.getSymbolicName()); + } + + private static final URL _liferayLookAndFeelXmlURL = + ThemeBundleInspectorTest.class.getResource("liferay-look-and-feel.xml"); + private static final URL _tokensJsonURL = + ThemeBundleInspectorTest.class.getResource("tokens.json"); + +} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.java b/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.java index 1771bc0cbb9287..8e7356ad259464 100644 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.java +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/test/java/com/liferay/frontend/css/variables/web/internal/theme/ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.java @@ -17,13 +17,12 @@ import com.liferay.frontend.css.variables.CSSVariableDescription; import com.liferay.frontend.css.variables.CSSVariableType; import com.liferay.portal.json.JSONFactoryImpl; -import com.liferay.portal.kernel.util.LocaleUtil; +import com.liferay.portal.kernel.util.HashMapDictionary; import java.net.URL; -import java.util.HashMap; -import java.util.Locale; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletContext; @@ -32,7 +31,7 @@ import org.mockito.Mockito; -import org.osgi.framework.BundleContext; +import org.osgi.framework.Bundle; import org.osgi.framework.ServiceReference; /** @@ -41,70 +40,81 @@ public class ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest { @Test - public void testModifiedService() throws Exception { - BundleContext bundleContext = Mockito.mock(BundleContext.class); + public void testModifiedService() { + ThemeCSSVariableDescriptionsServiceTrackerCustomizer + themeCSSVariableDescriptionsServiceTrackerCustomizer = + new ThemeCSSVariableDescriptionsServiceTrackerCustomizer( + new JSONFactoryImpl()); + + ServiceReference serviceReference = Mockito.mock( + ServiceReference.class); - ServletContext servletContext = Mockito.mock(ServletContext.class); + Bundle bundle = Mockito.mock(Bundle.class); Mockito.when( - servletContext.getResourceAsStream("/WEB-INF/css-variables.json") + bundle.getHeaders(Mockito.anyString()) ).thenReturn( - _cssVariablesJsonURL.openStream() + new HashMapDictionary<>() ); - ServiceReference serviceReference = Mockito.mock( - ServiceReference.class); + Mockito.when( + bundle.getResource("WEB-INF/liferay-look-and-feel.xml") + ).thenReturn( + _liferayLookAndFeelXmlURL + ); Mockito.when( - bundleContext.getService(serviceReference) + bundle.getResource("WEB-INF/tokens.json") ).thenReturn( - servletContext + _tokensJsonURL ); - ThemeCSSVariableDescriptionsServiceTrackerCustomizer - themeCSSVariableDescriptionsServiceTrackerCustomizer = - new ThemeCSSVariableDescriptionsServiceTrackerCustomizer( - bundleContext, new JSONFactoryImpl()); + Mockito.when( + bundle.getSymbolicName() + ).thenReturn( + "A theme" + ); + + Mockito.when( + serviceReference.getBundle() + ).thenReturn( + bundle + ); - Map cssVariablesDescriptions = - new HashMap<>(); + AtomicReference> atomicReference = + new AtomicReference<>(); themeCSSVariableDescriptionsServiceTrackerCustomizer.modifiedService( - serviceReference, cssVariablesDescriptions); + serviceReference, atomicReference); + + Map cssVariableDescriptions = + atomicReference.get(); Assert.assertEquals( - cssVariablesDescriptions.toString(), 2, - cssVariablesDescriptions.size()); + cssVariableDescriptions.toString(), 2, + cssVariableDescriptions.size()); CSSVariableDescription cssVariableDescription = - cssVariablesDescriptions.get("background"); + cssVariableDescriptions.get("background"); - Assert.assertEquals( - CSSVariableType.COLOR, cssVariableDescription.getCSSVariableType()); + Assert.assertEquals("background", cssVariableDescription.getName()); Assert.assertEquals( - "Background Color", - cssVariableDescription.getLabel(LocaleUtil.ITALIAN)); + CSSVariableType.COLOR, cssVariableDescription.getCSSVariableType()); - Assert.assertEquals( - "Background Color", - cssVariableDescription.getLabel(new Locale("es"))); + cssVariableDescription = cssVariableDescriptions.get("font"); - cssVariableDescription = cssVariablesDescriptions.get("font"); + Assert.assertEquals("font", cssVariableDescription.getName()); Assert.assertEquals( CSSVariableType.STRING, cssVariableDescription.getCSSVariableType()); - - Assert.assertEquals( - "Font", cssVariableDescription.getLabel(LocaleUtil.ITALIAN)); - - Assert.assertEquals( - "Tipo de letra", cssVariableDescription.getLabel(new Locale("es"))); } - private static final URL _cssVariablesJsonURL = + private static final URL _liferayLookAndFeelXmlURL = + ThemeBundleInspectorTest.class.getResource("liferay-look-and-feel.xml"); + private static final URL _tokensJsonURL = ThemeCSSVariableDescriptionsServiceTrackerCustomizerTest.class. - getResource("css-variables.json"); + getResource("tokens.json"); } \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/css-variables.json b/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/css-variables.json deleted file mode 100644 index df342730e85d2b..00000000000000 --- a/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/css-variables.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "variables": { - "background": { - "label": "Background Color", - "type": "color" - }, - "font": { - "label": { - "": "Font", - "es": "Tipo de letra" - } - } - } -} \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/liferay-look-and-feel.xml b/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/liferay-look-and-feel.xml new file mode 100644 index 00000000000000..fdb2404d4c737f --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/liferay-look-and-feel.xml @@ -0,0 +1,34 @@ + + + + + + 7.3.0+ + + + ftl + + + + + + + + + + + + User + + + portlet-barebone + + + true + portlet-borderless + + + portlet-decorate + + + \ No newline at end of file diff --git a/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/tokens.json b/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/tokens.json new file mode 100644 index 00000000000000..abf23e90ee608a --- /dev/null +++ b/modules/apps/frontend-css/frontend-css-variables-web/src/test/resources/com/liferay/frontend/css/variables/web/internal/theme/tokens.json @@ -0,0 +1,11 @@ +{ + "variables": [ + { + "name": "background", + "type": "color" + }, + { + "name": "font" + } + ] +} \ No newline at end of file