1 package org
.argeo
.cms
.osgi
;
3 import static java
.nio
.charset
.StandardCharsets
.UTF_8
;
5 import java
.io
.BufferedReader
;
6 import java
.io
.IOException
;
7 import java
.io
.InputStream
;
8 import java
.io
.InputStreamReader
;
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
;
17 import java
.util
.TreeSet
;
18 import java
.util
.stream
.Collectors
;
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
;
26 * Simplifies the theming of an app (only RAP is supported at this stage).<br>
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>
34 public class BundleCmsTheme
implements CmsTheme
{
35 public final static String DEFAULT_CMS_THEME_BUNDLE
= "org.argeo.theme.argeo2";
37 public final static String CMS_THEME_PROPERTY
= "argeo.cms.theme";
38 public final static String CMS_THEME_BUNDLE_PROPERTY
= "argeo.cms.theme.bundle";
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";
44 // private final static Log log = LogFactory.getLog(BundleCmsTheme.class);
46 private CmsTheme parentTheme
;
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
<>();
55 private String headerCss
;
56 private List
<String
> fonts
= new ArrayList
<>();
58 private String bodyHtml
= "<body></body>";
60 private String basePath
;
61 private String styleCssPath
;
62 // private String webCssPath;
63 // private String rapCssPath;
64 // private String swtCssPath;
65 private Bundle themeBundle
;
67 private Integer smallIconSize
= 16;
68 private Integer bigIconSize
= 32;
70 public BundleCmsTheme() {
74 public void init(BundleContext bundleContext
, Map
<String
, String
> properties
) {
75 initResources(bundleContext
, null);
78 public void destroy(BundleContext bundleContext
, Map
<String
, String
> properties
) {
83 public BundleCmsTheme(BundleContext bundleContext
) {
84 this(bundleContext
, null);
88 public BundleCmsTheme(BundleContext bundleContext
, String symbolicName
) {
89 initResources(bundleContext
, symbolicName
);
92 private void initResources(BundleContext bundleContext
, String symbolicName
) {
93 if (symbolicName
== null) {
94 themeBundle
= bundleContext
.getBundle();
95 // basePath = "/theme/";
96 // cssPath = basePath;
98 themeBundle
= findThemeBundle(bundleContext
, symbolicName
);
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/");
121 URL fontsUrl
= themeBundle
.getEntry(basePath
+ FONTS_TXT
);
122 if (fontsUrl
!= null) {
123 loadFontsUrl(fontsUrl
);
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
);
140 URL bodyUrl
= themeBundle
.getEntry(basePath
+ BODY_HTML
);
141 if (bodyUrl
!= null) {
142 loadBodyHtml(bodyUrl
);
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");
153 for (String link
: fonts
) {
154 sb
.append("<link rel='stylesheet' href='");
158 if (sb
.length() == 0)
161 return sb
.toString();
165 public String
getBodyHtml() {
169 Set
<String
> addCss(Bundle themeBundle
, String path
) {
170 Set
<String
> paths
= new TreeSet
<>();
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("/")) {
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("/")) {
200 void loadFontsUrl(URL url
) {
201 try (BufferedReader in
= new BufferedReader(new InputStreamReader(url
.openStream(), UTF_8
))) {
203 while ((line
= in
.readLine()) != null) {
205 if (!line
.equals("") && !line
.startsWith("#")) {
209 } catch (IOException e
) {
210 throw new IllegalArgumentException("Cannot load URL " + url
, e
);
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
);
222 void addImages(String pattern
) {
223 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(basePath
, pattern
, true);
224 if (themeResources
== null)
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
);
241 void addFonts(String pattern
) {
242 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(basePath
, pattern
, true);
243 if (themeResources
== null)
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
);
261 public InputStream
getResourceAsStream(String resourceName
) throws IOException
{
262 URL res
= themeBundle
.getEntry(resourceName
);
264 res
= themeBundle
.getResource(resourceName
);
266 if (parentTheme
== null)
268 return parentTheme
.getResourceAsStream(resourceName
);
271 return res
.openStream();
274 public String
getThemeId() {
279 public Set
<String
> getWebCssPaths() {
280 if (parentTheme
!= null) {
281 Set
<String
> res
= new HashSet
<>(parentTheme
.getWebCssPaths());
282 res
.addAll(webCssPaths
);
289 public Set
<String
> getRapCssPaths() {
290 if (parentTheme
!= null) {
291 Set
<String
> res
= new HashSet
<>(parentTheme
.getRapCssPaths());
292 res
.addAll(rapCssPaths
);
299 public Set
<String
> getSwtCssPaths() {
300 if (parentTheme
!= null) {
301 Set
<String
> res
= new HashSet
<>(parentTheme
.getSwtCssPaths());
302 res
.addAll(swtCssPaths
);
309 public Set
<String
> getImagesPaths() {
310 if (parentTheme
!= null) {
311 Set
<String
> res
= new HashSet
<>(parentTheme
.getImagesPaths());
312 res
.addAll(imagesPaths
);
319 public Set
<String
> getFontsPaths() {
324 public int getSmallIconSize() {
325 return smallIconSize
;
329 public int getBigIconSize() {
334 public InputStream
loadPath(String path
) throws IOException
{
335 URL url
= themeBundle
.getResource(path
);
337 if (parentTheme
!= null)
338 return parentTheme
.loadPath(path
);
340 throw new IllegalArgumentException(
341 "Path " + path
+ " not found in bundle " + themeBundle
.getSymbolicName());
343 return url
.openStream();
347 private static Bundle
findThemeBundle(BundleContext bundleContext
, String themeId
) {
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
;
364 public int hashCode() {
365 return themeId
.hashCode();
369 public String
toString() {
370 return "Bundle CMS Theme " + themeId
;
373 public void setParentTheme(CmsTheme parentTheme
) {
374 this.parentTheme
= parentTheme
;