All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vmware.xenon.common.ServiceDocument Maven / Gradle / Ivy

There is a newer version: 1.6.18
Show newest version
/*
 * Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, without warranties or
 * conditions of any kind, EITHER EXPRESS OR IMPLIED.  See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.vmware.xenon.common;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.EnumSet;

import com.vmware.xenon.common.Service.Action;
import com.vmware.xenon.common.ServiceDocumentDescription.DocumentIndexingOption;
import com.vmware.xenon.common.ServiceDocumentDescription.PropertyIndexingOption;
import com.vmware.xenon.common.ServiceDocumentDescription.PropertyUsageOption;

/**
 * Base implementation class for Service documents. A service document is a PODO (data only object,
 * no methods) that describes the service state. Even dynamic, state less services can have a
 * document representing state-less computation results, or data gathered dynamically from other
 * services
 */
public class ServiceDocument {
    public enum DocumentRelationship {
        IN_CONFLICT,
        EQUAL,
        NEWER_VERSION,
        NEWER_UPDATE_TIME,
        EQUAL_TIME,
        EQUAL_VERSION, PREFERRED
    }

    /**
     * Specifies {@link com.vmware.xenon.common.ServiceDocument} field usage option.
     * This annotation is repeatable.
     * Use {@link PropertyOptions} instead.
     *
     * @see PropertyUsageOption
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Repeatable(UsageOptions.class)
    @Target(ElementType.FIELD)
    public @interface UsageOption {
        /**
         * Field usage option to apply.
         */
        PropertyUsageOption option();
    }

    /**
     * This annotation defines field usage options.
     * Use {@link PropertyOptions} instead.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface UsageOptions {
        /**
         * Field usage options to apply.
         */
        UsageOption[] value();
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.FIELD)
    public @interface PropertyOptions {
        /**
         * Specifies how a field is used
         * @return
         */
        PropertyUsageOption[] usage() default {};

        /**
         * Specifies how a filed should be indexed.
         * @return
         */
        PropertyIndexingOption[] indexing() default {};
    }

    /**
     * Limits about a {@link ServiceDocument}.
     * In the rare case that the same ServiceDocument type is used to
     * represent the state of different services and different limits are
     * needed then {@link Service#getDocumentTemplate()} can still be used to
     * override the values.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface IndexingParameters {
        /**
         * Document indexing options.
         * @return
         */
        DocumentIndexingOption[] indexing() default {};

        /**
         * Max size of a serialized document
         * @return
         */
        int serializedStateSize() default ServiceDocumentDescription.DEFAULT_SERIALIZED_STATE_LIMIT;

        /**
         * Max versions to keep for a document.
         * @return
         */
        int versionRetention() default ServiceDocumentDescription.DEFAULT_VERSION_RETENTION_LIMIT;

        /**
         * Min versions to keep for a document.
         */
        int versionRetentionFloor() default ServiceDocumentDescription.DEFAULT_VERSION_RETENTION_LIMIT / 2;
    }

    /**
     * Annotations for ServiceDocumentDescription
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD, ElementType.TYPE})
    public @interface Documentation {
        // sets property name
        String name() default "";

        // sets propertyDocumentation
        String description() default "";

        // sets exampleValue
        String exampleString() default "";
    }

    public static final String FIELD_NAME_SELF_LINK = "documentSelfLink";
    public static final String FIELD_NAME_VERSION = "documentVersion";
    public static final String FIELD_NAME_EPOCH = "documentEpoch";
    public static final String FIELD_NAME_KIND = "documentKind";
    public static final String FIELD_NAME_UPDATE_TIME_MICROS = "documentUpdateTimeMicros";
    public static final String FIELD_NAME_UPDATE_ACTION = "documentUpdateAction";
    public static final String FIELD_NAME_DESCRIPTION = "documentDescription";
    public static final String FIELD_NAME_OWNER = "documentOwner";
    public static final String FIELD_NAME_SOURCE_LINK = "documentSourceLink";
    public static final String FIELD_NAME_EXPIRATION_TIME_MICROS = "documentExpirationTimeMicros";
    public static final String FIELD_NAME_AUTH_PRINCIPAL_LINK = "documentAuthPrincipalLink";
    public static final String FIELD_NAME_TRANSACTION_ID = "documentTransactionId";

    /**
     * Field names ending with this suffix will be indexed as URI paths
     */
    public static final String FIELD_NAME_SUFFIX_LINK = "Link";
    public static final String FIELD_NAME_SUFFIX_LINKS = "Links";

    /**
     * Field names ending in Address will be indexed as StringFields.
     */
    public static final String FIELD_NAME_SUFFIX_ADDRESS = "Address";

    /**
     * Optional description of the document, populated by the framework on requests to
     * /template
     */
    public ServiceDocumentDescription documentDescription;

    /**
     * Monotonically increasing number indicating the number of updates the local service instance
     * has processed in the current host session or since creation, if the service is durable
     *
     * Infrastructure use only
     */
    public long documentVersion;

    /**
     * Monotonically increasing number associated with a document owner. Each time ownership
     * changes a protocol guarantees new owner will assign a higher epoch number and get consensus
     * between peers before proceeding with replication.
     *
     * This field is not used for non replicated services
     *
     * Infrastructure use only
     */
    public Long documentEpoch;

    /**
     * A structured string identifier for the document PODO type
     *
     * Infrastructure use only
     */
    public String documentKind;

    /**
     * The relative URI path of the service managing this document
     */
    public String documentSelfLink;

    /**
     * Document update time in microseconds since UNIX epoch
     *
     * Infrastructure use only
     */
    public long documentUpdateTimeMicros;

    /**
     * Update action that produced the latest document version
     *
     * Infrastructure use only
     */
    public String documentUpdateAction;

    /**
     * Expiration time in microseconds since UNIX epoch. If a document is found to be expired a
     * running service instance will be deleted and the document will be marked deleted in the index
     */
    public long documentExpirationTimeMicros;

    /**
     * Identity of the node that is assigned this document. Assignment is done through the node
     * group partitioning service and it can change between versions
     *
     * Infrastructure use only
     */
    public String documentOwner;

    /**
     * Link to the document that served as the source for this document. The source document is
     * retrieved during a logical clone operation when this instance was created through a POST to a
     * service factory
     */
    public String documentSourceLink;

    /**
     * The relative URI path of the user principal who owns this document
     *
     * Infrastructure use only
     */
    public String documentAuthPrincipalLink;

    /**
     * Refers to the transaction coordinator: if this particular state version is part of a
     * pending transaction whose coordinator is `/core/transactions/[txid]`, it lets the system
     * hide this version from queries, concurrent transactions and operations -- if they don't
     * explicitly provide this id.
     */
    public String documentTransactionId;

    public void copyTo(ServiceDocument target) {
        target.documentEpoch = this.documentEpoch;
        target.documentDescription = this.documentDescription;
        target.documentOwner = this.documentOwner;
        target.documentSourceLink = this.documentSourceLink;
        target.documentVersion = this.documentVersion;
        target.documentKind = this.documentKind;
        target.documentSelfLink = this.documentSelfLink;
        target.documentUpdateTimeMicros = this.documentUpdateTimeMicros;
        target.documentUpdateAction = this.documentUpdateAction;
        target.documentExpirationTimeMicros = this.documentExpirationTimeMicros;
        target.documentAuthPrincipalLink = this.documentAuthPrincipalLink;
        target.documentTransactionId = this.documentTransactionId;
    }

    public static boolean isDeleted(ServiceDocument document) {
        return document != null && Action.DELETE.toString().equals(document.documentUpdateAction);
    }

    /**
     * Infrastructure use only.
     * Compares two documents and returns an set describing the relationship between them.
     *
     * All relationship flags are relative to the first parameter (stateA). The timeEpsilon is used
     * to determine if the update times between the two documents happened too close to each other
     * to safely determine order.
     *
     * If document A is preferred, the PREFERRED option will be set
     */
    public static EnumSet compare(ServiceDocument stateA,
            ServiceDocument stateB,
            ServiceDocumentDescription desc,
            long timeEpsilon) {

        boolean preferred = false;
        EnumSet results = EnumSet.noneOf(DocumentRelationship.class);
        long timeDifference;

        if (stateB == null) {
            results.add(DocumentRelationship.PREFERRED);
            return results;
        }

        long epochA = stateA.documentEpoch != null ? stateA.documentEpoch : 0;
        long epochB = stateB.documentEpoch != null ? stateB.documentEpoch : 0;

        if (epochA > epochB ||
                (epochA == epochB && stateA.documentVersion > stateB.documentVersion)) {
            results.add(DocumentRelationship.NEWER_VERSION);
            preferred = true;
        } else if (epochA == epochB &&
                stateA.documentVersion == stateB.documentVersion) {
            results.add(DocumentRelationship.EQUAL_VERSION);
        }

        timeDifference = stateA.documentUpdateTimeMicros - stateB.documentUpdateTimeMicros;
        if (timeDifference == 0) {
            results.add(DocumentRelationship.EQUAL_TIME);
        } else if (timeDifference > 0) {
            results.add(DocumentRelationship.NEWER_UPDATE_TIME);
        }

        // TODO We only attempt conflict detection if versions match. Otherwise we pick the highest
        // version which is not always correct. We need to formalize the eventual consistency model
        // and do proper merges for divergent states (due to partitioning, etc). Dotted version
        // vectors and other schemes are relatively easy to implement using our existing tracking
        // data
        if (results.contains(DocumentRelationship.EQUAL_VERSION)) {
            if (Math.abs(timeDifference) < timeEpsilon) {
                // documents changed within time epsilon so we can not safely determine which one
                // is newer.
                if (!ServiceDocument.equals(desc, stateA, stateB)) {
                    results.add(DocumentRelationship.IN_CONFLICT);
                }
            } else if (results.contains(DocumentRelationship.NEWER_UPDATE_TIME)) {
                preferred = true;
            }
        }

        if (preferred) {
            results.add(DocumentRelationship.PREFERRED);
        }

        return results;
    }

    /**
     * Compares ServiceDocument for equality. If they are same, then this method returns true;
     * false otherwise.
     *
     * @param description     ServiceDocumentDescription obtained by calling getDocumentTemplate
     *                        ().documentDescription
     * @param currentDocument current service document instance
     * @param newDocument     new service document instance
     * @return true / false
     */
    public static boolean equals(ServiceDocumentDescription description,
            ServiceDocument currentDocument, ServiceDocument newDocument) {
        if (currentDocument == null || newDocument == null) {
            throw new IllegalArgumentException(
                    "Null Service documents cannot be checked for equality.");
        }
        try {
            String currentSignature = Utils.computeSignature(currentDocument, description);
            String newSignature = Utils.computeSignature(newDocument, description);
            return currentSignature.equals(newSignature);
        } catch (Exception e) {
            if (e instanceof IllegalArgumentException) {
                throw (IllegalArgumentException) e;
            }
            return false;
        }
    }

    /**
     * Returns whether or not the {@code name} is a built-in field or not.
     *
     * @param name Field name
     * @return true/false
     */
    public static boolean isBuiltInDocumentField(String name) {
        switch (name) {
        case ServiceDocument.FIELD_NAME_KIND:
        case ServiceDocument.FIELD_NAME_EXPIRATION_TIME_MICROS:
        case ServiceDocument.FIELD_NAME_OWNER:
        case ServiceDocument.FIELD_NAME_SOURCE_LINK:
        case ServiceDocument.FIELD_NAME_VERSION:
        case ServiceDocument.FIELD_NAME_EPOCH:
        case ServiceDocument.FIELD_NAME_UPDATE_TIME_MICROS:
        case ServiceDocument.FIELD_NAME_UPDATE_ACTION:
        case ServiceDocument.FIELD_NAME_SELF_LINK:
        case ServiceDocument.FIELD_NAME_DESCRIPTION:
        case ServiceDocument.FIELD_NAME_AUTH_PRINCIPAL_LINK:
        case ServiceDocument.FIELD_NAME_TRANSACTION_ID:
            return true;
        default:
            return false;
        }
    }

    /**
     * Returns whether or not the {@code name} should support auto merge by default or not
     *
     * @param name Field name
     * @return true/false
     *
     * @see PropertyUsageOption#AUTO_MERGE_IF_NOT_NULL
     * @see ServiceDocumentDescription
     */
    public static boolean isAutoMergeEnabledByDefaultForField(String name) {
        switch (name) {
        case ServiceDocument.FIELD_NAME_EXPIRATION_TIME_MICROS:
            return true;
        default:
            return false;
        }
    }

    /**
     * Returns whether or not the {@code name} is a built-in field that should be excluded from
     * the service document signature computation.
     *
     * @see PropertyIndexingOption#EXCLUDE_FROM_SIGNATURE
     *
     * @param name Field name
     * @return true/false
     */
    public static boolean isBuiltInSignatureExcludedDocumentField(String name) {
        switch (name) {
        case ServiceDocument.FIELD_NAME_KIND:
        case ServiceDocument.FIELD_NAME_OWNER:
        case ServiceDocument.FIELD_NAME_SOURCE_LINK:
        case ServiceDocument.FIELD_NAME_VERSION:
        case ServiceDocument.FIELD_NAME_EPOCH:
        case ServiceDocument.FIELD_NAME_UPDATE_TIME_MICROS:
        case ServiceDocument.FIELD_NAME_SELF_LINK:
        case ServiceDocument.FIELD_NAME_AUTH_PRINCIPAL_LINK:
        case ServiceDocument.FIELD_NAME_TRANSACTION_ID:
            return true;
        default:
            return false;
        }
    }

    /**
     * Returns whether or not the {@code name} is a built-in field that should be
     * for infrastructure use only.
     *
     * @see PropertyUsageOption#INFRASTRUCTURE
     *
     * @param name Field name
     * @return true/false
     */
    public static boolean isBuiltInInfrastructureDocumentField(String name) {
        switch (name) {
        case ServiceDocument.FIELD_NAME_KIND:
        case ServiceDocument.FIELD_NAME_EXPIRATION_TIME_MICROS:
        case ServiceDocument.FIELD_NAME_OWNER:
        case ServiceDocument.FIELD_NAME_SOURCE_LINK:
        case ServiceDocument.FIELD_NAME_VERSION:
        case ServiceDocument.FIELD_NAME_EPOCH:
        case ServiceDocument.FIELD_NAME_UPDATE_TIME_MICROS:
        case ServiceDocument.FIELD_NAME_SELF_LINK:
        case ServiceDocument.FIELD_NAME_AUTH_PRINCIPAL_LINK:
        case ServiceDocument.FIELD_NAME_TRANSACTION_ID:
            return true;
        default:
            return false;
        }
    }

    /**
     * Returns whether or not the {@code name} is a built-in field that should not be indexed.
     *
     * @param name Field name
     * @return true/false
     */
    public static boolean isBuiltInNonIndexedDocumentField(String name) {
        switch (name) {
        case ServiceDocument.FIELD_NAME_DESCRIPTION:
        case ServiceDocument.FIELD_NAME_UPDATE_ACTION:
            return true;
        default:
            return false;
        }
    }

    public static boolean isBuiltInDocumentFieldWithNullExampleValue(String name) {
        switch (name) {
        case ServiceDocument.FIELD_NAME_AUTH_PRINCIPAL_LINK:
        case ServiceDocument.FIELD_NAME_SOURCE_LINK:
        case ServiceDocument.FIELD_NAME_OWNER:
        case ServiceDocument.FIELD_NAME_TRANSACTION_ID:
        case ServiceDocument.FIELD_NAME_EPOCH:
            return true;
        default:
            return false;
        }
    }

    public static boolean isLink(String name) {
        return name.endsWith(FIELD_NAME_SUFFIX_LINK);
    }

    public static boolean isLinks(String name) {
        return name.endsWith(FIELD_NAME_SUFFIX_LINKS);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy