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