Sapphire Developer Guide

Versions and Version Constraints

In many complex Sapphire models, it is useful to be able to constrain functionality based on a version. To simplify these scenarios, Sapphire has native constructs for dealing with versions and version constraints.

Version - Represents a version as a sequence of long integers. In string format, it is represented as a dot-separated list of numeric segments, such as "1.2.3" or "5.7.3.2012070310003".

VersionConstraint - A boolean expression that can check versions for applicability. In string format, it is represented as a comma-separated list of specific versions, closed ranges (expressed using "[1.2.3-4.5)" syntax and open ranges (expressed using "[1.2.3" or "4.5)" syntax). The square brackets indicate that the range includes the specified version. The parenthesis indicate that the range goes up to, but does not actually include the specified version.

Sapphire.version() - Determines the version of Sapphire.

Both Version and VersionConstraint classes can be used as a type of a value property.

Example

// *** Version ***

@Type( base = Version.class )

ValueProperty PROP_VERSION = new ValueProperty( TYPE, "Version" );

Value<Version> getVersion();
void setVersion( String value );
void setVersion( Version value );

// *** VersionConstraint ***

@Type( base = VersionConstraint.class )

ValueProperty PROP_VERSION_CONSTRAINT = new ValueProperty( TYPE, "VersionConstraint" );

Value<VersionConstraint> getVersionConstraint();
void setVersionConstraint( String value );
void setVersionConstraint( VersionConstraint value );

Further, version constraints can be evaluated in an expression via a pair of new functions. The VersionMatches function takes a version as the first parameter, a version constraint as a second parameter and returns a boolean. The SapphireVersionMatches function takes a version constraint as the sole parameter, evaluates it against Sapphire version and returns a boolean.

Example

In this example, the VersionMatches function is used to control property enablement.

// *** Provider ***

@Label( standard = "provider" )
@Enablement( expr = "${ VersionMatches( Root().Version, '[1.1' ) }" )
@XmlBinding( path = "provider" )

ValueProperty PROP_PROVIDER = new ValueProperty( TYPE, "Provider" );

Value<String> getProvider();
void setProvider( String value );

Example

In this example, the VersionMatches function is used in sdef to control visibility of a properties view page.

<properties-view>
    <page>
        <label>provider</label>
        <visible-when>${ VersionMatches( Root().Version, '[1.1' ) }</visible-when>
        <content>
            <property-editor>Provider</property-editor>
            <property-editor>
                <property>Copyright</property>
                <scale-vertically>true</scale-vertically>
            </property-editor>
        </content>
    </page>
</properties-view>

Even simpler, version compatibility can be attached to a property by using an @Since or an @VersionCompatibility annotation. This will configure enablement, validation and visibility. The @VersionCompatibilityTarget annotation works in conjunction with these annotations to specify the current version. This annotation is typically placed on the root element of the model and typically references a property that defines the version.

Example

@VersionCompatibilityTarget( version = "${ Version }", versioned = "Purchase Order" )
@GenerateImpl

public interface PurchaseOrder extends IModelElement
{
    ModelElementType TYPE = new ModelElementType( PurchaseOrder.class );

    // *** Version ***

    @Type( base = Version.class )
    @DefaultValue( text = "2.0" )

    ValueProperty PROP_VERSION = new ValueProperty( TYPE, "Version" );

    Value<Version> getVersion();
    void setVersion( String value );
    void setVersion( Version value );

    // *** Id ***

    @Required

    ValueProperty PROP_ID = new ValueProperty( TYPE, "Id" );

    Value<String> getId();
    void setId( String value );

    // *** Customer ***

    @Required

    ValueProperty PROP_CUSTOMER = new ValueProperty( TYPE, "Customer" );

    Value<String> getCustomer();
    void setCustomer( String value );

    // *** InitialQuoteDate ***

    @Type( base = Date.class )
    @Since( "1.5" )

    ValueProperty PROP_INITIAL_QUOTE_DATE = new ValueProperty( TYPE, "InitialQuoteDate" );

    Value<Date> getInitialQuoteDate();
    void setInitialQuoteDate( String value );
    void setInitialQuoteDate( Date value );

    // *** OrderDate ***

    @Type( base = Date.class )

    ValueProperty PROP_ORDER_DATE = new ValueProperty( TYPE, "OrderDate" );

    Value<Date> getOrderDate();
    void setOrderDate( String value );
    void setOrderDate( Date value );

    // *** FulfillmentDate ***

    @Type( base = Date.class )
    @Since( "2.0" )

    ValueProperty PROP_FULFILLMENT_DATE = new ValueProperty( TYPE, "FulfillmentDate" );

    Value<Date> getFulfillmentDate();
    void setFulfillmentDate( String value );
    void setFulfillmentDate( Date value );

    // *** BillingInformation ***

    @Type( base = BillingInformation.class )

    ImpliedElementProperty PROP_BILLING_INFORMATION = new ImpliedElementProperty( TYPE, "BillingInformation" );

    BillingInformation getBillingInformation();

    // *** ShippingInformation ***

    @Type( base = ShippingInformation.class )
    @Since( "2.0" )

    ImpliedElementProperty PROP_SHIPPING_INFORMATION = new ImpliedElementProperty( TYPE, "ShippingInformation" );

    ShippingInformation getShippingInformation();

    // *** Payment ***

    @Type( base = Payment.class, possible = { CreditCardPayment.class, CheckPayment.class, CashPayment.class } )
    @Label( standard = "payment" )
    @Since( "1.5" )

    ElementProperty PROP_PAYMENT = new ElementProperty( TYPE, "Payment" );

    ModelElementHandle<Payment> getPayment();
}

With the default 2.0 version, all of the purchase order fields are visible.

When user changes the version to 1.0, the fulfillment date, payment information and shipping information fields are automatically hidden. The initial quote date field remains visible because it has a value. A validation error alerts the user to the issue.

If the user chooses to resolve the version compatibility validation error by removing the initial quote date, the field automatically disappears.

If more flexibility is necessary, the annotations can be replaced by VersionCompatibilityService and VersionCompatibilityTargetService implementations.

Example

public class ExampleVersionCompatibilityService extends VersionCompatibilityService
{
    private VersionCompatibilityTargetService versionCompatibilityTargetService;
    private Listener versionCompatibilityTargetServiceListener;

    protected void initVersionCompatibilityService()
    {
        final IModelElement element = context( IModelElement.class );
        final ModelProperty property = context( ModelProperty.class );

        this.versionCompatibilityTargetService = VersionCompatibilityTargetService.find( element, property );

        this.versionCompatibilityTargetServiceListener = new Listener()
        {
            @Override
            public void handle( final Event event )
            {
                refresh();
            }
        };

        this.versionCompatibilityTargetService.attach( this.versionCompatibilityTargetServiceListener );
    }

    @Override
    protected Data compute()
    {
        final Version version = this.versionCompatibilityTargetService.version();
        final String versioned = this.versionCompatibilityTargetService.versioned();

        final boolean compatible = ...

        return new Data( compatible, version, versioned );
    }

    @Override
    public void dispose()
    {
        super.dispose();

        if( this.versionCompatibilityTargetService != null )
        {
            this.versionCompatibilityTargetService.detach( this.versionCompatibilityTargetServiceListener );
        }
    }
}
public class ExampleVersionCompatibilityTargetService extends VersionCompatibilityTargetService
{
    @Override
    protected void initContextVersionService()
    {
        // Listen on the source of the version and call refresh() when necessary.
    }

    @Override
    protected Data compute()
    {
        Version version = ...
        String versioned = ...

        return new Data( version, versioned );
    }

    @Override
    public void dispose()
    {
        super.dispose();

        // Detach any listeners attached in the initContextVersionService() method.
    }
}