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