com.avanza.ymer.MirroredObject.java Source code

Java tutorial

Introduction

Here is the source code for com.avanza.ymer.MirroredObject.java

Source

/*
 * Copyright 2015 Avanza Bank AB
 *
 * 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.avanza.ymer;

import java.lang.reflect.Method;

import com.gigaspaces.annotation.pojo.SpaceId;
import com.gigaspaces.annotation.pojo.SpaceRouting;
import com.mongodb.BasicDBObject;

/**
 * Holds information about one mirrored space object type.
 *
 * @author Elias Lindholm, Joakim Sahlstrom
 *
 */
final class MirroredObject<T> {

    public static final String DOCUMENT_FORMAT_VERSION_PROPERTY = "_formatVersion";
    public static final String DOCUMENT_ROUTING_KEY = "_routingKey";
    private final DocumentPatchChain<T> patchChain;
    private final RoutingKeyExtractor routingKeyExtractor;
    private final boolean excludeFromInitialLoad;
    private final boolean writeBackPatchedDocuments;
    private final boolean loadDocumentsRouted;
    private final boolean keepPersistent;
    private final String collectionName;
    private final TemplateFactory customInitialLoadTemplateFactory;

    public MirroredObject(MirroredObjectDefinition<T> definition) {
        this.patchChain = definition.createPatchChain();
        this.routingKeyExtractor = findRoutingKeyMethod(patchChain.getMirroredType());
        this.excludeFromInitialLoad = definition.excludeFromInitialLoad();
        this.writeBackPatchedDocuments = definition.writeBackPatchedDocuments();
        this.loadDocumentsRouted = definition.loadDocumentsRouted();
        this.keepPersistent = definition.keepPersistent();
        this.collectionName = definition.collectionName();
        this.customInitialLoadTemplateFactory = definition.customInitialLoadTemplateFactory();
    }

    private RoutingKeyExtractor findRoutingKeyMethod(Class<T> mirroredType) {
        for (Method m : mirroredType.getMethods()) {
            if (m.isAnnotationPresent(SpaceRouting.class) && !m.isAnnotationPresent(SpaceId.class)) {
                return new RoutingKeyExtractor.InstanceMethod(m);
            }
        }
        for (Method m : mirroredType.getMethods()) {
            if (m.isAnnotationPresent(SpaceId.class)) {
                if (RoutingKeyExtractor.GsAutoGenerated.isApplicable(m)) {
                    return new RoutingKeyExtractor.GsAutoGenerated(m);
                } else {
                    return new RoutingKeyExtractor.InstanceMethod(m);
                }
            }
        }
        throw new IllegalArgumentException(
                "Cannot find @SpaceRouting or @SpaceId method for: " + mirroredType.getName());
    }

    Class<T> getMirroredType() {
        return patchChain.getMirroredType();
    }

    /**
     * Checks whether a given document requires patching. <p>
     *
     * @param dbObject
     * @throws UnknownDocumentVersionException if the version of the given document is unknown
     *
     * @return
     */
    boolean requiresPatching(BasicDBObject dbObject) {
        int documentVersion = getDocumentVersion(dbObject);
        verifyKnownVersion(documentVersion, dbObject);
        return documentVersion != getCurrentVersion();
    }

    private void verifyKnownVersion(int documentVersion, BasicDBObject dbObject) {
        if (!isKnownVersion(documentVersion)) {
            throw new UnknownDocumentVersionException(String.format(
                    "Unknown document version %s, oldest known version is: %s, current version is : %s. document=%s",
                    documentVersion, getOldestKnownVersion(), getCurrentVersion(), dbObject));
        }
    }

    int getDocumentVersion(BasicDBObject dbObject) {
        return dbObject.getInt(DOCUMENT_FORMAT_VERSION_PROPERTY, 1);
    }

    void setDocumentVersion(BasicDBObject dbObject, int version) {
        dbObject.put(DOCUMENT_FORMAT_VERSION_PROPERTY, version);
    }

    void setDocumentAttributes(BasicDBObject dbObject, T spaceObject) {
        setDocumentVersion(dbObject);
        if (loadDocumentsRouted) {
            setRoutingKey(dbObject, spaceObject);
        }
    }

    private void setDocumentVersion(BasicDBObject dbObject) {
        dbObject.put(DOCUMENT_FORMAT_VERSION_PROPERTY, getCurrentVersion());
    }

    private void setRoutingKey(BasicDBObject dbObject, T spaceObject) {
        Object routingKey = routingKeyExtractor.getRoutingKey(spaceObject);
        if (routingKey != null) {
            dbObject.put(DOCUMENT_ROUTING_KEY, routingKey.hashCode());
        }
    }

    int getCurrentVersion() {
        if (this.patchChain.isEmpty()) {
            return 1;
        }
        return this.patchChain.getLastPatchInChain().patchedVersion() + 1;
    }

    int getOldestKnownVersion() {
        if (this.patchChain.isEmpty()) {
            return getCurrentVersion();
        }
        return this.patchChain.getFirstPatchInChain().patchedVersion();
    }

    /**
     * Patches the given document to the current version. <p>
     *
     * The argument document will not be mutated. <p>
     *
     * @param dbObject
     * @return
     */
    BasicDBObject patch(BasicDBObject dbObject) {
        if (!requiresPatching(dbObject)) {
            throw new IllegalArgumentException("Document does not require patching: " + dbObject.toString());
        }
        while (requiresPatching(dbObject)) {
            patchToNextVersion(dbObject);
        }
        return dbObject;
    }

    /**
     * Patches the given document to the next version by writing mutating the passed in document. <p>
     *
     * @param dbObject
     */
    void patchToNextVersion(BasicDBObject dbObject) {
        if (!requiresPatching(dbObject)) {
            throw new IllegalArgumentException("Document does not require patching: " + dbObject.toString());
        }
        DocumentPatch patch = this.patchChain.getPatch(getDocumentVersion(dbObject));
        patch.apply(dbObject);
        setDocumentVersion(dbObject, patch.patchedVersion() + 1);
    }

    /**
     * Returns the name of the collection that the underlying documents will be stored in. <p>
     *
     * @return
     */
    public String getCollectionName() {
        return this.collectionName;
    }

    Object getRoutingKey(T spaceObject) {
        return routingKeyExtractor.getRoutingKey(spaceObject);
    }

    boolean isKnownVersion(int documentVersion) {
        return documentVersion >= getOldestKnownVersion() && documentVersion <= getCurrentVersion();
    }

    boolean excludeFromInitialLoad() {
        return excludeFromInitialLoad;
    }

    boolean writeBackPatchedDocuments() {
        return writeBackPatchedDocuments;
    }

    boolean loadDocumentsRouted() {
        return loadDocumentsRouted;
    }

    public boolean keepPersistent() {
        return keepPersistent;
    }

    public boolean hasCustomInitialLoadTemplate() {
        return this.customInitialLoadTemplateFactory != null;
    }

    public TemplateFactory getCustomInitialLoadTemplateFactory() {
        return this.customInitialLoadTemplateFactory;
    }
}