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