1 package org
.argeo
.cms
.ui
.util
;
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
.List
;
15 import java
.util
.TreeSet
;
16 import java
.util
.stream
.Collectors
;
18 import org
.osgi
.framework
.Bundle
;
19 import org
.osgi
.framework
.BundleContext
;
22 * Simplifies the theming of an app (only RAP is supported at this stage).<br>
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>
30 public class BundleCmsTheme
extends AbstractCmsTheme
{
31 public final static String DEFAULT_CMS_THEME_BUNDLE
= "org.argeo.theme.argeo2";
33 public final static String CMS_THEME_PROPERTY
= "argeo.cms.theme";
34 public final static String CMS_THEME_BUNDLE_PROPERTY
= "argeo.cms.theme.bundle";
36 // private final static Log log = LogFactory.getLog(BundleCmsTheme.class);
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 private Set
<String
> fontsPaths
= new TreeSet
<>();
45 private String headerCss
;
46 private List
<String
> fonts
= new ArrayList
<>();
48 private String basePath
;
49 private String styleCssPath
;
50 // private String webCssPath;
51 // private String rapCssPath;
52 // private String swtCssPath;
53 private Bundle themeBundle
;
55 public BundleCmsTheme() {
59 public void init(BundleContext bundleContext
, Map
<String
, String
> properties
) {
60 initResources(bundleContext
, null);
63 public void destroy(BundleContext bundleContext
, Map
<String
, String
> properties
) {
68 public BundleCmsTheme(BundleContext bundleContext
) {
69 this(bundleContext
, null);
73 public BundleCmsTheme(BundleContext bundleContext
, String symbolicName
) {
74 initResources(bundleContext
, symbolicName
);
77 private void initResources(BundleContext bundleContext
, String symbolicName
) {
78 if (symbolicName
== null) {
79 themeBundle
= bundleContext
.getBundle();
80 // basePath = "/theme/";
81 // cssPath = basePath;
83 themeBundle
= findThemeBundle(bundleContext
, symbolicName
);
86 styleCssPath
= "/style/";
87 // webCssPath = "/css/";
88 // rapCssPath = "/rap/";
89 // swtCssPath = "/swt/";
90 // this.themeId = RWT.DEFAULT_THEME_ID;
91 this.themeId
= themeBundle
.getSymbolicName();
92 webCssPaths
= addCss(themeBundle
, "/css/");
93 rapCssPaths
= addCss(themeBundle
, "/rap/");
94 swtCssPaths
= addCss(themeBundle
, "/swt/");
106 URL fontsUrl
= themeBundle
.getEntry(basePath
+ "fonts.txt");
107 if (fontsUrl
!= null) {
108 loadFontsUrl(fontsUrl
);
111 // common CSS header (plain CSS)
112 URL headerCssUrl
= themeBundle
.getEntry(basePath
+ "header.css");
113 if (headerCssUrl
!= null) {
114 try (BufferedReader buffer
= new BufferedReader(new InputStreamReader(headerCssUrl
.openStream(), UTF_8
))) {
115 headerCss
= buffer
.lines().collect(Collectors
.joining("\n"));
116 } catch (IOException e
) {
117 throw new IllegalArgumentException("Cannot read " + headerCssUrl
, e
);
122 public String
getHtmlHeaders() {
123 StringBuilder sb
= new StringBuilder();
124 if (headerCss
!= null) {
125 sb
.append("<style type='text/css'>\n");
126 sb
.append(headerCss
);
127 sb
.append("\n</style>\n");
129 for (String link
: fonts
) {
130 sb
.append("<link rel='stylesheet' href='");
134 if (sb
.length() == 0)
137 return sb
.toString();
140 Set
<String
> addCss(Bundle themeBundle
, String path
) {
141 Set
<String
> paths
= new TreeSet
<>();
144 Enumeration
<URL
> commonResources
= themeBundle
.findEntries(styleCssPath
, "*.css", true);
145 if (commonResources
!= null) {
146 while (commonResources
.hasMoreElements()) {
147 String resource
= commonResources
.nextElement().getPath();
148 // remove first '/' so that RWT registers it
149 resource
= resource
.substring(1);
150 if (!resource
.endsWith("/")) {
157 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(path
, "*.css", true);
158 if (themeResources
!= null) {
159 while (themeResources
.hasMoreElements()) {
160 String resource
= themeResources
.nextElement().getPath();
161 // remove first '/' so that RWT registers it
162 resource
= resource
.substring(1);
163 if (!resource
.endsWith("/")) {
171 void loadFontsUrl(URL url
) {
172 try (BufferedReader in
= new BufferedReader(new InputStreamReader(url
.openStream(), UTF_8
))) {
174 while ((line
= in
.readLine()) != null) {
176 if (!line
.equals("") && !line
.startsWith("#")) {
180 } catch (IOException e
) {
181 throw new IllegalArgumentException("Cannot load URL " + url
, e
);
185 void addImages(String pattern
) {
186 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(basePath
, pattern
, true);
187 if (themeResources
== null)
189 while (themeResources
.hasMoreElements()) {
190 String resource
= themeResources
.nextElement().getPath();
191 // remove first '/' so that RWT registers it
192 resource
= resource
.substring(1);
193 if (!resource
.endsWith("/")) {
194 // if (resources.containsKey(resource))
195 // log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
196 // resources.put(resource, themeBRL);
197 imagesPaths
.add(resource
);
204 void addFonts(String pattern
) {
205 Enumeration
<URL
> themeResources
= themeBundle
.findEntries(basePath
, pattern
, true);
206 if (themeResources
== null)
208 while (themeResources
.hasMoreElements()) {
209 String resource
= themeResources
.nextElement().getPath();
210 // remove first '/' so that RWT registers it
211 resource
= resource
.substring(1);
212 if (!resource
.endsWith("/")) {
213 // if (resources.containsKey(resource))
214 // log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
215 // resources.put(resource, themeBRL);
216 fontsPaths
.add(resource
);
224 public InputStream
getResourceAsStream(String resourceName
) throws IOException
{
225 URL res
= themeBundle
.getEntry(resourceName
);
227 res
= themeBundle
.getResource(resourceName
);
230 // throw new IllegalArgumentException(
231 // "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
233 return res
.openStream();
236 public String
getThemeId() {
240 // public void setThemeId(String themeId) {
241 // this.themeId = themeId;
244 // public String getBasePath() {
248 // public void setBasePath(String basePath) {
249 // this.basePath = basePath;
252 // public String getRapCssPath() {
253 // return rapCssPath;
256 // public void setRapCssPath(String cssPath) {
257 // this.rapCssPath = cssPath;
261 public Set
<String
> getWebCssPaths() {
266 public Set
<String
> getRapCssPaths() {
271 public Set
<String
> getSwtCssPaths() {
276 public Set
<String
> getImagesPaths() {
281 public Set
<String
> getFontsPaths() {
286 public InputStream
loadPath(String path
) throws IOException
{
287 URL url
= themeBundle
.getResource(path
);
289 throw new IllegalArgumentException(
290 "Path " + path
+ " not found in bundle " + themeBundle
.getSymbolicName());
291 return url
.openStream();
294 private static Bundle
findThemeBundle(BundleContext bundleContext
, String themeId
) {
298 // TODO deal with multiple versions
299 Bundle themeBundle
= null;
300 if (themeId
!= null) {
301 for (Bundle bundle
: bundleContext
.getBundles())
302 if (themeId
.equals(bundle
.getSymbolicName())) {
303 themeBundle
= bundle
;
311 public int hashCode() {
312 return themeId
.hashCode();
316 public String
toString() {
317 return "Bundle CMS Theme " + themeId
;