Package org.slim3.datastore

Source Code of org.slim3.datastore.Lock

/*
* Copyright 2004-2010 the Seasar Foundation and the Others.
*
* 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 org.slim3.datastore;

import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Map;

import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.datastore.Query.FilterOperator;

/**
* A class to lock a target entity.
*
* @author higa
* @since 1.0.0
*
*/
public class Lock {

    /**
     * The kind of lock entity.
     */
    public static final String KIND = "slim3.Lock";

    /**
     * The globalTransactionKey property name.
     */
    public static final String GLOBAL_TRANSACTION_KEY_PROPERTY =
        "globalTransactionKey";

    /**
     * The timestamp property name.
     */
    public static final String TIMESTAMP_PROPERTY = "timestampType";

    /**
     * The timeout.
     */
    protected static final long TIMEOUT = 30 * 1000;

    /**
     * The asynchronous datastore service.
     */
    protected AsyncDatastoreService ds;

    /**
     * The key.
     */
    protected Key key;

    /**
     * The root key.
     */
    protected Key rootKey;

    /**
     * The time-stamp.
     */
    protected long timestamp;

    /**
     * The global transaction key.
     */
    protected Key globalTransactionKey;

    /**
     * Creates a key for lock.
     *
     * @param rootKey
     *            the root key
     * @return a key
     * @throws NullPointerException
     *             if the targetKey parameter is null
     */
    public static Key createKey(Key rootKey) throws NullPointerException {
        if (rootKey == null) {
            throw new NullPointerException("The target key must not be null.");
        }
        if (rootKey.getParent() != null) {
            throw new IllegalArgumentException("The key("
                + rootKey
                + ") must be a root.");
        }
        return KeyFactory.createKey(rootKey, KIND, 1);
    }

    /**
     * Converts the entity to a {@link Lock}.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param entity
     *            an entity
     *
     * @return a {@link Lock}
     * @throws NullPointerException
     *             if the ds parameter is null or if the entity property is null
     * @throws IllegalArgumentException
     *             if the kind of the entity is not slim3.Lock
     */
    public static Lock toLock(AsyncDatastoreService ds, Entity entity)
            throws NullPointerException, IllegalArgumentException {
        if (entity == null) {
            throw new NullPointerException(
                "The entity parameter must not be null.");
        }
        if (!KIND.equals(entity.getKind())) {
            throw new IllegalArgumentException("The kind("
                + entity.getKind()
                + ") of the entity("
                + entity.getKey()
                + ") must be "
                + KIND
                + ".");
        }
        Key globalTransactionKey =
            (Key) entity.getProperty(GLOBAL_TRANSACTION_KEY_PROPERTY);
        Long timestamp = (Long) entity.getProperty(TIMESTAMP_PROPERTY);
        return new Lock(
            ds,
            globalTransactionKey,
            entity.getKey().getParent(),
            timestamp);
    }

    /**
     * Returns a {@link Lock} specified by the key. Returns null if no entity is
     * found.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param tx
     *            the transaction
     * @param key
     *            the key
     *
     * @return a {@link Lock} specified by the key
     * @throws NullPointerException
     *             if the ds parameter is null
     */
    public static Lock getOrNull(AsyncDatastoreService ds, Transaction tx,
            Key key) throws NullPointerException {
        Entity entity = DatastoreUtil.getOrNull(ds, tx, key);
        if (entity == null) {
            return null;
        }
        return toLock(ds, entity);
    }

    /**
     * Returns keys specified by the global transaction key.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param globalTransactionKey
     *            the global transaction key
     * @return a list of keys
     * @throws NullPointerException
     *             if the ds parameter is null or if the globalTransactionKey
     *             parameter is null
     */
    public static List<Key> getKeys(AsyncDatastoreService ds,
            Key globalTransactionKey) throws NullPointerException {
        if (ds == null) {
            throw new NullPointerException("The ds parameter must not be null.");
        }
        if (globalTransactionKey == null) {
            throw new NullPointerException(
                "The globalTransactionKey parameter must not be null.");
        }
        return new EntityQuery(ds, KIND).filter(
            new Query.FilterPredicate(
            GLOBAL_TRANSACTION_KEY_PROPERTY,
            FilterOperator.EQUAL,
            globalTransactionKey)).asKeyList();
    }

    /**
     * Deletes entities specified by the global transaction key in transaction.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param globalTransactionKey
     *            the global transaction key
     * @throws NullPointerException
     *             if the ds parameter is null or if the globalTransactionKey
     *             parameter is null
     *
     */
    public static void deleteInTx(AsyncDatastoreService ds,
            Key globalTransactionKey) throws NullPointerException {
        if (ds == null) {
            throw new NullPointerException("The ds parameter must not be null.");
        }
        if (globalTransactionKey == null) {
            throw new NullPointerException(
                "The globalTransactionKey parameter must not be null.");
        }
        for (Key key : getKeys(ds, globalTransactionKey)) {
            deleteInTx(ds, globalTransactionKey, key);
        }
    }

    /**
     * Deletes the locks from the datastore in transaction.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param globalTransactionKey
     *            the global transaction key
     * @param locks
     *            the locks
     * @throws NullPointerException
     *             if the ds parameter is null or if the globalTransactionKey
     *             parameter is null or if the locks parameter is null
     *
     */
    public static void deleteInTx(AsyncDatastoreService ds,
            Key globalTransactionKey, Iterable<Lock> locks)
            throws NullPointerException {
        if (ds == null) {
            throw new NullPointerException("The ds parameter must not be null.");
        }
        if (globalTransactionKey == null) {
            throw new NullPointerException(
                "The globalTransactionKey parameter must not be null.");
        }
        if (locks == null) {
            throw new NullPointerException(
                "The locks parameter must not be null.");
        }
        for (Lock lock : locks) {
            deleteInTx(ds, globalTransactionKey, lock.key);
        }
    }

    /**
     * Deletes an entity specified by the key in transaction.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param globalTransactionKey
     *            the global transaction key
     * @param key
     *            the key
     *
     * @throws NullPointerException
     *             if the ds parameter is null or if the globalTransactionKey
     *             parameter is null or if the key parameter is null
     */
    protected static void deleteInTx(AsyncDatastoreService ds,
            Key globalTransactionKey, Key key) throws NullPointerException {
        if (ds == null) {
            throw new NullPointerException("The ds parameter must not be null.");
        }
        if (globalTransactionKey == null) {
            throw new NullPointerException(
                "The globalTransactionKey parameter must not be null.");
        }
        if (key == null) {
            throw new NullPointerException(
                "The key parameter must not be null.");
        }
        Transaction tx = DatastoreUtil.beginTransaction(ds);
        try {
            Lock lock = getOrNull(ds, tx, key);
            if (lock != null
                && globalTransactionKey.equals(lock.globalTransactionKey)) {
                DatastoreUtil.delete(ds, tx, key);
                tx.commit();
            }
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }
    }

    /**
     * Deletes entities specified by the global transaction key without
     * transaction.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param globalTransactionKey
     *            the global transaction key
     * @throws NullPointerException
     *             if the ds parameter is null or if the globalTransactionKey
     *             parameter is null
     */
    public static void deleteWithoutTx(AsyncDatastoreService ds,
            Key globalTransactionKey) throws NullPointerException {
        DatastoreUtil.delete(ds, null, getKeys(ds, globalTransactionKey));
    }

    /**
     * Deletes the locks without transaction.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param locks
     *            the locks
     * @throws NullPointerException
     *             if the ds parameter is null or if the locks parameter is null
     */
    public static void deleteWithoutTx(AsyncDatastoreService ds,
            Iterable<Lock> locks) throws NullPointerException {
        if (ds == null) {
            throw new NullPointerException("The ds parameter must not be null.");
        }
        if (locks == null) {
            throw new NullPointerException(
                "The locks parameter must not be null.");
        }
        List<Key> keys = new ArrayList<Key>();
        for (Lock lock : locks) {
            keys.add(lock.key);
        }
        if (keys.isEmpty()) {
            return;
        }
        DatastoreUtil.delete(ds, null, keys);
    }

    /**
     * Verifies lock specified by the root key and returns entities specified by
     * the keys as map.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param tx
     *            the transaction
     * @param rootKey
     *            the root key
     * @param keys
     *            the keys
     * @return an entity
     * @throws NullPointerException
     *             if the ds parameter is null or if the tx parameter is null or
     *             if the rootKey parameter is null or if the keys parameter is
     *             null
     *
     * @throws ConcurrentModificationException
     *             if locking the entity group failed
     */
    public static Map<Key, Entity> verifyAndGetAsMap(AsyncDatastoreService ds,
            Transaction tx, Key rootKey, Collection<Key> keys)
            throws NullPointerException, ConcurrentModificationException {
        if (ds == null) {
            throw new NullPointerException("The ds parameter must not be null.");
        }
        if (tx == null) {
            throw new NullPointerException("The tx parameter must not be null.");
        }
        if (rootKey == null) {
            throw new NullPointerException(
                "The rootKey parameter must not be null.");
        }
        if (keys == null) {
            throw new NullPointerException(
                "The keys parameter must not be null.");
        }
        Key lockKey = createKey(rootKey);
        List<Key> keyList = new ArrayList<Key>(keys.size() + 1);
        keyList.addAll(keys);
        keyList.add(lockKey);
        Map<Key, Entity> map = DatastoreUtil.getAsMap(ds, tx, keyList);
        if (map.containsKey(lockKey)) {
            throw createConcurrentModificationException(rootKey);
        }
        return map;
    }

    /**
     * Creates a {@link ConcurrentModificationException}.
     *
     * @param rootKey
     *            the root key
     *
     * @return a {@link ConcurrentModificationException}
     * @throws NullPointerException
     *             if the cause parameter is null
     */
    protected static ConcurrentModificationException createConcurrentModificationException(
            Key rootKey) throws NullPointerException {
        if (rootKey == null) {
            throw new NullPointerException(
                "The rootKey parameter must not be null.");
        }
        return new ConcurrentModificationException("Locking the entity group("
            + rootKey
            + ") failed.");
    }

    /**
     * Creates a {@link ConcurrentModificationException}.
     *
     * @param rootKey
     *            the root key
     * @param cause
     *            the cause
     *
     * @return a {@link ConcurrentModificationException}
     * @throws NullPointerException
     *             if the rootKey parameter is null or if the cause parameter is
     *             null
     */
    protected static ConcurrentModificationException createConcurrentModificationException(
            Key rootKey, Throwable cause) throws NullPointerException {
        if (rootKey == null) {
            throw new NullPointerException(
                "The rootKey parameter must not be null.");
        }
        if (cause == null) {
            throw new NullPointerException(
                "The cause parameter must not be null.");
        }
        ConcurrentModificationException cme =
            new ConcurrentModificationException("Locking the entity group("
                + rootKey
                + ") failed.");
        cme.initCause(cause);
        return cme;
    }

    /**
     * Constructor.
     *
     * @param ds
     *            the asynchronous datastore service
     * @param globalTransactionKey
     *            the global transaction key
     * @param rootKey
     *            the root key
     * @param timestamp
     *            the time-stamp
     *
     * @throws NullPointerException
     *             if the ds parameter is null or if the rootKey parameter is
     *             null or if the timestamp parameter is null or if the
     *             globalTransactionKey parameter is null
     */
    public Lock(AsyncDatastoreService ds, Key globalTransactionKey,
            Key rootKey, Long timestamp) throws NullPointerException {
        if (ds == null) {
            throw new NullPointerException("The ds parameter must not be null.");
        }
        if (globalTransactionKey == null) {
            throw new NullPointerException(
                "The globalTranactionKey parameter must not be null.");
        }
        if (rootKey == null) {
            throw new NullPointerException(
                "The rootKey parameter must not be null.");
        }
        if (timestamp == null) {
            throw new NullPointerException(
                "The timestamp parameter must not be null.");
        }
        this.ds = ds;
        this.globalTransactionKey = globalTransactionKey;
        this.key = createKey(rootKey);
        this.rootKey = rootKey;
        this.timestamp = timestamp;
    }

    /**
     * Converts this instance to an entity.
     *
     * @return an entity
     */
    public Entity toEntity() {
        Entity entity = new Entity(key);
        entity.setProperty(
            GLOBAL_TRANSACTION_KEY_PROPERTY,
            globalTransactionKey);
        entity.setProperty(TIMESTAMP_PROPERTY, timestamp);
        return entity;
    }

    /**
     * Locks the entity group.
     *
     * @throws ConcurrentModificationException
     *             if locking the entity failed
     */
    public void lock() throws ConcurrentModificationException {
        Transaction tx = DatastoreUtil.beginTransaction(ds);
        try {
            Lock other = getOrNull(ds, tx, key);
            if (other != null) {
                verify(other);
            }
            DatastoreUtil.put(ds, tx, toEntity());
            tx.commit();
            return;
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }
    }

    /**
     * Locks the entity group and returns entities specified by the target keys.
     *
     * @param targetKeys
     *            the target keys
     * @return an entity specified by the target keys
     * @throws NullPointerException
     *             if the targetKeys parameter is null
     * @throws ConcurrentModificationException
     *             if locking the entity failed
     */
    public Map<Key, Entity> lockAndGetAsMap(Collection<Key> targetKeys)
            throws NullPointerException, ConcurrentModificationException {
        Map<Key, Entity> map = null;
        Transaction tx = DatastoreUtil.beginTransaction(ds);
        try {
            List<Key> keyList = new ArrayList<Key>(targetKeys.size() + 1);
            keyList.addAll(targetKeys);
            keyList.add(key);
            map = DatastoreUtil.getAsMap(ds, tx, keyList);
            Entity otherEntity = map.remove(key);
            if (otherEntity != null) {
                Lock other = toLock(ds, otherEntity);
                verify(other);
            }
            DatastoreUtil.put(ds, tx, toEntity());
            tx.commit();
            return map;
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }
    }

    /**
     * Verifies the other {@link Lock}.
     *
     * @param other
     *            the other {@link Lock}
     * @throws NullPointerException
     *             if the other parameter is null
     * @throws ConcurrentModificationException
     *             if the entity group is locked by the other one
     */
    protected void verify(Lock other) throws NullPointerException,
            ConcurrentModificationException {
        if (other == null) {
            throw new NullPointerException(
                "The other parameter must not be null.");
        }
        if (globalTransactionKey.equals(other.globalTransactionKey)) {
            return;
        }
        if (timestamp <= other.getTimestamp() + TIMEOUT) {
            throw createConcurrentModificationException(rootKey);
        }
        Transaction tx = DatastoreUtil.beginTransaction(ds);
        try {
            GlobalTransaction gtx =
                GlobalTransaction.getOrNull(ds, tx, other.globalTransactionKey);
            if (gtx != null) {
                if (gtx.valid) {
                    throw createConcurrentModificationException(rootKey);
                }
                return;
            }
            gtx = new GlobalTransaction(ds, other.globalTransactionKey, false);
            GlobalTransaction.put(ds, tx, gtx);
            tx.commit();
        } catch (ConcurrentModificationException e) {
            throw createConcurrentModificationException(rootKey, e);
        } finally {
            if (tx.isActive()) {
                tx.rollback();
            }
        }
    }

    /**
     * Returns the global transaction key.
     *
     * @return the global transaction key
     */
    public Key getGlobalTransactionKey() {
        return globalTransactionKey;
    }

    /**
     * Returns the key.
     *
     * @return the key
     */
    public Key getKey() {
        return key;
    }

    /**
     * Returns the root key.
     *
     * @return the root key
     */
    public Key getRootKey() {
        return rootKey;
    }

    /**
     * Returns the time-stamp.
     *
     * @return the time-stamp
     */
    public long getTimestamp() {
        return timestamp;
    }
}
TOP

Related Classes of org.slim3.datastore.Lock

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.