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
.util
.ArrayList
;
11 import java
.util
.Enumeration
;
12 import java
.util
.HashSet
;
13 import java
.util
.List
;
16 import java
.util
.TreeSet
;
17 import java
.util
.stream
.Collectors
;
19 import org
.argeo
.api
.cms
.ux
.CmsTheme
;
20 import org
.argeo
.cms
.util
.StreamUtils
;
21 import org
.osgi
.framework
.Bundle
;
22 import org
.osgi
.framework
.BundleContext
;
25 * Simplifies the theming of an app (only RAP is supported at this stage).<br>
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>
33 public class BundleCmsTheme
implements CmsTheme
{
34 // public final static String DEFAULT_CMS_THEME_BUNDLE = "org.argeo.theme.argeo2";
36 // public final static String CMS_THEME_PROPERTY = "argeo.cms.theme";
38 public final static String CMS_THEME_BUNDLE_PROPERTY
= "argeo.cms.theme.bundle";
40 /** Declared theme ID, to be used by OSGi services to reference it as parent. */
41 public final static String THEME_ID_PROPERTY
= "themeId";
42 public final static String SMALL_ICON_SIZE_PROPERTY
= "smallIconSize";
43 public final static String BIG_ICON_SIZE_PROPERTY
= "bigIconSize";
45 private final static String HEADER_CSS
= "header.css";
46 private final static String FONTS_TXT
= "fonts.txt";
47 private final static String BODY_HTML
= "body.html";
49 // private final static Log log = LogFactory.getLog(BundleCmsTheme.class);
51 private CmsTheme parentTheme
;
53 private String themeId
;
54 private String declaredThemeId
;;
56 private Set
<String
> webCssPaths
= new TreeSet
<>();
57 private Set
<String
> rapCssPaths
= new TreeSet
<>();
58 private Set
<String
> swtCssPaths
= new TreeSet
<>();
59 private Set
<String
> imagesPaths
= new TreeSet
<>();
60 private Set
<String
> fontsPaths
= new TreeSet
<>();
62 private String headerCss
;
63 private List
<String
> fonts
= new ArrayList
<>();
65 private String bodyHtml
= "<body></body>";
67 private String basePath
;
68 private String styleCssPath
;
69 // private String webCssPath;
70 // private String rapCssPath;
71 // private String swtCssPath;
72 private Bundle themeBundle
;
74 private Integer smallIconSize
= 16;
75 private Integer bigIconSize
= 32;
77 public BundleCmsTheme() {
81 public void init(BundleContext bundleContext
, Map
<String
, String
> properties
) {
82 declaredThemeId
= properties
.get(THEME_ID_PROPERTY
);
83 if (properties
.containsKey(SMALL_ICON_SIZE_PROPERTY
))
84 smallIconSize
= Integer
.valueOf(properties
.get(SMALL_ICON_SIZE_PROPERTY
));
85 if (properties
.containsKey(BIG_ICON_SIZE_PROPERTY
))
86 smallIconSize
= Integer
.valueOf(properties
.get(BIG_ICON_SIZE_PROPERTY
));
88 initResources(bundleContext
, null);
91 public void destroy(BundleContext bundleContext
, Map
<String
, String
> properties
) {
96 public BundleCmsTheme(BundleContext bundleContext
) {
97 this(bundleContext
, null);
101 public BundleCmsTheme(BundleContext bundleContext
, String symbolicName
) {
102 initResources(bundleContext
, symbolicName
);
105 private void initResources(BundleContext bundleContext
, String symbolicName
) {
106 if (symbolicName
== null) {
107 themeBundle
= bundleContext
.getBundle();
108 // basePath = "/theme/";
109 // cssPath = basePath;
111 themeBundle
= findThemeBundle(bundleContext
, symbolicName
);
114 styleCssPath
= "/style/";
115 // webCssPath = "/css/";
116 // rapCssPath = "/rap/";
117 // swtCssPath = "/swt/";
118 // this.themeId = RWT.DEFAULT_THEME_ID;
119 this.themeId
= themeBundle
.getSymbolicName();
120 if (declaredThemeId
!= null && !declaredThemeId
.equals(themeId
))
121 throw new IllegalArgumentException(
122 "Declared theme id " + declaredThemeId
+ " is different from " + themeId
);
124 webCssPaths
= addCss(themeBundle
, "/css/");
125 rapCssPaths
= addCss(themeBundle
, "/rap/");
126 swtCssPaths
= addCss(themeBundle
, "/swt/");
138 URL fontsUrl
= themeBundle
.getEntry(basePath
+ FONTS_TXT
);
139 if (fontsUrl
!= null) {
140 loadFontsUrl(fontsUrl
);
143 // common CSS header (plain CSS)
144 URL headerCssUrl
= themeBundle
.getEntry(basePath
+ HEADER_CSS
);
145 if (headerCssUrl
!= null) {
146 // added to plain Web CSS
147 webCssPaths
.add(basePath
+ HEADER_CSS
);
148 // and it will also be used by RAP:
149 try (BufferedReader buffer
= new BufferedReader(new InputStreamReader(headerCssUrl
.openStream(), UTF_8
))) {
150 headerCss
= buffer
.lines().collect(Collectors
.joining("\n"));
151 } catch (IOException e
) {
152 throw new IllegalArgumentException("Cannot read " + headerCssUrl
, e
);
157 URL bodyUrl
= themeBundle
.getEntry(basePath
+ BODY_HTML
);
158 if (bodyUrl
!= null) {
159 loadBodyHtml(bodyUrl
);
163 public String
getHtmlHeaders() {
164 StringBuilder sb
= new StringBuilder();
165 if (headerCss
!= null) {
166 sb
.append("<style type='text/css'>\n");
167 sb
.append(headerCss
);
168 sb
.append("\n</style>\n");
170 for (String link
: fonts
) {
171 sb
.append("<link rel='stylesheet' href='");
175 if (sb
.length() == 0)
178 return sb
.toString();
182 public String
getBodyHtml() {
186 Set
<String
> addCss(Bundle themeBundle
, String path
) {
187 Set
<String
> paths
= new TreeSet
<>();
190 Enumeration
<URL
> commonResources
= themeBundle
.findEntries(styleCssPath
, "*.css", true);
191 if (commonResources
!= null) {
192 while (commonResources
.hasMoreElements()) {
193 String resource
= commonResources
.nextElement().getPath();
194 // remove first '/' so that RWT registers it
195 resource
= resource
.substring(1);
196 if (!resource
.endsWith("/")) {
203 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(path
, "*.css", true);
204 if (themeResources
!= null) {
205 while (themeResources
.hasMoreElements()) {
206 String resource
= themeResources
.nextElement().getPath();
207 // remove first '/' so that RWT registers it
208 resource
= resource
.substring(1);
209 if (!resource
.endsWith("/")) {
217 void loadFontsUrl(URL url
) {
218 try (BufferedReader in
= new BufferedReader(new InputStreamReader(url
.openStream(), UTF_8
))) {
220 while ((line
= in
.readLine()) != null) {
222 if (!line
.equals("") && !line
.startsWith("#")) {
226 } catch (IOException e
) {
227 throw new IllegalArgumentException("Cannot load URL " + url
, e
);
231 void loadBodyHtml(URL url
) {
232 try (BufferedReader in
= new BufferedReader(new InputStreamReader(url
.openStream(), UTF_8
))) {
233 bodyHtml
= StreamUtils
.toString(in
);
234 } catch (IOException e
) {
235 throw new IllegalArgumentException("Cannot load URL " + url
, e
);
239 void addImages(String pattern
) {
240 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(basePath
, pattern
, true);
241 if (themeResources
== null)
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 imagesPaths
.add(resource
);
258 void addFonts(String pattern
) {
259 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(basePath
, pattern
, true);
260 if (themeResources
== null)
262 while (themeResources
.hasMoreElements()) {
263 String resource
= themeResources
.nextElement().getPath();
264 // remove first '/' so that RWT registers it
265 resource
= resource
.substring(1);
266 if (!resource
.endsWith("/")) {
267 // if (resources.containsKey(resource))
268 // log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
269 // resources.put(resource, themeBRL);
270 fontsPaths
.add(resource
);
278 public InputStream
getResourceAsStream(String resourceName
) throws IOException
{
279 URL res
= themeBundle
.getEntry(resourceName
);
281 res
= themeBundle
.getResource(resourceName
);
283 if (parentTheme
== null)
285 return parentTheme
.getResourceAsStream(resourceName
);
288 return res
.openStream();
291 public String
getThemeId() {
296 public Set
<String
> getWebCssPaths() {
297 if (parentTheme
!= null) {
298 Set
<String
> res
= new HashSet
<>(parentTheme
.getWebCssPaths());
299 res
.addAll(webCssPaths
);
306 public Set
<String
> getRapCssPaths() {
307 if (parentTheme
!= null) {
308 Set
<String
> res
= new HashSet
<>(parentTheme
.getRapCssPaths());
309 res
.addAll(rapCssPaths
);
316 public Set
<String
> getSwtCssPaths() {
317 if (parentTheme
!= null) {
318 Set
<String
> res
= new HashSet
<>(parentTheme
.getSwtCssPaths());
319 res
.addAll(swtCssPaths
);
326 public Set
<String
> getImagesPaths() {
327 if (parentTheme
!= null) {
328 Set
<String
> res
= new HashSet
<>(parentTheme
.getImagesPaths());
329 res
.addAll(imagesPaths
);
336 public Set
<String
> getFontsPaths() {
341 public int getSmallIconSize() {
342 return smallIconSize
;
346 public int getBigIconSize() {
351 public InputStream
loadPath(String path
) throws IOException
{
352 URL url
= themeBundle
.getResource(path
);
354 if (parentTheme
!= null)
355 return parentTheme
.loadPath(path
);
357 throw new IllegalArgumentException(
358 "Path " + path
+ " not found in bundle " + themeBundle
.getSymbolicName());
360 return url
.openStream();
364 private static Bundle
findThemeBundle(BundleContext bundleContext
, String themeId
) {
368 // TODO deal with multiple versions
369 Bundle themeBundle
= null;
370 if (themeId
!= null) {
371 for (Bundle bundle
: bundleContext
.getBundles())
372 if (themeId
.equals(bundle
.getSymbolicName())) {
373 themeBundle
= bundle
;
381 public int hashCode() {
382 return themeId
.hashCode();
386 public String
toString() {
387 return "Bundle CMS Theme " + themeId
;
390 public void setParentTheme(CmsTheme parentTheme
) {
391 this.parentTheme
= parentTheme
;
394 public void setSmallIconSize(Integer smallIconSize
) {
395 this.smallIconSize
= smallIconSize
;
398 public void setBigIconSize(Integer bigIconSize
) {
399 this.bigIconSize
= bigIconSize
;