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