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