[jboss-cvs] jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences ...

Christian Bauer christian at hibernate.org
Mon Apr 2 14:25:06 EDT 2007


  User: cbauer  
  Date: 07/04/02 14:25:06

  Added:       examples/wiki/src/main/org/jboss/seam/wiki/preferences        
                        PreferenceProvider.java PreferenceProperty.java
                        Preference.java PreferenceRegistry.java
                        PreferenceSupport.java PreferenceVisibility.java
                        PreferenceComponent.java PreferenceValue.java
  Log:
  Totally overengineered but definitely cool system/user/instance wiki preferences architecture
  
  Revision  Changes    Path
  1.1      date: 2007/04/02 18:25:05;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences/PreferenceProvider.java
  
  Index: PreferenceProvider.java
  ===================================================================
  package org.jboss.seam.wiki.preferences;
  
  import java.util.Set;
  
  /**
   * Interface for loading and storing of preference values.
   * <p>
   * Implement this interface to load and store preference values grouped by "preference component". Expose
   * your implementation as an auto-created Seam component with the name <tt>preferenceProvider</tt>.
   * <p>
   *
   * @author Christian Bauer
   */
  public interface PreferenceProvider {
  
      /**
       * Load preference values for a particular component.
       * <p>
       * The <tt>user</tt> and <tt>instance</tt> arguments can be null, in that case, you need to only load system
       * preference values (see <tt>PreferenceVisibility</tt>) for that component. If <tt>user</tt> and <tt>instance</tt>
       * are provided, you can return a combination of values that represent the override. E.g. if the component has
       * preference properties foo1, foo2, and foo3, and foo2 allows user override while foo3 allows instance override,
       * you can return three values which you looked up accordingly.
       * <p>
       * If <tt>includeSystemPreferences</tt> is true, the provider should return all system-level preference values even
       * if they do not allow user and instance override. If this argument is false, the provider should not return any
       * system-level preference values, unless these propertis allow override on the user and instance level. To understand
       * this, think about the two use cases how preference values are loaded: We need the "current" preference values, optionally
       * resolved against the given user and instance. We also need the "current" preference values for editing, in that case however,
       * we don't want to see any values that can't be edited.
       *
       * @param component the preference component meta data, read this to know what to load
       * @param user an optional (nullable) user argument useful for override lookup
       * @param instance an optional (nullable) instance argument useful for override lookup
       * @param includeSystemPreferences true if the provider should load
       * @return a set of <tt>PreferenceValue</tt> objects, can be a sorted set
       */
      public Set<PreferenceValue> load(PreferenceComponent component, Object user, Object instance, boolean includeSystemPreferences);
  
      /**
       * Store preference values for particular component.
       * <p>
       * This method should not directly and immediately store the preference values, but queue them in some way.
       * They should only be flushed to a permanent data store when <tt>flush</tt> is called.
       * 
       * @param component the preference component metadata for which the values should be stored
       * @param valueHolders the values to store, wrapped in the <tt>PreferenceValue</tt> interface
       * @param user an optional (nullable) user argument that can be used to convert the value holders before storing
       * @param instance an optional (nullable) instance argument that can be used to convert the value holders before storing
       * @return an updated set of <tt>PreferenceValue</tt> objects, if some value holders were converted
       */
      public Set<PreferenceValue> store(PreferenceComponent component, Set<PreferenceValue> valueHolders, Object user, Object instance);
  
      /**
       * Delete all preference setting for a particular user (because the user was deleted)
       * @param user the user the preference values should be deleted for
       */
      public void deleteUserPreferences(Object user);
  
      /**
       * Delete all preference setting for a particular instance (because the instance was deleted)
       * @param instance the oinstance the preference values should be deleted for
       */
      public void deleteInstancePreferences(Object instance);
  
      /**
       * Write the queued preference values to a permanent data store
       */
      public void flush();
  }
  
  
  
  1.1      date: 2007/04/02 18:25:05;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences/PreferenceProperty.java
  
  Index: PreferenceProperty.java
  ===================================================================
  package org.jboss.seam.wiki.preferences;
  
  import org.jboss.seam.util.Reflections;
  import org.jboss.seam.Component;
  import org.hibernate.validator.InvalidValue;
  import org.hibernate.validator.ClassValidator;
  import org.hibernate.validator.InvalidStateException;
  
  import java.lang.reflect.Method;
  import java.lang.reflect.Field;
  import java.io.Serializable;
  
  /**
   * Meta model, represents a field or property of a Seam component that extends <tt>PreferenceSupport</tt> and
   * has a <tt>Preference</tt> annotation.
   *
   * @author Christian Bauer
   */
  public class PreferenceProperty implements Comparable, Serializable {
  
      private String name;
      private String description;
      private PreferenceVisibility visibility;
      private boolean fieldAccess;
      private PreferenceComponent preferenceComponent;
  
      public PreferenceProperty(String name, String description, PreferenceVisibility visibility, boolean fieldAccess, PreferenceComponent preferenceComponent) {
          this.name = name;
          this.description = description;
          this.visibility = visibility;
          this.fieldAccess = fieldAccess;
          this.preferenceComponent = preferenceComponent;
      }
  
      public String getName() {
          return name;
      }
  
      public String getDescription() {
          return description;
      }
  
      public PreferenceVisibility getVisibility() {
          return visibility;
      }
  
      public PreferenceComponent getPreferenceComponent() {
          return preferenceComponent;
      }
  
      public boolean allowsUserOverride() {
          return getVisibility().equals(PreferenceVisibility.USER) || getVisibility().equals(PreferenceVisibility.INSTANCE);
      }
  
      public boolean allowsInstanceOverride() {
          return getVisibility().equals(PreferenceVisibility.INSTANCE);
      }
  
      public void write(Object componentInstance, Object value) throws Exception {
          Component component = Component.forName(Component.getComponentName(componentInstance.getClass()));
  
          // Validate first
          // TODO: The exception is currently swallowed... but ideally we should never have invalid input here (from user or database)
          InvalidValue[] invalidValues = validate(component, value);
          if (invalidValues.length >0)
              throw new InvalidStateException(invalidValues, component.getName() + "." + getName());
  
          if (fieldAccess) {
              Field field = Reflections.getField(componentInstance.getClass(), getName());
              field.setAccessible(true);
              Reflections.set(field, componentInstance, value);
          } else {
              Method setterMethod = Reflections.getSetterMethod(componentInstance.getClass(), getName());
              setterMethod.setAccessible(true);
              Reflections.invoke(setterMethod, componentInstance, value);
          }
      }
  
      public InvalidValue[] validate(Component component, Object value) {
          ClassValidator validator = component.getValidator();
          return validator.getPotentialInvalidValues( getName(), value );
      }
  
      public int compareTo(Object o) {
          return getDescription().compareTo( ((PreferenceProperty)o).getDescription() );
      }
  
      public String toString() {
          return "PreferenceProperty: " + getName();
      }
  }
  
  
  
  1.1      date: 2007/04/02 18:25:05;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences/Preference.java
  
  Index: Preference.java
  ===================================================================
  package org.jboss.seam.wiki.preferences;
  
  import java.lang.annotation.Target;
  import java.lang.annotation.ElementType;
  import java.lang.annotation.RetentionPolicy;
  import java.lang.annotation.Retention;
  
  /**
   * Used for definition of preferences meta model.
   * <p>
   * Put this annotation on either a Seam component that extends <tt>PreferenceSupport</tt> or on a
   * field of that component. You can then access the preference setting via
   * <tt>#{prefComponent.properties['prefProperty']}</tt> or with <tt>#{prefComponent.prefProperty}</tt>
   * if you have defined a getter method. You can also put this annotation on the setter method instead
   * of the field. Use Hibernate Validator annotations on the property fields or getters to restrict
   * the applicable value ranges.
   * <p>
   * The <tt>PreferenceRegistry</tt> reads these annotations and builds the internal meta model for
   * preference loading, saving, and editing.
   * <p>
   * Note that all preference components and properties that have <tt>USER</tt> visibility also automatically
   * have <tt>INSTANCE</tt> visibility. That means you can not define a preference component or property that
   * has only <tt>INSTANCE</tt> visibility and that can not be configured on a user-level.
   * 
   * @author Christian Bauer
   */
  @Target({ElementType.TYPE, ElementType.FIELD})
  @Retention(RetentionPolicy.RUNTIME)
  public @interface Preference {
      String description();
      PreferenceVisibility visibility() default PreferenceVisibility.SYSTEM;
  }
  
  
  
  1.1      date: 2007/04/02 18:25:05;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences/PreferenceRegistry.java
  
  Index: PreferenceRegistry.java
  ===================================================================
  package org.jboss.seam.wiki.preferences;
  
  import org.jboss.seam.annotations.*;
  import org.jboss.seam.ScopeType;
  import org.jboss.seam.Component;
  import org.jboss.seam.log.Log;
  import org.jboss.seam.contexts.Contexts;
  
  import java.util.*;
  
  /**
   * Reads all components and properties on such components with a <tt>@Preference</tt> annotation.
   * <tt>
   * You can access the preference meta model, e.g. to build a dynamic preference editor, by looking up
   * this registry with <tt>#{preferenceRegistry}</tt> and accessing the metadata through the
   * <tt>preferenceComponents</tt> and <tt>preferenceComponentsByName</tt> collections.
   *
   * @author Christian Bauer
   */
  @Name("preferenceRegistry")
  @Scope(ScopeType.APPLICATION)
  @Startup(depends = "wikiInit")
  public class PreferenceRegistry {
  
      @Logger static Log log;
  
      SortedSet<PreferenceComponent> preferenceComponents = new TreeSet<PreferenceComponent>();
      Map<String, PreferenceComponent> preferenceComponentsByName = new HashMap<String, PreferenceComponent>();
  
      @Create
      public void scanPreferenceComponents() {
          // Register the meta model by scanning components with @Preference annotation
          String[] boundNames = Contexts.getApplicationContext().getNames();
  
          for (String boundName : boundNames) {
              if (boundName.endsWith(".component") && !boundName.startsWith("org.jboss.seam")) {
                  Component component = (Component) Contexts.getApplicationContext().get(boundName);
  
                  if (component.getBeanClass().getAnnotation(Preference.class) != null &&
                      !preferenceComponentsByName.containsKey(component.getName())) {
  
                      log.debug("Registering preference component: " + component.getName());
                      PreferenceComponent prefComponent = new PreferenceComponent(component);
                      preferenceComponentsByName.put(prefComponent.getName(), prefComponent);
                      preferenceComponents.add(prefComponent);
                  }
              }
          }
      }
  
      public Map<String, PreferenceComponent> getPreferenceComponentsByName() {
          return preferenceComponentsByName;
      }
  
      public SortedSet<PreferenceComponent> getPreferenceComponents() {
          return preferenceComponents;
      }
  
      public SortedSet<PreferenceComponent> getPreferenceComponents(PreferenceVisibility visibility) {
          SortedSet<PreferenceComponent> filteredComponents = new TreeSet<PreferenceComponent>();
          for (PreferenceComponent preferenceComponent : preferenceComponents) {
              if (preferenceComponent.getVisibility().ordinal() >= visibility.ordinal()) filteredComponents.add(preferenceComponent);
          }
          return filteredComponents;
      }
  }
  
  
  
  1.1      date: 2007/04/02 18:25:05;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences/PreferenceSupport.java
  
  Index: PreferenceSupport.java
  ===================================================================
  package org.jboss.seam.wiki.preferences;
  
  import org.jboss.seam.annotations.Create;
  import org.jboss.seam.annotations.Logger;
  import org.jboss.seam.Component;
  import org.jboss.seam.log.Log;
  import org.jboss.seam.core.Events;
  
  import java.util.Map;
  import java.util.HashMap;
  import java.util.Set;
  
  /**
   * Superclass for definition of the preferences meta data.
   * <p>
   * Subclass this class with a Seam component in <tt>CONVERSATION</tt> scope and apply
   * <tt>@Preference</tt> annotations to define your preferences.
   * <p>
   * Think about grouping of preference values: Each Seam component that subclasses this
   * class is a group of preferences, the name of the group is the name of the Seam component.
   * Each property of the subclass, or even each field (whatever is annotated with <tt>@Preference</tt>)
   * is a preference property that can be loaded and stored with an implementation of <tt>PreferenceProvider</tt>.
   * <p>
   * You can access preference properties transparently with either
   * <tt>#{seamNameOfThePreferenceComponent.properties['prefPropertyName']}</tt> or with
   * <tt>#{seamNameOfThePreferenceComponent.prefProperty}</tt> if there are property accessor methods, or even
   * type-safe by getting the  whole <tt>#{seamNameOfThePreferenceComponent}</tt> injected.
   * <p>
   * Subclasses should be in <tt>CONVERSATION</tt> scope.
   * <p>
   * Subclasses automatically read preference properties when they are instantiated for the current conversation.
   * Subclasses are  automatically notified to refresh their property values inside a conversation, however, you need
   * to call the method <tt>super.refreshProperties()</tt> in your subclass in a method that has the event listener
   * <tt>@Observer("PreferenceEditor.refresh.seamNameOfThePreferenceComponent")</tt> to enable this functionality.
   * This is only used if preference values can change during a conversation, typically when a preference editor
   * is available to the user in that conversation.
   * <p>
   * You can notify all users of a preference component when a preference property value is changed during the
   * same conversation in which the preference values are used. The event
   * <tt>@Observer("Preferences.seamNameOfThePreferenceComponent")</tt> is fired when preference values are
   * re-loaded/loaded and you can put it on any method that needs to re-read some state after a preference value
   * change. Note again that this is mostly useful inside a conversation, instances of this class should not
   * live longer than a conversation.
   * <p>
   * Override the <tt>getCurrentUserVariable</tt> and <tt>getCurrentInstanceVariable</tt> with EL expressions or
   * context variable names if you want to use a particular user or instance for lookup of the preference values.
   * These methods default to null and only system-level preference values are resolved.
   *
   * @author Christian Bauer
   */
  public abstract class PreferenceSupport {
  
      @Logger Log log;
  
      Map<String, Object> properties = new HashMap<String, Object>();
  
      @Create
      public void materialize() {
          loadPropertyValues();
      }
  
      public Map<String, Object> getProperties() {
          return properties;
      }
  
      private void loadPropertyValues() {
          log.debug("Loading preference component property values");
          PreferenceRegistry registry = (PreferenceRegistry) Component.getInstance("preferenceRegistry");
          PreferenceProvider provider = (PreferenceProvider) Component.getInstance("preferenceProvider");
          Object user = getCurrentUserVariable() != null ? Component.getInstance(getCurrentUserVariable()) : null;
          Object instance = getCurrentInstanceVariable() != null ? Component.getInstance(getCurrentInstanceVariable()) : null;
  
          PreferenceComponent prefComponent =
              registry.getPreferenceComponentsByName().get( Component.getComponentName(getClass()) );
  
          try {
              Set<PreferenceValue> valueHolders = provider.load(prefComponent, user, instance, true);
              for (PreferenceValue valueHolder : valueHolders) {
                  log.trace("Loaded preference property value: " + valueHolder.getPreferenceProperty().getName());
  
                  // Write onto instance so users can call #{myPrefs.getThisPreferenceSetting}
                  valueHolder.getPreferenceProperty().write(this, valueHolder.getValue());
  
                  // Keep a duplicate in this map so users can call #{myPrefs.properties['thisPreferenceSetting']}
                  properties.put(valueHolder.getPreferenceProperty().getName(), valueHolder.getValue());
  
              }
          } catch (Exception ex) {
              log.warn("Could not write preference property value on component: " + prefComponent.getName(), ex);
          }
  
      }
  
      public void refreshProperties() {
          PreferenceRegistry registry = (PreferenceRegistry) Component.getInstance("preferenceRegistry");
          PreferenceComponent prefComponent =
              registry.getPreferenceComponentsByName().get( Component.getComponentName(getClass()) );
  
          log.debug("Refreshing preference component property values: " + prefComponent.getName());
  
          loadPropertyValues();
  
          log.debug("Notifying all preference component refresh isteners");
  
          Events.instance().raiseEvent("Preferences." + prefComponent.getName());
      }
  
      public String getCurrentUserVariable() { return null; }
      public String getCurrentInstanceVariable() {return null; }
  }
  
  
  
  1.1      date: 2007/04/02 18:25:05;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences/PreferenceVisibility.java
  
  Index: PreferenceVisibility.java
  ===================================================================
  package org.jboss.seam.wiki.preferences;
  
  import java.io.Serializable;
  
  /**
   * Support for multi-level preference overrides.
   * <p>
   * You can either only use <tt>SYSTEM</tt> and have one level of preferences that can
   * be changed with a system-level preferences editor, or you can override preference values
   * for a logged-in user or even for a currently active instance (e.g. the currently visible
   * document).
   * 
   * @author Christian Bauer
   */
  public enum PreferenceVisibility implements Serializable {
      SYSTEM, USER, INSTANCE
  }
  
  
  
  1.1      date: 2007/04/02 18:25:05;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences/PreferenceComponent.java
  
  Index: PreferenceComponent.java
  ===================================================================
  package org.jboss.seam.wiki.preferences;
  
  import org.jboss.seam.Component;
  import org.hibernate.validator.InvalidValue;
  
  import java.util.*;
  import java.lang.reflect.Method;
  import java.lang.reflect.Field;
  import java.beans.Introspector;
  
  /**
   * Meta model, represents a Seam component that extends <tt>PreferenceSupport</tt> and has a <tt>Preference</tt> annotation.
   *
   * @author Christian Bauer
   */
  public class PreferenceComponent implements Comparable {
  
      private String name;
      private String description;
      private PreferenceVisibility visibility;
      private SortedSet<PreferenceProperty> properties = new TreeSet<PreferenceProperty>();
      private Map<String, PreferenceProperty> propertiesByName = new HashMap<String, PreferenceProperty>();
  
      private Component component;
  
      public PreferenceComponent(Component component) {
  
          this.component = component;
          this.name = component.getName();
          this.description = component.getBeanClass().getAnnotation(Preference.class).description();
          this.visibility = component.getBeanClass().getAnnotation(Preference.class).visibility();
  
          // Now find the preference properties this component wants
          Class beanClass = component.getBeanClass();
  
          String propertyName;
          String propertyDescription;
          PreferenceVisibility propertyVisibility;
  
          // @Preference setter methods
          Method[] methods = beanClass.getDeclaredMethods();
          for (Method method : methods) {
              String methodName = method.getName();
              if ( method.isAnnotationPresent(Preference.class) &&
                   methodName.startsWith("set") &&
                   method.getParameterTypes().length == 1) {
                  if ( !method.isAccessible() ) method.setAccessible(true);
  
                  propertyName = Introspector.decapitalize(methodName.substring(3));
                  propertyDescription  = method.getAnnotation(Preference.class).description();
                  propertyVisibility = method.getAnnotation(Preference.class).visibility();
  
                  PreferenceProperty property =
                          new PreferenceProperty(propertyName, propertyDescription, propertyVisibility, false, this);
                  properties.add(property);
                  propertiesByName.put(property.getName(), property);
              }
          }
  
          // @Preference fields
          for ( Field field: beanClass.getDeclaredFields() ) {
              if ( field.isAnnotationPresent(Preference.class) ) {
                  if ( !field.isAccessible() ) field.setAccessible(true);
  
                  propertyName = field.getName();
                  propertyDescription = field.getAnnotation(Preference.class).description();
                  propertyVisibility = field.getAnnotation(Preference.class).visibility();
  
                  PreferenceProperty property =
                          new PreferenceProperty(propertyName, propertyDescription, propertyVisibility, true, this);
                  properties.add(property);
                  propertiesByName.put(property.getName(), property);
              }
          }
      }
  
      public String getName() {
          return name;
      }
  
      public String getDescription() {
          return description;
      }
  
      public PreferenceVisibility getVisibility() {
          return visibility;
      }
  
      public boolean allowsUserOverride() {
          return getVisibility().equals(PreferenceVisibility.USER) || getVisibility().equals(PreferenceVisibility.INSTANCE);
      }
  
      public boolean allowsInstanceOverride() {
          return getVisibility().equals(PreferenceVisibility.INSTANCE);
      }
  
      public SortedSet<PreferenceProperty> getProperties() {
          return properties;
      }
  
      public SortedSet<PreferenceProperty> getProperties(PreferenceVisibility visibility) {
          SortedSet<PreferenceProperty> filteredProperties = new TreeSet<PreferenceProperty>();
          for (PreferenceProperty property : properties) {
              if (property.getVisibility().ordinal() >= visibility.ordinal()) filteredProperties.add(property);
          }
          return filteredProperties;
      }
  
      public Map<String, PreferenceProperty> getPropertiesByName() {
          return propertiesByName;
      }
  
      public Component getComponent() {
          return component;
      }
  
      public Map<PreferenceProperty, InvalidValue[]> validate(Collection<PreferenceValue> valueHolders) {
          Map<PreferenceProperty, InvalidValue[]> invalidProperties = new HashMap<PreferenceProperty, InvalidValue[]>();
          for (PreferenceValue valueHolder : valueHolders) {
              PreferenceProperty property = valueHolder.getPreferenceProperty();
              invalidProperties.put(property, property.validate(getComponent(), valueHolder.getValue()));
          }
          return invalidProperties;
      }
  
      public int compareTo(Object o) {
          return getDescription().compareTo( ((PreferenceComponent)o).getDescription() );
      }
  
  }
  
  
  
  1.1      date: 2007/04/02 18:25:05;  author: cbauer;  state: Exp;jboss-seam/examples/wiki/src/main/org/jboss/seam/wiki/preferences/PreferenceValue.java
  
  Index: PreferenceValue.java
  ===================================================================
  package org.jboss.seam.wiki.preferences;
  
  /**
   * Implementation of a value holder, load and stored by <tt>PreferenceProvider</tt>.
   * <p>
   * Use this interface to plug-in your own preference values, your <tt>PreferenceProvider</tt>
   * has to return values wrapped in this interface and it will receive values wrapped in this
   * interface.
   *
   * @author Christian Bauer
   */
  public interface PreferenceValue {
  
      public Object getValue();
      public void setValue(Object value);
  
      public void setPreferenceProperty(PreferenceProperty property);
      public PreferenceProperty getPreferenceProperty();
  
      public boolean isDirty();
  
      public boolean isSystemAssigned();
      public boolean isUserAssigned();
      public boolean isInstanceAssigned();
  
  }
  
  
  



More information about the jboss-cvs-commits mailing list