public boolean addUserToTopic(final String topicKey,
                                  final String userKey,
                                  final String userName,
                                  final String channelToken) throws TopicAccessException {
        assert topicKey != null && topicKey.trim().length() > 0;
        assert userKey != null && userKey.trim().length() > 0;
        assert userName != null && userName.trim().length() > 0;
        assert channelToken != null && channelToken.trim().length() > 0;

        try {
            return new TransactionRunner<Boolean>(getDatastore()) {
                protected Boolean txnBlock() {
                    try {
                        Entity subscriberEntity = datastore.get(subscriberEntityKey(topicKey, userKey));
                        if ((Boolean)subscriberEntity.getProperty(IS_DELETED_PROP)) {
                            //if deleted then treat it like it wasn't found in the first place
                            throw new EntityNotFoundException(subscriberEntity.getKey());
                        }
                        return false; //subscriber already existed
                    } catch (EntityNotFoundException enfe) {
                        //then need to create subscriber
                        Entity subscriberEntity = new Entity(SUBSCRIBER_KIND, userKey, topicEntityKey(topicKey));
                        subscriberEntity.setProperty(IS_DELETED_PROP, false);
                        subscriberEntity.setProperty(USER_NAME_PROP, userName);
                        subscriberEntity.setUnindexedProperty(CHANNEL_TOKEN_PROP, channelToken);
                        subscriberEntity.setUnindexedProperty(MESSAGE_NUM_PROP, 0);
                        datastore.put(subscriberEntity);
                        return true;
                    }
                }
            }.run();
        } catch (Exception e) {
            throw new TopicAccessException("Error adding user " + userKey + " to topic " + topicKey, e);
        }
    }