package org.argeo.cms.ui.util; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; /** * Simplifies the theming of an app (only RAP is supported at this stage).
* * Additional fonts listed in /fonts.txt.
* Additional (standard CSS) header in /header.css.
* RAP specific CSS files in /rap/*.css.
* All images added as additional resources based on extensions * / ** /*.{png,gif,jpeg,...}.
*/ public class BundleCmsTheme extends AbstractCmsTheme { public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2"; public final static String CMS_THEME_PROPERTY = "argeo.cms.theme"; public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle"; private final static String HEADER_CSS = "header.css"; private final static String FONTS_TXT = "fonts.txt"; private final static String BODY_HTML = "body.html"; // private final static Log log = LogFactory.getLog(BundleCmsTheme.class); private String themeId; private Set webCssPaths = new TreeSet<>(); private Set rapCssPaths = new TreeSet<>(); private Set swtCssPaths = new TreeSet<>(); private Set imagesPaths = new TreeSet<>(); private Set fontsPaths = new TreeSet<>(); private String headerCss; private List fonts = new ArrayList<>(); private String bodyHtml=""; private String basePath; private String styleCssPath; // private String webCssPath; // private String rapCssPath; // private String swtCssPath; private Bundle themeBundle; public BundleCmsTheme() { } public void init(BundleContext bundleContext, Map properties) { initResources(bundleContext, null); } public void destroy(BundleContext bundleContext, Map properties) { } @Deprecated public BundleCmsTheme(BundleContext bundleContext) { this(bundleContext, null); } @Deprecated public BundleCmsTheme(BundleContext bundleContext, String symbolicName) { initResources(bundleContext, symbolicName); } private void initResources(BundleContext bundleContext, String symbolicName) { if (symbolicName == null) { themeBundle = bundleContext.getBundle(); // basePath = "/theme/"; // cssPath = basePath; } else { themeBundle = findThemeBundle(bundleContext, symbolicName); } basePath = "/"; styleCssPath = "/style/"; // webCssPath = "/css/"; // rapCssPath = "/rap/"; // swtCssPath = "/swt/"; // this.themeId = RWT.DEFAULT_THEME_ID; this.themeId = themeBundle.getSymbolicName(); webCssPaths = addCss(themeBundle, "/css/"); rapCssPaths = addCss(themeBundle, "/rap/"); swtCssPaths = addCss(themeBundle, "/swt/"); addImages("*.png"); addImages("*.gif"); addImages("*.jpg"); addImages("*.jpeg"); addImages("*.svg"); addImages("*.ico"); addFonts("*.woff"); addFonts("*.woff2"); // fonts URL fontsUrl = themeBundle.getEntry(basePath + FONTS_TXT); if (fontsUrl != null) { loadFontsUrl(fontsUrl); } // common CSS header (plain CSS) URL headerCssUrl = themeBundle.getEntry(basePath + HEADER_CSS); if (headerCssUrl != null) { // added to plain Web CSS webCssPaths.add(basePath + HEADER_CSS); // and it will also be used by RAP: try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) { headerCss = buffer.lines().collect(Collectors.joining("\n")); } catch (IOException e) { throw new IllegalArgumentException("Cannot read " + headerCssUrl, e); } } // body URL bodyUrl = themeBundle.getEntry(basePath + BODY_HTML); if (bodyUrl != null) { loadBodyHtml(bodyUrl); } } public String getHtmlHeaders() { StringBuilder sb = new StringBuilder(); if (headerCss != null) { sb.append("\n"); } for (String link : fonts) { sb.append("\n"); } if (sb.length() == 0) return null; else return sb.toString(); } @Override public String getBodyHtml() { return bodyHtml; } Set addCss(Bundle themeBundle, String path) { Set paths = new TreeSet<>(); // common CSS Enumeration commonResources = themeBundle.findEntries(styleCssPath, "*.css", true); if (commonResources != null) { while (commonResources.hasMoreElements()) { String resource = commonResources.nextElement().getPath(); // remove first '/' so that RWT registers it resource = resource.substring(1); if (!resource.endsWith("/")) { paths.add(resource); } } } // specific CSS Enumeration themeResources = themeBundle.findEntries(path, "*.css", true); if (themeResources != null) { while (themeResources.hasMoreElements()) { String resource = themeResources.nextElement().getPath(); // remove first '/' so that RWT registers it resource = resource.substring(1); if (!resource.endsWith("/")) { paths.add(resource); } } } return paths; } void loadFontsUrl(URL url) { try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) { String line = null; while ((line = in.readLine()) != null) { line = line.trim(); if (!line.equals("") && !line.startsWith("#")) { fonts.add(line); } } } catch (IOException e) { throw new IllegalArgumentException("Cannot load URL " + url, e); } } void loadBodyHtml(URL url) { try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) { bodyHtml= IOUtils.toString(url,StandardCharsets.UTF_8); } catch (IOException e) { throw new IllegalArgumentException("Cannot load URL " + url, e); } } void addImages(String pattern) { Enumeration themeResources = themeBundle.findEntries(basePath, pattern, true); if (themeResources == null) return; while (themeResources.hasMoreElements()) { String resource = themeResources.nextElement().getPath(); // remove first '/' so that RWT registers it resource = resource.substring(1); if (!resource.endsWith("/")) { // if (resources.containsKey(resource)) // log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName()); // resources.put(resource, themeBRL); imagesPaths.add(resource); } } } void addFonts(String pattern) { Enumeration themeResources = themeBundle.findEntries(basePath, pattern, true); if (themeResources == null) return; while (themeResources.hasMoreElements()) { String resource = themeResources.nextElement().getPath(); // remove first '/' so that RWT registers it resource = resource.substring(1); if (!resource.endsWith("/")) { // if (resources.containsKey(resource)) // log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName()); // resources.put(resource, themeBRL); fontsPaths.add(resource); } } } @Override public InputStream getResourceAsStream(String resourceName) throws IOException { URL res = themeBundle.getEntry(resourceName); if (res == null) { res = themeBundle.getResource(resourceName); if (res == null) return null; // throw new IllegalArgumentException( // "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName()); } return res.openStream(); } public String getThemeId() { return themeId; } // public void setThemeId(String themeId) { // this.themeId = themeId; // } // // public String getBasePath() { // return basePath; // } // // public void setBasePath(String basePath) { // this.basePath = basePath; // } // // public String getRapCssPath() { // return rapCssPath; // } // // public void setRapCssPath(String cssPath) { // this.rapCssPath = cssPath; // } @Override public Set getWebCssPaths() { return webCssPaths; } @Override public Set getRapCssPaths() { return rapCssPaths; } @Override public Set getSwtCssPaths() { return swtCssPaths; } @Override public Set getImagesPaths() { return imagesPaths; } @Override public Set getFontsPaths() { return fontsPaths; } @Override public InputStream loadPath(String path) throws IOException { URL url = themeBundle.getResource(path); if (url == null) throw new IllegalArgumentException( "Path " + path + " not found in bundle " + themeBundle.getSymbolicName()); return url.openStream(); } private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) { if (themeId == null) return null; // TODO optimize // TODO deal with multiple versions Bundle themeBundle = null; if (themeId != null) { for (Bundle bundle : bundleContext.getBundles()) if (themeId.equals(bundle.getSymbolicName())) { themeBundle = bundle; break; } } return themeBundle; } @Override public int hashCode() { return themeId.hashCode(); } @Override public String toString() { return "Bundle CMS Theme " + themeId; } }