diff --git a/api/pom.xml b/api/pom.xml index 427d407..787a251 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -1,133 +1,139 @@ - - - 4.0.0 - - - seam-faces-parent - org.jboss.seam.faces - 3.2.0-SNAPSHOT - ../pom.xml - - - seam-faces-api - - jar - Seam Faces API - - ${project.parent.url} - - - - org.jboss.solder - solder-api - - - org.jboss.seam.security - seam-security-api - provided - - - - - com.sun.faces - jsf-api - 2.0.4-b07 - provided - - - javax.enterprise - cdi-api - provided - - - org.jboss.spec.javax.el - jboss-el-api_2.2_spec - provided - - - org.jboss.spec.javax.annotation - jboss-annotations-api_1.1_spec - provided - - - org.jboss.spec.javax.interceptor - jboss-interceptors-api_1.1_spec - provided - - - javax.validation - validation-api - provided - - - com.ocpsoft - prettyfaces-jsf2 - provided - true - - - - - - - - maven-surefire-plugin - - true - - - - - - - - distribution - - - release - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - false - - - - - - - - - code-coverage - - - - org.codehaus.mojo - emma-maven-plugin - - - org.apache.maven.plugins - maven-surefire-plugin - - - org.sonatype.maven.plugin - emma4it-maven-plugin - - - - - - - + + + 4.0.0 + + + seam-faces-parent + org.jboss.seam.faces + 3.2.0-SNAPSHOT + ../pom.xml + + + seam-faces-api + + jar + Seam Faces API + + ${project.parent.url} + + + + org.jboss.solder + solder-api + + + org.jboss.seam.security + seam-security-api + provided + + + + + com.sun.faces + jsf-api + 2.0.4-b07 + provided + + + javax.enterprise + cdi-api + provided + + + org.jboss.spec.javax.el + jboss-el-api_2.2_spec + provided + + + org.jboss.spec.javax.annotation + jboss-annotations-api_1.1_spec + provided + + + org.jboss.spec.javax.interceptor + jboss-interceptors-api_1.1_spec + provided + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.0_spec + provided + + + javax.validation + validation-api + provided + + + com.ocpsoft + prettyfaces-jsf2 + provided + true + + + + + + + + maven-surefire-plugin + + true + + + + + + + + distribution + + + release + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + false + + + + + + + + + code-coverage + + + + org.codehaus.mojo + emma-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.sonatype.maven.plugin + emma4it-maven-plugin + + + + + + + diff --git a/api/src/main/java/org/jboss/seam/faces/theme/Theme.java b/api/src/main/java/org/jboss/seam/faces/theme/Theme.java new file mode 100644 index 0000000..c3250cb --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/theme/Theme.java @@ -0,0 +1,14 @@ +package org.jboss.seam.faces.theme; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) +public @interface Theme { +} diff --git a/api/src/main/java/org/jboss/seam/faces/theme/ThemeChangedEvent.java b/api/src/main/java/org/jboss/seam/faces/theme/ThemeChangedEvent.java new file mode 100644 index 0000000..35e273f --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/theme/ThemeChangedEvent.java @@ -0,0 +1,13 @@ +package org.jboss.seam.faces.theme; + +public class ThemeChangedEvent { + private String themeName; + + public ThemeChangedEvent(String themeName) { + this.themeName = themeName; + } + + public String getThemeName() { + return themeName; + } +} diff --git a/api/src/main/java/org/jboss/seam/faces/theme/ThemeConfig.java b/api/src/main/java/org/jboss/seam/faces/theme/ThemeConfig.java new file mode 100644 index 0000000..963fb7b --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/theme/ThemeConfig.java @@ -0,0 +1,55 @@ +package org.jboss.seam.faces.theme; + +import javax.enterprise.context.ApplicationScoped; + + +@ApplicationScoped +public class ThemeConfig { + + private String[] availableThemes; + public static final int DEFAULT_MAX_AGE = 31536000; // 1 year in seconds + private boolean cookieEnabled = true; + private int cookieMaxAge = DEFAULT_MAX_AGE; + private String cookiePath = "/"; + private String cookieName = "org.jboss.seam.core.Theme"; + + public boolean isCookieEnabled() { + return cookieEnabled; + } + + public void setCookieEnabled(boolean cookieEnabled) { + this.cookieEnabled = cookieEnabled; + } + + public int getCookieMaxAge() { + return cookieMaxAge; + } + + public void setCookieMaxAge(int cookieMaxAge) { + this.cookieMaxAge = cookieMaxAge; + } + + public String getCookiePath() { + return cookiePath; + } + + public void setCookiePath(String cookiePath) { + this.cookiePath = cookiePath; + } + + public String getCookieName() { + return cookieName; + } + + public void setCookieName(String cookieName) { + this.cookieName = cookieName; + } + + public String[] getAvailableThemes() { + return availableThemes; + } + + public void setAvailableThemes(String[] availableThemes) { + this.availableThemes = availableThemes; + } +} \ No newline at end of file diff --git a/api/src/main/java/org/jboss/seam/faces/theme/ThemeSelector.java b/api/src/main/java/org/jboss/seam/faces/theme/ThemeSelector.java new file mode 100644 index 0000000..673e2e2 --- /dev/null +++ b/api/src/main/java/org/jboss/seam/faces/theme/ThemeSelector.java @@ -0,0 +1,289 @@ +package org.jboss.seam.faces.theme; + +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.context.SessionScoped; +import javax.enterprise.event.Event; +import javax.enterprise.inject.Produces; +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; +import javax.faces.event.ValueChangeEvent; +import javax.faces.model.SelectItem; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.jboss.solder.logging.Logger; + +/** + * Selects the current user's theme + */ +@SessionScoped +@Named("themeSelector") +public class ThemeSelector implements Serializable { + + private static final long serialVersionUID = 3920407140011388341L; + private transient final Logger log = Logger.getLogger(ThemeSelector.class); + private String theme; + + @Inject + private ThemeConfig themeConfig; + + @Inject + private Event event; + + private boolean cookieEnabled; + private int cookieMaxAge; + private String cookiePath; + private String cookieName; + private String[] availableThemes; + + @PostConstruct + public void initDefaultTheme() { + initParams(); + String themeName = getCookieValueIfEnabled(); + if (isThemeAvailable(themeName)) { + setTheme(themeName); + } else { + theme = availableThemes[0]; + } + } + + public void selectTheme(String themeName) { + removeFromAllContexts("theme"); + setTheme(themeName); + setCookieValueIfEnabled(themeName); + raiseThemeEvent(themeName); + } + + public void select(ValueChangeEvent event) { + selectTheme((String) event.getNewValue()); + } + + public void select() { + if (theme == null) { + throw new IllegalStateException("no themes defined"); + } + selectTheme(theme); + } + + public List getThemes() { + List selectItems = new ArrayList(availableThemes.length); + for (String name : availableThemes) { + selectItems.add(new SelectItem(name, getLocalizedThemeName(name))); + } + return selectItems; + } + + @Produces + @RequestScoped + @Named("theme") + @Theme + public Map getThemeMap() { + return createMap(); + } + + /** + * Get the name of the current theme + */ + public String getTheme() { + return theme; + } + + public void setTheme(String themeName) { + this.theme = themeName; + } + + private void initParams() { + cookieEnabled = themeConfig.isCookieEnabled(); + cookieMaxAge = themeConfig.getCookieMaxAge(); + cookiePath = themeConfig.getCookiePath(); + cookieName = themeConfig.getCookieName(); + availableThemes = themeConfig.getAvailableThemes(); + if (availableThemes == null || availableThemes.length == 0) { + throw new IllegalStateException("no themes defined"); + } + } + + private void setCookieValueIfEnabled(String value) { + FacesContext ctx = FacesContext.getCurrentInstance(); + if (cookieEnabled && ctx != null) { + HttpServletResponse response = (HttpServletResponse) ctx.getExternalContext().getResponse(); + Cookie cookie = new Cookie(cookieName, value); + cookie.setMaxAge(cookieMaxAge); + cookie.setPath(cookiePath); + response.addCookie(cookie); + } + } + + private String getCookieValueIfEnabled() { + return cookieEnabled ? getCookieValue() : null; + } + + private Cookie getCookie() { + FacesContext ctx = FacesContext.getCurrentInstance(); + if (ctx != null) { + return (Cookie) ctx.getExternalContext().getRequestCookieMap().get(themeConfig.getCookieName()); + } else { + return null; + } + } + + private String getCookieValue() { + Cookie cookie = getCookie(); + return cookie == null ? null : cookie.getValue(); + } + + private void raiseThemeEvent(String themeName) { + event.fire(new ThemeChangedEvent(themeName)); + } + + private boolean isThemeAvailable(String themeName) { + return (themeName != null && Arrays.asList(availableThemes).contains(themeName)); + } + + private void removeFromAllContexts(String nameTheme) { + // TODO : this method should scan all CDI scopes + // but don't know how to achieve it + ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext(); + externalContext.getRequestMap().remove(nameTheme); + externalContext.getSessionMap().remove(nameTheme); + externalContext.getApplicationMap().remove(nameTheme); + } + + protected Map createMap() { + final ResourceBundle bundle = getThemeResourceBundle(); + if (bundle == null) { + return null; + } + return new AbstractMap() { + @Override + public String get(Object key) { + if (key instanceof String) { + String resourceKey = (String) key; + String resource; + try { + resource = bundle.getString(resourceKey); + } catch (MissingResourceException mre) { + return resourceKey; + } + if (resource == null) { + return resourceKey; + } else { + return resource; + } + } else { + return null; + } + } + + @Override + public Set> entrySet() { + Set> entrySet = new HashSet>(); + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + final String key = keys.nextElement(); + entrySet.add(new Entry() { + + public String getKey() { + return key; + } + + public String getValue() { + return get(key); + } + + public String setValue(String arg0) { + throw new UnsupportedOperationException("not implemented"); + } + }); + } + return entrySet; + } + }; + } + + /** + * Get the localized name of the named theme, by looking for + * org.jboss.seam.theme.<name> in the JSF messageBundle + * bundle + */ + public String getLocalizedThemeName(String name) { + + String key = "org.jboss.seam.theme." + name; + FacesContext facesContext = FacesContext.getCurrentInstance(); + String messageBundle = facesContext.getApplication().getMessageBundle(); + if (messageBundle != null) { + Locale locale = facesContext.getViewRoot().getLocale(); + try { + ResourceBundle resourceBundle = ResourceBundle.getBundle(messageBundle, locale); + return resourceBundle.getString(key); + } catch (MissingResourceException e) { + log.debugf(e, "Error loading resourceBundle {0} or loading key {1}", messageBundle, key); + } + } + return key; + } + + /** + * Get the resource bundle for the theme + */ + public ResourceBundle getThemeResourceBundle() { + try { + ResourceBundle bundle = ResourceBundle.getBundle( + theme, + FacesContext.getCurrentInstance().getViewRoot().getLocale(), + Thread.currentThread().getContextClassLoader() + ); + log.trace("loaded resource bundle: " + theme); + return bundle; + + } catch (MissingResourceException mre) { + log.debug("resource bundle missing: " + theme); + return new ResourceBundle() { + + @Override + public Enumeration getKeys() { + return new IteratorEnumeration(Collections.EMPTY_LIST.iterator()); + } + + @Override + protected Object handleGetObject(String key) { + return null; + } + }; + } + } + + private static class IteratorEnumeration implements Enumeration { + private Iterator iterator; + + public IteratorEnumeration(Iterator iterator) { + this.iterator = iterator; + } + + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + public Object nextElement() { + return iterator.next(); + } + } +} \ No newline at end of file diff --git a/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppStartupConfig.java b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppStartupConfig.java new file mode 100644 index 0000000..9d1c3c9 --- /dev/null +++ b/examples/viewconfig/src/main/java/org/jboss/seam/faces/examples/viewconfig/MyAppStartupConfig.java @@ -0,0 +1,21 @@ +package org.jboss.seam.faces.examples.viewconfig; + +import javax.annotation.PostConstruct; +import javax.ejb.Singleton; +import javax.ejb.Startup; +import javax.inject.Inject; + +import org.jboss.seam.faces.theme.ThemeConfig; + +@Singleton +@Startup +public class MyAppStartupConfig { + + @Inject + private ThemeConfig themeConfig; + + @PostConstruct + public void onLoad() { + themeConfig.setAvailableThemes(new String[]{"blue", "red", "black"}); + } +} diff --git a/examples/viewconfig/src/main/resources/black.properties b/examples/viewconfig/src/main/resources/black.properties new file mode 100644 index 0000000..ebbb7fc --- /dev/null +++ b/examples/viewconfig/src/main/resources/black.properties @@ -0,0 +1 @@ +borderColor=black \ No newline at end of file diff --git a/examples/viewconfig/src/main/resources/blue.properties b/examples/viewconfig/src/main/resources/blue.properties new file mode 100644 index 0000000..dfd06b0 --- /dev/null +++ b/examples/viewconfig/src/main/resources/blue.properties @@ -0,0 +1 @@ +borderColor=blue \ No newline at end of file diff --git a/examples/viewconfig/src/main/resources/messages.properties b/examples/viewconfig/src/main/resources/messages.properties new file mode 100644 index 0000000..7079ddb --- /dev/null +++ b/examples/viewconfig/src/main/resources/messages.properties @@ -0,0 +1,3 @@ +org.jboss.seam.theme.blue=blue +org.jboss.seam.theme.red=red +org.jboss.seam.theme.black=black diff --git a/examples/viewconfig/src/main/resources/red.properties b/examples/viewconfig/src/main/resources/red.properties new file mode 100644 index 0000000..d32d7f7 --- /dev/null +++ b/examples/viewconfig/src/main/resources/red.properties @@ -0,0 +1 @@ +borderColor=red \ No newline at end of file diff --git a/examples/viewconfig/src/main/webapp/WEB-INF/faces-config.xml b/examples/viewconfig/src/main/webapp/WEB-INF/faces-config.xml index 5334c1d..80009c9 100644 --- a/examples/viewconfig/src/main/webapp/WEB-INF/faces-config.xml +++ b/examples/viewconfig/src/main/webapp/WEB-INF/faces-config.xml @@ -1,7 +1,13 @@ - - - + + + + messages + + messages + messages + + \ No newline at end of file diff --git a/examples/viewconfig/src/main/webapp/index.xhtml b/examples/viewconfig/src/main/webapp/index.xhtml index 7407433..d4582e9 100644 --- a/examples/viewconfig/src/main/webapp/index.xhtml +++ b/examples/viewconfig/src/main/webapp/index.xhtml @@ -1,27 +1,30 @@ - - - - - - - Welcome - -

This example demonstrates how to secure pages using the @ViewConfig annotation

- -
    -
  • - -
  • -
  • - -
  • -
-
-
-
- + + + + + + + Welcome + +

This example demonstrates how to secure pages using the @ViewConfig annotation

+ +
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+
+
+ diff --git a/examples/viewconfig/src/main/webapp/resources/layout.xhtml b/examples/viewconfig/src/main/webapp/resources/layout.xhtml index e64c7bf..a96760d 100644 --- a/examples/viewconfig/src/main/webapp/resources/layout.xhtml +++ b/examples/viewconfig/src/main/webapp/resources/layout.xhtml @@ -1,29 +1,29 @@ - - - - - - - - <ui:insert name="title">Sample</ui:insert> - | ViewConfig Sample App - - - -
- -
- - - -
-
- -
-
-
- + + + + + + + + <ui:insert name="title">Sample</ui:insert> + | ViewConfig Sample App + + + +
+ +
+ + + +
+
+ +
+
+
+ diff --git a/examples/viewconfig/src/main/webapp/theme.xhtml b/examples/viewconfig/src/main/webapp/theme.xhtml new file mode 100644 index 0000000..6fa318a --- /dev/null +++ b/examples/viewconfig/src/main/webapp/theme.xhtml @@ -0,0 +1,26 @@ + + + + + + + Bienvenue + +

Please select your favorite color

+ +
+
Current theme : #{themeSelector.theme}
+ + + + +
+ +
+
+
+