Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

 

Table of Contents
outlinetrue

...

The a super user, all preferences are visible.

REST API operation /broker/

...

delete(user)

This broker-level operation allows the a suitable permissioned user to delete all records personally owned by the identified user.  This will delete the preferences belonging user's preferences.  In future it may delete other items from belong personally to the indicated user.  This will support the case where a user has left an organisationuser, such as last login records, human names mapping etc.

An organisation may hook this operation up to a 'leavers' feed.

Configured Object Changes

All ConfiguredObjects will have a set of preferences representing the set of preferences containing the preferences associated with the that configured object.

The caller will use the ConfiguredObject is responsible for:

  • creation of new preference(s)
  • update of existing preference(s)
  • delete of existing preferences(s)
  • access of some/all of the preference(s)
  • makes security decisions by talking to the SecurityManager.

to retrieve a UserPreferences facade object.  It is the facade object that allows preferences, for this user, to be created/read/updated/deleted.

The UserPreferences facade  will use a dedicated single thread for it work which will belong the the Configured ObjectThe ConfiguredObject will use a dedicated single thread for the creation/deletion/update of preferences. This approach will allow the individual REST API interactions to be atomic even when the request embodies multiple preferences.  It is likely that the preference executor thread will be shared by all configured objects within a contiguous part of the model - that is, there will be a broker preference executor, and a per virtualhost preference executor.

The REST API interacts with preferences via the ConfiguredObject APIuses path to retrive the correct ConfiguredObject API then retrieves the UserPreferences object.

Code Block
public interface ConfiguredObject
{
   ...
  List<Preference>UserPreferences createPreferencesgetPreferences(List<Map<String,Object>>); 
  Set<Preference> getPreferences(PreferenceFilter);
 
  void updatePreferences(List<Map<String,Object>>);
  
  Set<Preference> deletePreferences(PreferenceFilter);
  ...
}

 

ConfiguredObject#createPreferences

This method will create one or more preferences as an atomic operation.  This method first will:

  • establish that the business rules (name uniqueness, cardinality rules) are not broken by the proposed new preferences
  • check with the security manager that the creation of the preferences is allowed

It will then:

  • use the PreferencesFactory to create the new preference(s) and put them in a local preference registry
  • tell the store listener that new preferences have been created.

As preferences are mutated by a single thread and the business rules are checked for violations up front, the need to 'rollback' successfully created preferences if a later one fails is avoided.

ConfiguredObject#updatePreferences

This method will update one or more preferences as an atomic operation.  This method first will:

  • establish that the business rules (name uniqueness, cardinality rules, mutablity of id/name) are not broken by the proposed changes
  • check with the security manager that the update of the preferences is allowed

It will then:

  • use the PreferencesFactory to create replacement preference object(s)
  • tell the store listener that preferences have been updated.

As preferences are mutated by a single thread and the business rules are checked for violations up front, the need to 'rollback' successfully created preferences if a later one fails is avoided.

ConfiguredObject#getPreferences()

This method gets the preferences that match a caller supplied filter and are accessible to the authenticated user.

The caller (typically the REST layer) will pass a filter with predicates:

  • on type, type/name or id
  • if a /userpreferences request - a predicate that encapsulates preference.owner == authenticate user
    if a /visiblepreferences request - a predicate that encapsulates preference.owner <> authenticate user

This method will call out to the SecurityManager is accessible.  Only those passing this check will be included in the Set.

ConfiguredObject#deletePreferences()

This method deletes the preferences that match a caller supplied filter and belong to the authenticated user.  The caller (typically the REST layer) will passing a filter which filters on type, type/name or id.

This method will call out to the SecurityManager to determine if the preference may be deleted.

This method will return the preferences that were deleted, allowing a future version of the REST API to return the delete content as part of the DELETE response.

Preferences Model Object

Preference object represents an instance of a preference.      Each Preference will have a PreferenceValue encapsulating the type specific details of preference.  There will be a GenericPreferenceValue used to represent arbitrary preferences that the server requires no knowledge (these will have a type name begining X-).  There will also specific concrete implementations of PreferenceValue that represent preferences types which the server has knowledge.

Concrete instances of Preference/PreferenceValue will be immutable.

Preference Objects do not make access decisions.

Code Block
public interface Preference<V extends PreferenceValue>
{
 UUID getId();
 String getName();
 String getType();
 String getDescription();
 Principal getOwner();
 ConfiguredObject getAssociatedObject()
 List<Principal> getVisibiltyList();
 Date getCreatedDate();
 Date getLastUpdatedDate();
 
 V getValue();
 Map<String,Object> getAttributes();
}
 
public interface PreferenceValue
{
  Map<String,Object> getAttributes();
}

Preference#getAttributes

Returns a r/o map containing the attributes of the preference.  The map will have key/value pairs for id, name, type, description, owner, visibilityList, createdDate, lastUpdateDate.  It will also have a key value whose value is a map containing the key/value pairs for the preference value itself.

Preferences Factory

There will be a preference factory which will work rather like the object factory.  The Model will give the ConfiguredObject a preference factory.  BrokerModel will have a hard relationship with a PreferencesFactoryImpl which will know how to create Preference and PreferenceValue objects from the attributes.

The factory will use the type discriminator to determine the actual type of PreferenceValue.  If the discriminator begins with "X-" the GenericPreferenceValue will be used, other a specific concrete PreferenceValue will be used.

Preference Storage

In the store an instance of a Preference will look like the example below. 

Code Block
languagejs
{
 id: <UUID - immutable>,
 name: <name - immutable>,
 type: <type discriminator>
 description: <description>,
 associatedId: <UUID of model object - immutable>,
 owner: <Domain prefixed Principal - normally that belonging to a user - immutable>,
 visibliltyList: [<Domain prefixed Principal - normally that belonging to a group>],
 value: {
   ...
 },
 createdDate: 123456789,
 updatedDate: 123456789
} 

 

Security Manager

The security manager will gain responsibilities for authorising preference view/create/update/delete operations.

The Security Manager will first need to check that the user has access to the ConfiguredObject itself.  For the moment the ordinary user operations we will probably use the existing ACL rules CONFIGURE BROKER and ACCESS VIRTUALHOST

For the super-user, we will need a new permission will be required (ACCESS PREFERENCES).  A user in the identity maintainer role will normally hold this.

SecurityManager#authoriseCreate

Returns true if the user is allow to create the preference. It will be true if:

  • the user belongs to all the groups identified in the visibilityList
  • the owner matches the authenticated user.

SecurityManager#authoriseUpdate

Returns true if the user is allow to update the preference. It will be true if:

  • the user belongs to all the groups identified in the visibilityList
  • the owner matches the authenticated user.

SecurityManager#authoriseView

Returns true if the user is allow to view this preference:

  • authenticated user owns the preference, or
  • authenticated user belongs to a group that is visible to the visibilityList
  • authenticate user is the super user.

SecurityManager#authoriseDelete

Returns true if the user is allow to delete this preference:

  • authenticated user owns the preference, or
  • authenticate user is the super user.

Preference Store

We will have a PreferenceStore interface rather like DurableConfigurationStore

  • open/close

  • upgrade
  • visit
  • create
  • update
  • delete

API is in terms of PreferencesRecord (analogous to ConfiguredObjectRecords).  The stores themselves will hold the preference records as JSON, but this isn't part of the contract.

AbstractSystemConfig and AbstractVirtualHostNode have a separate preference stores.

  • The concrete instances of SystemConfig and VirtualHostNode responsible for creating the preference store too.
  • For storage mechanism BDB, JDBC, Derby there would be a restriction that the preference store must be co-located with the configuration.
  • For storage mechanism JSON the preferences would be stored in a separate file.

Recovery

At recovery time the AbstractSystemConfig and AbstractVirtualHostNode will need to read all the preferences from their respective stores and create Preference objects.    This needs to occur after the configured objects are recovered. In this phase, objects corresponding to the records must be created.  The recovery algorithm will be similar to that of configured object recovery as preferences may reference other preferences, so some preferences (Dashboards) may have to await the creation of others before they may be created.   As preferences are created they need to be assigned to their associated configured object.

If during the recovery phase, a preference is encountered that refers to a configured object that no longer exists, the preference can be dropped.  If a preference is found that refers to a preference that no longer exists, the reference can be removed.  The latter would happen if a dashboard of user B refers to a query of user A.  If user A deletes their query, user B's reference will be left dangling.  On next recovery, the dangling reference must be removed.  (The UI should probably just display a ? where the query would have been).

Persistence

...
}

public interface UserPreferences() 
{
  // Gets all preferences belonging to this user
  Map<String, Map<String,Preference> getPreferences()

  // replaces all current preferences with replacement.  returns preferences that were removed – used by PUT /userpreferences & DELETE
  Map<String, Map<String, Preference>> replace(Map<String, Map<String, Preference>>)

  // update/append preferences – used by POST /userpreferences
  updateOrAppend(Map<String, Map<String, Preference>>) 

  // replaces all prefs of given type with the replacement. returns preferences that were removed – used by PUT /userpreferences/type & DELETE
  Map<String, Preference> replace (String type, Map<String, Preference>)

  // update/append preferences within given type – used by POST /userpreferences/type
  updateOrAppend(String type, Map<String, Preference>)

  // // Gets all preferences visible to this user, does not include those that belong to him
  Map<String, Map<String, Preference> getVisiblePreferences()

  // Allows REST API to construct the Preference objects – does not persist of the object.  Object will be immutable
 Preference createPreference(String type, Map<String,Object>) 
}

UserPreferences#getPreferences

This method gets the preferences that belong to the current user.

Checks with the security manager that the access of the preference is allowed.

UserPreferences#getVisiblePreferences

This method gets the preferences that are visible to the user.  This method needs to return all preferences that are visible to the user but do not belong to him.

UserPreferences#replace

Replaces current preferences with new ones (at either the top-level or type level), returning the ones that were removed.

  1. Identifies all the preferences that belong to the user that need to be removed
    1. For each preference, call the SecurityManager#authoriseDelete.
  2. For each replacement preference
    1. create new preference objects populating the id and the current user as owner
    2. call the SecurityManager#authoriseCreate
  3. Tells the store listener about preferences that have been removed and those that have been added.
  4. Return those preferences that were removed

UserPreferences#updateOrAppend

Updates or appends new preferences (at either the top-level or type level).

  1. Separate new preferences from the updates.  Do this by examining the incoming parameter: those with an id are updates.
  2. For each update preference:
    1. Find the stored preference with the same ID.
    2. create a new preference object taking the mutable values from the incoming object and immutable values from stored preference (id, name, owner)
    3. SecurityManager#authoriseUpdate on the preference
  3. For all other preferences:
    1. create new preference objects populating the id and the current user as owner and other fields from the incoming
    2. call the SecurityManager#authoriseCreate
  4. Ensure that preference names remains unique for current user within each type
  5. Tells the store listener about preferences that have been updated and those that have been added.

UserPreferences#createPreference

Create a preference value object.  Does not perform ACL check and does not store the preference in the store.

ConfiguredObject#getUserPreferences

Gets the UserPreference facade.  The caller can be assured that this object will always return a non-null value, even for those users who have no permission to access or mutate preferences.

Preferences Model Object

Preference object represents an instance of a preference.      Each Preference will have a PreferenceValue encapsulating the type specific details of preference.  There will be a GenericPreferenceValue used to represent arbitrary preferences that the server requires no knowledge (these will have a type name beginning X-).  There will also specific concrete implementations of PreferenceValue that represent preferences types which the server has knowledge.

Concrete instances of Preference/PreferenceValue will be immutable.

Preference Objects do not make access decisions.

Code Block
public interface Preference<V extends PreferenceValue>
{
 UUID getId();
 String getName();
 String getType();
 String getDescription();
 Principal getOwner();
 ConfiguredObject getAssociatedObject()
 List<Principal> getVisibiltyList();
 Date getCreatedDate();
 Date getLastUpdatedDate();
 
 V getValue();
 Map<String,Object> getAttributes();
}
 
public interface PreferenceValue
{
  Map<String,Object> getAttributes();
}

Preference#getAttributes

Returns a r/o map containing the attributes of the preference.  The map will have key/value pairs for id, name, type, description, owner, visibilityList, createdDate, lastUpdateDate.  It will also have a key value whose value is a map containing the key/value pairs for the preference value itself.

Preferences Factory

There will be a preference factory which will work rather like the object factory.  The Model will give the ConfiguredObject a preference factory.  BrokerModel will have a hard relationship with a PreferencesFactoryImpl which will know how to create Preference and PreferenceValue objects from the attributes.

The factory will use the type discriminator to determine the actual type of PreferenceValue.  If the discriminator begins with "X-" the GenericPreferenceValue will be used, other a specific concrete PreferenceValue will be used.

Preference Storage

In the store an instance of a Preference will look like the example below. 

Code Block
languagejs
{
 id: <UUID - immutable>,
 name: <name - immutable>,
 type: <type discriminator>
 description: <description>,
 associatedId: <UUID of model object - immutable>,
 owner: <Domain prefixed Principal - normally that belonging to a user - immutable>,
 visibliltyList: [<Domain prefixed Principal - normally that belonging to a group>],
 value: {
   ...
 },
 createdDate: 123456789,
 updatedDate: 123456789
} 

 

Security Manager

The security manager will gain responsibilities for authorising preference view/mutate operations.

The Security Manager will first need to check that the user has access to the ConfiguredObject itself.  For the moment the ordinary user operations we will probably use the existing ACL rules CONFIGURE BROKER and ACCESS VIRTUALHOST

For the super-user, we will need a new permission will be required (ACCESS PREFERENCES).  A user in the identity maintainer role will normally hold this.

SecurityManager#authoriseCreate

Returns true if the user is allow to create preferences. It will be true if:

  • the user belongs to all the groups identified in the visibilityList
  • the owner matches the authenticated user.

SecurityManager#authoriseUpdate

Returns true if the user is allow to update the preference. It will be true if:

  • the user belongs to all the groups identified in the visibilityList
  • the owner matches the authenticated user.

SecurityManager#authoriseDelete

Returns true if the user is allow to delete this preference:

  • authenticated user owns the preference

SecurityManager#authoriseView

Returns true if the user is allow to view this preference:

  • authenticated user owns the preference, or
  • authenticated user belongs to a group that is visible to the visibilityList

Preference Store

We will have a PreferenceStore interface similiar to the DurableConfigurationStore but with some notable difference

  • open/close

  • upgrade
  • visit
  • create
  • update
  • delete

Compared to the DurableConfigurationStore the PreferenceStore has the following differences:

  • no upgrade method. The store should handle upgrades transparently during startup/open. This is considered a fault in the original API
  • no visit method. Rather the open method should handle initial loading of the data. See below for more detail.

API is in terms of PreferenceRecords (analogous to ConfiguredObjectRecords) which have minimal knowledge of the internal structure except from the UUID. The ID is necessary to identify records for deletion and update operations.

Broker and VirtualHost have separate preference stores.

  • The Broker and VirtualHost configuration will have an attribute of a custom ManagedAttributeValueType containing a type and an optional attributes filed for type specific configuration (e.g., store location).
  • The concrete instances of SystemConfig and VirtualHostNode responsible for creating the preference store too.
  • For storage mechanism BDB, JDBC, Derby there would be a restriction that the preference store must be co-located with the configuration.
  • For storage mechanism JSON the preferences would be stored in a separate file.
Code Block
languagejava
interface PreferenceStore
{
    // see below for the Updater and Recoverer interfaces
    Collection<PreferenceRecords> openAndLoad(PreferenceStoreUpdater updater);

    // safely persist all state and close all resources
    void close();

    // adds preferences to the store
    void create(Collection<PreferenceRecord> preferences);

    // updates existing preferences. throws an exception if preference with given id is not already in the store
    void update(Collection<PreferenceRecord> preferences);

    // updates existing preferences. if a preference with given id is not already in the store it will be added
    void updateOrCreate(Collection<PreferenceRecord> preferences);

    // remove preferences from the store. throws an exception if preference with given id is not already in the store
    void delete(Collection<PreferenceRecord> preferences);
}

interface PreferenceRecord
{
    UUID getId();
	Map<String, Object> getAttributes();
}

Recovery

At recovery time the PreferenceRecords in the PreferenceStore will need to be converted into Preference objects. This needs to occur after the configured objects are recovered. In this phase, objects corresponding to the records must be created. The recovery algorithm will be similar to that of configured object recovery as preferences may reference other preferences, so some preferences (Dashboards) may have to await the creation of others before they may be created. As preferences are created they need to be assigned to their associated configured object.

If during the recovery phase, a preference is encountered that refers to a configured object that no longer exists, the preference can be dropped.  If a preference is found that refers to a preference that no longer exists, the reference can be removed.  The latter would happen if a dashboard of user B refers to a query of user A.  If user A deletes their query, user B's reference will be left dangling.  On next recovery, the dangling reference must be removed.  (The UI should probably just display a ? where the query would have been).

The idea behind getting rid of the visit method is to be able to use a store that is essentially write-only after it has been read from disk. However the loading needs some customization hooks:

  • we want to ability to update individual preferences. This may include updating, creating and deleting preferences. This might require multiple passes.
  • Since the update step might arbitrarily modify the preferences the store needs to be informed about the changes so that the update can be persisted.
  • preferences need to ability to reference each other. This is done by storing UUIDs. The objects using the preferences are responsible for resolving those references.

The first to points are the responsibility of the PreferenceStoreUpdater while the second should be performed by the PreferenceStoreRecoverer.

The recoverer is responsible for

  1. Convert PreferenceRecord to Preference
  2. Convert generic attributes to a type specific PreferenceValue
  3. Attach Preference to AbstractConfiguredObject
Code Block
languagejava
interface PreferenceStoreUpdater
{
    Collection<PreferenceRecord> updatePreferences(String currentVersion, Collection<PreferenceRecord> preferences);
    String getLatestVersion();
}

Persistence

Preferences are considered immutable. All changes to preferences should go through a UserPreferences facade which is responsible for updating the storeThe persistent stores need to listen for new preferences, updates to existing preferences or deletes.  A listener mechanism similar to ConfigurationChangeListener will be used.

High Level Class Diagram

...

  1. Should preferences have metadata?  At the moment I think we can live without it
  2. How will preferences map to AMQP Management?
  3. What limits should be placed on the maximum number of preferences the user is allowed to have?