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
.List
;
16 import java
.util
.TreeSet
;
17 import java
.util
.stream
.Collectors
;
19 import org
.apache
.commons
.io
.IOUtils
;
20 import org
.argeo
.api
.cms
.CmsTheme
;
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";
37 public final static String CMS_THEME_BUNDLE_PROPERTY
= "argeo.cms.theme.bundle";
39 private final static String HEADER_CSS
= "header.css";
40 private final static String FONTS_TXT
= "fonts.txt";
41 private final static String BODY_HTML
= "body.html";
43 // private final static Log log = LogFactory.getLog(BundleCmsTheme.class);
45 private String themeId
;
46 private Set
<String
> webCssPaths
= new TreeSet
<>();
47 private Set
<String
> rapCssPaths
= new TreeSet
<>();
48 private Set
<String
> swtCssPaths
= new TreeSet
<>();
49 private Set
<String
> imagesPaths
= new TreeSet
<>();
50 private Set
<String
> fontsPaths
= new TreeSet
<>();
52 private String headerCss
;
53 private List
<String
> fonts
= new ArrayList
<>();
55 private String bodyHtml
="<body></body>";
57 private String basePath
;
58 private String styleCssPath
;
59 // private String webCssPath;
60 // private String rapCssPath;
61 // private String swtCssPath;
62 private Bundle themeBundle
;
64 private Integer defaultIconSize
= 16;
66 public BundleCmsTheme() {
70 public void init(BundleContext bundleContext
, Map
<String
, String
> properties
) {
71 initResources(bundleContext
, null);
74 public void destroy(BundleContext bundleContext
, Map
<String
, String
> properties
) {
79 public BundleCmsTheme(BundleContext bundleContext
) {
80 this(bundleContext
, null);
84 public BundleCmsTheme(BundleContext bundleContext
, String symbolicName
) {
85 initResources(bundleContext
, symbolicName
);
88 private void initResources(BundleContext bundleContext
, String symbolicName
) {
89 if (symbolicName
== null) {
90 themeBundle
= bundleContext
.getBundle();
91 // basePath = "/theme/";
92 // cssPath = basePath;
94 themeBundle
= findThemeBundle(bundleContext
, symbolicName
);
97 styleCssPath
= "/style/";
98 // webCssPath = "/css/";
99 // rapCssPath = "/rap/";
100 // swtCssPath = "/swt/";
101 // this.themeId = RWT.DEFAULT_THEME_ID;
102 this.themeId
= themeBundle
.getSymbolicName();
103 webCssPaths
= addCss(themeBundle
, "/css/");
104 rapCssPaths
= addCss(themeBundle
, "/rap/");
105 swtCssPaths
= addCss(themeBundle
, "/swt/");
117 URL fontsUrl
= themeBundle
.getEntry(basePath
+ FONTS_TXT
);
118 if (fontsUrl
!= null) {
119 loadFontsUrl(fontsUrl
);
122 // common CSS header (plain CSS)
123 URL headerCssUrl
= themeBundle
.getEntry(basePath
+ HEADER_CSS
);
124 if (headerCssUrl
!= null) {
125 // added to plain Web CSS
126 webCssPaths
.add(basePath
+ HEADER_CSS
);
127 // and it will also be used by RAP:
128 try (BufferedReader buffer
= new BufferedReader(new InputStreamReader(headerCssUrl
.openStream(), UTF_8
))) {
129 headerCss
= buffer
.lines().collect(Collectors
.joining("\n"));
130 } catch (IOException e
) {
131 throw new IllegalArgumentException("Cannot read " + headerCssUrl
, e
);
136 URL bodyUrl
= themeBundle
.getEntry(basePath
+ BODY_HTML
);
137 if (bodyUrl
!= null) {
138 loadBodyHtml(bodyUrl
);
142 public String
getHtmlHeaders() {
143 StringBuilder sb
= new StringBuilder();
144 if (headerCss
!= null) {
145 sb
.append("<style type='text/css'>\n");
146 sb
.append(headerCss
);
147 sb
.append("\n</style>\n");
149 for (String link
: fonts
) {
150 sb
.append("<link rel='stylesheet' href='");
154 if (sb
.length() == 0)
157 return sb
.toString();
163 public String
getBodyHtml() {
167 Set
<String
> addCss(Bundle themeBundle
, String path
) {
168 Set
<String
> paths
= new TreeSet
<>();
171 Enumeration
<URL
> commonResources
= themeBundle
.findEntries(styleCssPath
, "*.css", true);
172 if (commonResources
!= null) {
173 while (commonResources
.hasMoreElements()) {
174 String resource
= commonResources
.nextElement().getPath();
175 // remove first '/' so that RWT registers it
176 resource
= resource
.substring(1);
177 if (!resource
.endsWith("/")) {
184 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(path
, "*.css", true);
185 if (themeResources
!= null) {
186 while (themeResources
.hasMoreElements()) {
187 String resource
= themeResources
.nextElement().getPath();
188 // remove first '/' so that RWT registers it
189 resource
= resource
.substring(1);
190 if (!resource
.endsWith("/")) {
198 void loadFontsUrl(URL url
) {
199 try (BufferedReader in
= new BufferedReader(new InputStreamReader(url
.openStream(), UTF_8
))) {
201 while ((line
= in
.readLine()) != null) {
203 if (!line
.equals("") && !line
.startsWith("#")) {
207 } catch (IOException e
) {
208 throw new IllegalArgumentException("Cannot load URL " + url
, e
);
212 void loadBodyHtml(URL url
) {
213 try (BufferedReader in
= new BufferedReader(new InputStreamReader(url
.openStream(), UTF_8
))) {
214 bodyHtml
= IOUtils
.toString(url
,StandardCharsets
.UTF_8
);
215 } catch (IOException e
) {
216 throw new IllegalArgumentException("Cannot load URL " + url
, e
);
220 void addImages(String pattern
) {
221 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(basePath
, pattern
, true);
222 if (themeResources
== null)
224 while (themeResources
.hasMoreElements()) {
225 String resource
= themeResources
.nextElement().getPath();
226 // remove first '/' so that RWT registers it
227 resource
= resource
.substring(1);
228 if (!resource
.endsWith("/")) {
229 // if (resources.containsKey(resource))
230 // log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
231 // resources.put(resource, themeBRL);
232 imagesPaths
.add(resource
);
239 void addFonts(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 fontsPaths
.add(resource
);
259 public InputStream
getResourceAsStream(String resourceName
) throws IOException
{
260 URL res
= themeBundle
.getEntry(resourceName
);
262 res
= themeBundle
.getResource(resourceName
);
265 // throw new IllegalArgumentException(
266 // "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
268 return res
.openStream();
271 public String
getThemeId() {
275 // public void setThemeId(String themeId) {
276 // this.themeId = themeId;
279 // public String getBasePath() {
283 // public void setBasePath(String basePath) {
284 // this.basePath = basePath;
287 // public String getRapCssPath() {
288 // return rapCssPath;
291 // public void setRapCssPath(String cssPath) {
292 // this.rapCssPath = cssPath;
296 public Set
<String
> getWebCssPaths() {
301 public Set
<String
> getRapCssPaths() {
306 public Set
<String
> getSwtCssPaths() {
311 public Set
<String
> getImagesPaths() {
316 public Set
<String
> getFontsPaths() {
321 public Integer
getDefaultIconSize() {
322 return defaultIconSize
;
326 public InputStream
loadPath(String path
) throws IOException
{
327 URL url
= themeBundle
.getResource(path
);
329 throw new IllegalArgumentException(
330 "Path " + path
+ " not found in bundle " + themeBundle
.getSymbolicName());
331 return url
.openStream();
334 private static Bundle
findThemeBundle(BundleContext bundleContext
, String themeId
) {
338 // TODO deal with multiple versions
339 Bundle themeBundle
= null;
340 if (themeId
!= null) {
341 for (Bundle bundle
: bundleContext
.getBundles())
342 if (themeId
.equals(bundle
.getSymbolicName())) {
343 themeBundle
= bundle
;
351 public int hashCode() {
352 return themeId
.hashCode();
356 public String
toString() {
357 return "Bundle CMS Theme " + themeId
;