View Javadoc
1   package org.argeo.cms.ui.util;
2   
3   import static java.nio.charset.StandardCharsets.UTF_8;
4   
5   import java.io.BufferedReader;
6   import java.io.IOException;
7   import java.io.InputStreamReader;
8   import java.net.URL;
9   import java.util.ArrayList;
10  import java.util.Enumeration;
11  import java.util.HashMap;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.stream.Collectors;
15  
16  import org.apache.commons.logging.Log;
17  import org.apache.commons.logging.LogFactory;
18  import org.argeo.cms.CmsException;
19  import org.eclipse.rap.rwt.RWT;
20  import org.eclipse.rap.rwt.application.Application;
21  import org.eclipse.rap.rwt.service.ResourceLoader;
22  import org.osgi.framework.Bundle;
23  import org.osgi.framework.BundleContext;
24  
25  /**
26   * Simplifies the theming of an app (only RAP is supported at this stage).<br>
27   * 
28   * Additional fonts listed in <code>/fonts.txt</code>.<br>
29   * Additional (standard CSS) header in <code>/header.css</code>.<br>
30   * RAP specific CSS files in <code>/rap/*.css</code>.<br>
31   * All images added as additional resources based on extensions
32   * <code>/ ** /*.{png,gif,jpeg,...}</code>.<br>
33   */
34  public class CmsTheme {
35  	private final static Log log = LogFactory.getLog(CmsTheme.class);
36  
37  	private String themeId;
38  	private Map<String, ResourceLoader> css = new HashMap<>();
39  	private Map<String, ResourceLoader> resources = new HashMap<>();
40  
41  	private String headerCss;
42  	private List<String> fonts = new ArrayList<>();
43  
44  	private String basePath;
45  	private String cssPath;
46  
47  	public CmsTheme(BundleContext bundleContext) {
48  		this(bundleContext, null);
49  	}
50  
51  	public CmsTheme(BundleContext bundleContext, String symbolicName) {
52  		Bundle themeBundle;
53  		if (symbolicName == null) {
54  			themeBundle = bundleContext.getBundle();
55  //			basePath = "/theme/";
56  //			cssPath = basePath;
57  		} else {
58  			themeBundle = ThemeUtils.findThemeBundle(bundleContext, symbolicName);
59  		}
60  		basePath = "/";
61  		cssPath = "/rap/";
62  		this.themeId = RWT.DEFAULT_THEME_ID;
63  		addStyleSheets(themeBundle, new BundleResourceLoader(themeBundle));
64  		BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle);
65  		addResources(themeBRL, "*.png");
66  		addResources(themeBRL, "*.gif");
67  		addResources(themeBRL, "*.jpg");
68  		addResources(themeBRL, "*.jpeg");
69  		addResources(themeBRL, "*.svg");
70  		addResources(themeBRL, "*.ico");
71  
72  		// fonts
73  		URL fontsUrl = themeBundle.getEntry(basePath + "fonts.txt");
74  		if (fontsUrl != null) {
75  			loadFontsUrl(fontsUrl);
76  		}
77  
78  		// common CSS header (plain CSS)
79  		URL headerCssUrl = themeBundle.getEntry(basePath + "header.css");
80  		if (headerCssUrl != null) {
81  			try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) {
82  				headerCss = buffer.lines().collect(Collectors.joining("\n"));
83  			} catch (IOException e) {
84  				throw new CmsException("Cannot read " + headerCssUrl, e);
85  			}
86  		}
87  
88  	}
89  
90  	public void apply(Application application) {
91  		resources: for (String name : resources.keySet()) {
92  			if (name.startsWith("target/"))
93  				continue resources; // skip maven output
94  			application.addResource(name, resources.get(name));
95  			if (log.isTraceEnabled())
96  				log.trace("Added resource " + name);
97  		}
98  		for (String name : css.keySet()) {
99  			application.addStyleSheet(themeId, name, css.get(name));
100 			if (log.isDebugEnabled())
101 				log.debug("Added RAP CSS " + name);
102 		}
103 	}
104 
105 	public String getAdditionalHeaders() {
106 		StringBuilder sb = new StringBuilder();
107 		if (headerCss != null) {
108 			sb.append("<style type='text/css'>\n");
109 			sb.append(headerCss);
110 			sb.append("\n</style>\n");
111 		}
112 		for (String link : fonts) {
113 			sb.append("<link rel='stylesheet' href='");
114 			sb.append(link);
115 			sb.append("'/>\n");
116 		}
117 		if (sb.length() == 0)
118 			return null;
119 		else
120 			return sb.toString();
121 	}
122 
123 	void addStyleSheets(Bundle themeBundle, ResourceLoader ssRL) {
124 		Enumeration<URL> themeResources = themeBundle.findEntries(cssPath, "*.css", true);
125 		if (themeResources == null)
126 			return;
127 		while (themeResources.hasMoreElements()) {
128 			String resource = themeResources.nextElement().getPath();
129 			// remove first '/' so that RWT registers it
130 			resource = resource.substring(1);
131 			if (!resource.endsWith("/")) {
132 				if (css.containsKey(resource))
133 					log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
134 				css.put(resource, ssRL);
135 			}
136 
137 		}
138 
139 	}
140 
141 	void loadFontsUrl(URL url) {
142 		try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
143 			String line = null;
144 			while ((line = in.readLine()) != null) {
145 				line = line.trim();
146 				if (!line.equals("") && !line.startsWith("#")) {
147 					fonts.add(line);
148 				}
149 			}
150 		} catch (IOException e) {
151 			throw new CmsException("Cannot load URL " + url, e);
152 		}
153 	}
154 
155 	void addResources(BundleResourceLoader themeBRL, String pattern) {
156 		Bundle themeBundle = themeBRL.getBundle();
157 		Enumeration<URL> themeResources = themeBundle.findEntries(basePath, pattern, true);
158 		if (themeResources == null)
159 			return;
160 		while (themeResources.hasMoreElements()) {
161 			String resource = themeResources.nextElement().getPath();
162 			// remove first '/' so that RWT registers it
163 			resource = resource.substring(1);
164 			if (!resource.endsWith("/")) {
165 				if (resources.containsKey(resource))
166 					log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
167 				resources.put(resource, themeBRL);
168 			}
169 
170 		}
171 
172 	}
173 
174 	public String getThemeId() {
175 		return themeId;
176 	}
177 
178 	public void setThemeId(String themeId) {
179 		this.themeId = themeId;
180 	}
181 
182 	public String getBasePath() {
183 		return basePath;
184 	}
185 
186 	public void setBasePath(String basePath) {
187 		this.basePath = basePath;
188 	}
189 
190 	public String getCssPath() {
191 		return cssPath;
192 	}
193 
194 	public void setCssPath(String cssPath) {
195 		this.cssPath = cssPath;
196 	}
197 
198 }