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