[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