Versions Compared

Key

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

...

On our last few design calls we've been working through the generic AttributeType mechanism that we're introducing in 1.9. (

Jira Legacy
TRUNK-2588
TRUNK-2588
serverOpenMRS JIRA
serverId45c5771b-fa4b-3e43-b34a-c19dc45ccc95
)
We wanted to summarize the current state of things. Some of this is in trunk, but some is refactoring Darius is doing. We're especially interested in thoughts about how these classes should be linked together with parameterized types, since we're not convinced we've gotten that right:

Code Block
languagejava
/**
 * Capable of converting T to/from a String that can be persisted in the varchar column of a database. 
 * E.g. a date would be stored as yyyy-mm-dd and an image might be stored as a uri pointing to a 
 * PACS system. Also capable of validating T, e.g. so we can use a plain java.util.Date to represent 
 * "date-in-past" but limit its possible values.
 * Defines a String "datatypeHandled", e.g. "date", "regex-validated-string")
 */
interface CustomDatatypeHandler<T> {
  T fromPersistedString(String);
  String toPersistedString(T);
  void validate(T);
  String render(String persistedValue, String view);
  // can be configured with setHandlerConfiguration(String);
}

/**
 * holds the definition of a custom datatype (which is handled by a handler of the above interface). 
 * For example VisitAttributeType and SettingGlobalProperty (formerly global properties 1.8 and below) (we're able to do typed GPs trivially now)
 */
interface CustomDatatyped {
  String getDatatype(); // required
  /**
   * if specified, will be handled by this specific CustomDatatypeHandler, otherwise it will be handled 
   * by the default handler for this thing's datatype
   */
  String getPreferredHandlerClassname(); // optional
  String getHandlerConfig(); // optional
}

/**
 * for user-defined extensions to core domain objects, which would be handled by adding custom 
 * database columns in a less generic system. E.g. VisitAttributeType implements AttributeType<Visit>
 * datatype/handler specified via CustomDatatyped superinterface
 */
interface AttributeType<OwningType extends Customizable> extends CustomDatatyped, OpenmrsMetadata {
  Integer getMinOccurs();
  Integer getMaxOccurs();
}

/**
 * trivial implementations of AttributeType (via BaseAttributeType)
 */
class VisitAttributeType
class LocationAttributeType
class ProviderAttributeType

/**
 * Implemented by domain classes that may be customized by the user via custom attributes. 
 * E.g. Visit implements Customizable<VisitAttribute>, Location implements Customizable<LocationAttribute>
 * Has convenience methods for dealing with a collection of attributes, of different types:
 */
interface Customizable<AttrClass extends Attribute> {
  Collection<AttrClass> getAttributes();  //includes voided
  Collection<AttrClass> getActiveAttributes();  //non-voided
  void addAttribute(AttrClass);
  void setAttribute(AttrClass);  // voids other attributes of the given type
}

/**
* holds a value managed by a CustomDatatypeHandler. E.g. VisitAttribute, Setting (formerly global properties 1.8 and below)GlobalProperty. 
* Any implementation of this has a corresponding CustomDatatyped implementation. "persistedValue"
* is a String suitable for persisting in a db varchar column; "objectValue" is what you'd want 
* to work with in the API.
*/
interface CustomValue {
  String getPersistedValue();
  void setPersistedValue();
  Object getObjectValue();  // don't remember why this isn't <T>
  void setObjectValue(Object);
}

/**
* value corresponding to an AttributeType (which defines its datatype, whether it's 
* required, whether it can repeat, etc), e.g. VisitAttribute corresponds to VisitAttributeType
*/
interface Attribute<OwningType extends Customizable> implements CustomValue, OpenmrsData {
  OwningType getOwner();
  void setOwner(OwningType);
  AttributeType<OwningType> getAttributeType();
}

/**
* trivial implementation of Attribute (via BaseAttribute)
*/
class VisitAttribute
class LocationAttribute
class ProviderAttribute

/**
* this is interesting in that it both defines a custom datatype and stores its value
*/
class Setting (formerly global properties 1.8 and below) GlobalProperty implements CustomDatatyped, CustomValue

...

Interface with the custom datatype consumer (setting settings (formerly global properties Global Properties from platform 1.8 and belowdownwards), object attribute, complex obs)

  • one object per consumer – in attribute context, this means one per property type; in the setting settings (formerly global properties Global Properties from platform 1.8 and belowdownwards) context context, this means one for each property that is being displayed
  • constructor() – default handler, handler config="", minOccurs=0, maxOccurs=1
  • constructor(String handler, String handlerConfig, int minOccurs, int maxOccurs)
  • the consumer loads the custom datatype with all its values and receives them back via save
    • standard interface
      • data is exchanged through a List<CustomDataRow>; deleted objects have newValue=null, new objects have oldValue=null

        Code Block
        languagejava
        public CustomDataRow {
          public String key;  // optionally set by/returned to consumer
          public String oldValue;  // set by/returned to consumer
          public String newValue;  // returned to consumer
          public String voidReason;  // returned to consumer
          public Object data;  // for internal use by custom data service object
        }
        

        The list is copied to a local list on load and then to a temporary list before returning from save; changes to the list by the consumer have no effect and the data object is never returnd to the customer

      • load(List<CustomDataRow>) (empty indicates no values exist)
      • save() : List<CustomDataRow> throws DataError Exception (empty indicates no values exists and none were added; underlying data is saved)
      • customerIterator() : ListIterator<Integer>
    • alternate interface where maxOccurs=1 (throws UnsupportedOperation Exception if maxOccurs != 1)
      • load(String) (null indicates no value exists)
      • save() : String throws DataError Exception (null indicates not set or voided; underlying data is saved)
      • getVoidReason() : String (returns null if not present)
    • both interfaces
      • cancel() (any changes not previously saved are abandoned and the custom data service object is cleared)
      • isDirty() : Boolean (whether any changed have been made to the list)
      • validate() : List<String> (each element is validated and the multiplicity is validated; any errors found are returned as messages; if no errors are found, returns and empty list and dirty is cleared; calling validate before saving guarantees no error will be thrown)
      • cancel() (changes are discarded and loaded data is cleared)
  • the custom data service object delegates handler calls to the handler

...