/*
 * Copyright 2016-2017 the original author or authors.
 *
 * 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.glowroot.central.repo;

import java.util.List;

import javax.annotation.Nullable;

import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;

import org.glowroot.central.util.Cache;
import org.glowroot.central.util.Cache.CacheLoader;
import org.glowroot.central.util.ClusterManager;
import org.glowroot.common.config.ImmutableRoleConfig;
import org.glowroot.common.config.RoleConfig;
import org.glowroot.common.repo.ConfigRepository.DuplicateRoleNameException;

import static com.google.common.base.Preconditions.checkNotNull;

public class RoleDao {

    private static final String WITH_LCS =
            "with compaction = { 'class' : 'LeveledCompactionStrategy' }";

    private static final String ALL_ROLES_SINGLE_CACHE_KEY = "x";

    private final Session session;

    private final PreparedStatement readPS;
    private final PreparedStatement insertIfNotExistsPS;
    private final PreparedStatement insertPS;
    private final PreparedStatement deletePS;

    private final PreparedStatement readOnePS;

    private final Cache<String, Optional<RoleConfig>> roleConfigCache;
    private final Cache<String, List<RoleConfig>> allRoleConfigsCache;

    public RoleDao(Session session, KeyspaceMetadata keyspaceMetadata,
            ClusterManager clusterManager) {
        this.session = session;

        boolean createAnonymousRole = keyspaceMetadata.getTable("role") == null;

        session.execute("create table if not exists role (name varchar, permissions set<varchar>,"
                + " primary key (name)) " + WITH_LCS);

        readPS = session.prepare("select name, permissions from role");
        insertIfNotExistsPS =
                session.prepare("insert into role (name, permissions) values (?, ?) if not exists");
        insertPS = session.prepare("insert into role (name, permissions) values (?, ?)");
        deletePS = session.prepare("delete from role where name = ?");

        readOnePS = session.prepare("select name, permissions from role where name = ?");

        if (createAnonymousRole) {
            BoundStatement boundStatement = insertIfNotExistsPS.bind();
            int i = 0;
            boundStatement.setString(i++, "Administrator");
            boundStatement.setSet(i++, ImmutableSet.of("agent:*:transaction", "agent:*:error",
                    "agent:*:jvm", "agent:*:config", "admin"));
            session.execute(boundStatement);
        }

        roleConfigCache =
                clusterManager.createCache("roleConfigCache", new RoleConfigCacheLoader());
        allRoleConfigsCache =
                clusterManager.createCache("allRoleConfigsCache", new AllRolesCacheLoader());
    }

    List<RoleConfig> read() throws Exception {
        return allRoleConfigsCache.get(ALL_ROLES_SINGLE_CACHE_KEY);
    }

    @Nullable
    RoleConfig read(String name) throws Exception {
        return roleConfigCache.get(name).orNull();
    }

    void delete(String name) throws Exception {
        BoundStatement boundStatement = deletePS.bind();
        boundStatement.setString(0, name);
        session.execute(boundStatement);
        roleConfigCache.invalidate(name);
        allRoleConfigsCache.invalidate(ALL_ROLES_SINGLE_CACHE_KEY);
    }

    void insert(RoleConfig userConfig) throws Exception {
        BoundStatement boundStatement = insertPS.bind();
        bindInsert(boundStatement, userConfig);
        session.execute(boundStatement);
        roleConfigCache.invalidate(userConfig.name());
        allRoleConfigsCache.invalidate(ALL_ROLES_SINGLE_CACHE_KEY);

    }

    void insertIfNotExists(RoleConfig userConfig) throws Exception {
        BoundStatement boundStatement = insertIfNotExistsPS.bind();
        bindInsert(boundStatement, userConfig);
        ResultSet results = session.execute(boundStatement);
        Row row = checkNotNull(results.one());
        boolean applied = row.getBool("[applied]");
        if (applied) {
            roleConfigCache.invalidate(userConfig.name());
            allRoleConfigsCache.invalidate(ALL_ROLES_SINGLE_CACHE_KEY);
        } else {
            throw new DuplicateRoleNameException();
        }
    }

    private static void bindInsert(BoundStatement boundStatement, RoleConfig userConfig) {
        int i = 0;
        boundStatement.setString(i++, userConfig.name());
        boundStatement.setSet(i++, userConfig.permissions());
    }

    private static ImmutableRoleConfig buildRole(Row row) {
        int i = 0;
        return ImmutableRoleConfig.builder()
                .central(true)
                .name(checkNotNull(row.getString(i++)))
                .permissions(row.getSet(i++, String.class))
                .build();
    }

    private class RoleConfigCacheLoader implements CacheLoader<String, Optional<RoleConfig>> {
        @Override
        public Optional<RoleConfig> load(String name) {
            BoundStatement boundStatement = readOnePS.bind();
            boundStatement.setString(0, name);
            ResultSet results = session.execute(boundStatement);
            if (results.isExhausted()) {
                return Optional.absent();
            }
            Row row = results.one();
            if (!results.isExhausted()) {
                throw new IllegalStateException("Multiple role records for name: " + name);
            }
            return Optional.of(buildRole(row));
        }
    }

    private class AllRolesCacheLoader implements CacheLoader<String, List<RoleConfig>> {
        @Override
        public List<RoleConfig> load(String dummy) {
            ResultSet results = session.execute(readPS.bind());
            List<RoleConfig> users = Lists.newArrayList();
            for (Row row : results) {
                users.add(buildRole(row));
            }
            return users;
        }
    }
}

@@ -3189,16 +3189,36 @@
 Set(i++,
+%0A                   
  Immutab
@@ -3265,17 +3265,40 @@
 :error%22,
-%0A
+ %22agent:*:jvm%22,%0A        
         
@@ -3322,31 +3322,89 @@
 t:*:
-jvm%22, %22agent:*:config%22,
+syntheticMonitor%22, %22agent:*:alert%22, %22agent:*:config%22,%0A                           
  %22ad

Add missing permissions to default role