]> git.argeo.org Git - lgpl/argeo-commons.git/blob - BundleCmsTheme.java
6b997e6a35c4f0e691b412b1d9129a2759b4ede7
[lgpl/argeo-commons.git] / BundleCmsTheme.java
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.InputStream;
8 import java.io.InputStreamReader;
9 import java.net.URL;
10 import java.nio.charset.StandardCharsets;
11 import java.util.ArrayList;
12 import java.util.Enumeration;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.TreeSet;
17 import java.util.stream.Collectors;
18
19 import org.apache.commons.io.IOUtils;
20 import org.osgi.framework.Bundle;
21 import org.osgi.framework.BundleContext;
22
23 /**
24 * Simplifies the theming of an app (only RAP is supported at this stage).<br>
25 *
26 * Additional fonts listed in <code>/fonts.txt</code>.<br>
27 * Additional (standard CSS) header in <code>/header.css</code>.<br>
28 * RAP specific CSS files in <code>/rap/*.css</code>.<br>
29 * All images added as additional resources based on extensions
30 * <code>/ ** /*.{png,gif,jpeg,...}</code>.<br>
31 */
32 public class BundleCmsTheme extends AbstractCmsTheme {
33 public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2";
34
35 public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
36 public final static String CMS_THEME_BUNDLE_PROPERTY = "argeo.cms.theme.bundle";
37
38 private final static String HEADER_CSS = "header.css";
39 private final static String FONTS_TXT = "fonts.txt";
40 private final static String BODY_HTML = "body.html";
41
42 // private final static Log log = LogFactory.getLog(BundleCmsTheme.class);
43
44 private String themeId;
45 private Set<String> webCssPaths = new TreeSet<>();
46 private Set<String> rapCssPaths = new TreeSet<>();
47 private Set<String> swtCssPaths = new TreeSet<>();
48 private Set<String> imagesPaths = new TreeSet<>();
49 private Set<String> fontsPaths = new TreeSet<>();
50
51 private String headerCss;
52 private List<String> fonts = new ArrayList<>();
53
54 private String bodyHtml="<body></body>";
55
56 private String basePath;
57 private String styleCssPath;
58 // private String webCssPath;
59 // private String rapCssPath;
60 // private String swtCssPath;
61 private Bundle themeBundle;
62
63 public BundleCmsTheme() {
64
65 }
66
67 public void init(BundleContext bundleContext, Map<String, String> properties) {
68 initResources(bundleContext, null);
69 }
70
71 public void destroy(BundleContext bundleContext, Map<String, String> properties) {
72
73 }
74
75 @Deprecated
76 public BundleCmsTheme(BundleContext bundleContext) {
77 this(bundleContext, null);
78 }
79
80 @Deprecated
81 public BundleCmsTheme(BundleContext bundleContext, String symbolicName) {
82 initResources(bundleContext, symbolicName);
83 }
84
85 private void initResources(BundleContext bundleContext, String symbolicName) {
86 if (symbolicName == null) {
87 themeBundle = bundleContext.getBundle();
88 // basePath = "/theme/";
89 // cssPath = basePath;
90 } else {
91 themeBundle = findThemeBundle(bundleContext, symbolicName);
92 }
93 basePath = "/";
94 styleCssPath = "/style/";
95 // webCssPath = "/css/";
96 // rapCssPath = "/rap/";
97 // swtCssPath = "/swt/";
98 // this.themeId = RWT.DEFAULT_THEME_ID;
99 this.themeId = themeBundle.getSymbolicName();
100 webCssPaths = addCss(themeBundle, "/css/");
101 rapCssPaths = addCss(themeBundle, "/rap/");
102 swtCssPaths = addCss(themeBundle, "/swt/");
103 addImages("*.png");
104 addImages("*.gif");
105 addImages("*.jpg");
106 addImages("*.jpeg");
107 addImages("*.svg");
108 addImages("*.ico");
109
110 addFonts("*.woff");
111 addFonts("*.woff2");
112
113 // fonts
114 URL fontsUrl = themeBundle.getEntry(basePath + FONTS_TXT);
115 if (fontsUrl != null) {
116 loadFontsUrl(fontsUrl);
117 }
118
119 // common CSS header (plain CSS)
120 URL headerCssUrl = themeBundle.getEntry(basePath + HEADER_CSS);
121 if (headerCssUrl != null) {
122 // added to plain Web CSS
123 webCssPaths.add(basePath + HEADER_CSS);
124 // and it will also be used by RAP:
125 try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) {
126 headerCss = buffer.lines().collect(Collectors.joining("\n"));
127 } catch (IOException e) {
128 throw new IllegalArgumentException("Cannot read " + headerCssUrl, e);
129 }
130 }
131
132 // body
133 URL bodyUrl = themeBundle.getEntry(basePath + BODY_HTML);
134 if (bodyUrl != null) {
135 loadBodyHtml(bodyUrl);
136 }
137 }
138
139 public String getHtmlHeaders() {
140 StringBuilder sb = new StringBuilder();
141 if (headerCss != null) {
142 sb.append("<style type='text/css'>\n");
143 sb.append(headerCss);
144 sb.append("\n</style>\n");
145 }
146 for (String link : fonts) {
147 sb.append("<link rel='stylesheet' href='");
148 sb.append(link);
149 sb.append("'/>\n");
150 }
151 if (sb.length() == 0)
152 return null;
153 else
154 return sb.toString();
155 }
156
157
158
159 @Override
160 public String getBodyHtml() {
161 return bodyHtml;
162 }
163
164 Set<String> addCss(Bundle themeBundle, String path) {
165 Set<String> paths = new TreeSet<>();
166
167 // common CSS
168 Enumeration<URL> commonResources = themeBundle.findEntries(styleCssPath, "*.css", true);
169 if (commonResources != null) {
170 while (commonResources.hasMoreElements()) {
171 String resource = commonResources.nextElement().getPath();
172 // remove first '/' so that RWT registers it
173 resource = resource.substring(1);
174 if (!resource.endsWith("/")) {
175 paths.add(resource);
176 }
177 }
178 }
179
180 // specific CSS
181 Enumeration<URL> themeResources = themeBundle.findEntries(path, "*.css", true);
182 if (themeResources != null) {
183 while (themeResources.hasMoreElements()) {
184 String resource = themeResources.nextElement().getPath();
185 // remove first '/' so that RWT registers it
186 resource = resource.substring(1);
187 if (!resource.endsWith("/")) {
188 paths.add(resource);
189 }
190 }
191 }
192 return paths;
193 }
194
195 void loadFontsUrl(URL url) {
196 try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
197 String line = null;
198 while ((line = in.readLine()) != null) {
199 line = line.trim();
200 if (!line.equals("") && !line.startsWith("#")) {
201 fonts.add(line);
202 }
203 }
204 } catch (IOException e) {
205 throw new IllegalArgumentException("Cannot load URL " + url, e);
206 }
207 }
208
209 void loadBodyHtml(URL url) {
210 try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
211 bodyHtml= IOUtils.toString(url,StandardCharsets.UTF_8);
212 } catch (IOException e) {
213 throw new IllegalArgumentException("Cannot load URL " + url, e);
214 }
215 }
216
217 void addImages(String pattern) {
218 Enumeration<URL> themeResources = themeBundle.findEntries(basePath, pattern, true);
219 if (themeResources == null)
220 return;
221 while (themeResources.hasMoreElements()) {
222 String resource = themeResources.nextElement().getPath();
223 // remove first '/' so that RWT registers it
224 resource = resource.substring(1);
225 if (!resource.endsWith("/")) {
226 // if (resources.containsKey(resource))
227 // log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
228 // resources.put(resource, themeBRL);
229 imagesPaths.add(resource);
230 }
231
232 }
233
234 }
235
236 void addFonts(String pattern) {
237 Enumeration<URL> themeResources = themeBundle.findEntries(basePath, pattern, true);
238 if (themeResources == null)
239 return;
240 while (themeResources.hasMoreElements()) {
241 String resource = themeResources.nextElement().getPath();
242 // remove first '/' so that RWT registers it
243 resource = resource.substring(1);
244 if (!resource.endsWith("/")) {
245 // if (resources.containsKey(resource))
246 // log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
247 // resources.put(resource, themeBRL);
248 fontsPaths.add(resource);
249 }
250
251 }
252
253 }
254
255 @Override
256 public InputStream getResourceAsStream(String resourceName) throws IOException {
257 URL res = themeBundle.getEntry(resourceName);
258 if (res == null) {
259 res = themeBundle.getResource(resourceName);
260 if (res == null)
261 return null;
262 // throw new IllegalArgumentException(
263 // "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
264 }
265 return res.openStream();
266 }
267
268 public String getThemeId() {
269 return themeId;
270 }
271
272 // public void setThemeId(String themeId) {
273 // this.themeId = themeId;
274 // }
275 //
276 // public String getBasePath() {
277 // return basePath;
278 // }
279 //
280 // public void setBasePath(String basePath) {
281 // this.basePath = basePath;
282 // }
283 //
284 // public String getRapCssPath() {
285 // return rapCssPath;
286 // }
287 //
288 // public void setRapCssPath(String cssPath) {
289 // this.rapCssPath = cssPath;
290 // }
291
292 @Override
293 public Set<String> getWebCssPaths() {
294 return webCssPaths;
295 }
296
297 @Override
298 public Set<String> getRapCssPaths() {
299 return rapCssPaths;
300 }
301
302 @Override
303 public Set<String> getSwtCssPaths() {
304 return swtCssPaths;
305 }
306
307 @Override
308 public Set<String> getImagesPaths() {
309 return imagesPaths;
310 }
311
312 @Override
313 public Set<String> getFontsPaths() {
314 return fontsPaths;
315 }
316
317 @Override
318 public InputStream loadPath(String path) throws IOException {
319 URL url = themeBundle.getResource(path);
320 if (url == null)
321 throw new IllegalArgumentException(
322 "Path " + path + " not found in bundle " + themeBundle.getSymbolicName());
323 return url.openStream();
324 }
325
326 private static Bundle findThemeBundle(BundleContext bundleContext, String themeId) {
327 if (themeId == null)
328 return null;
329 // TODO optimize
330 // TODO deal with multiple versions
331 Bundle themeBundle = null;
332 if (themeId != null) {
333 for (Bundle bundle : bundleContext.getBundles())
334 if (themeId.equals(bundle.getSymbolicName())) {
335 themeBundle = bundle;
336 break;
337 }
338 }
339 return themeBundle;
340 }
341
342 @Override
343 public int hashCode() {
344 return themeId.hashCode();
345 }
346
347 @Override
348 public String toString() {
349 return "Bundle CMS Theme " + themeId;
350 }
351
352 }