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

io.stargate.graphql.web.GraphqlCache Maven / Gradle / Ivy

/*
 * Copyright The Stargate 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 io.stargate.graphql.web;

import com.google.errorprone.annotations.FormatMethod;
import com.google.errorprone.annotations.FormatString;
import graphql.GraphQL;
import graphql.execution.AsyncExecutionStrategy;
import graphql.schema.GraphQLSchema;
import io.stargate.auth.AuthenticationService;
import io.stargate.auth.AuthorizationService;
import io.stargate.db.EventListener;
import io.stargate.db.Persistence;
import io.stargate.db.datastore.DataStore;
import io.stargate.db.datastore.DataStoreFactory;
import io.stargate.db.datastore.ResultSet;
import io.stargate.db.datastore.Row;
import io.stargate.db.schema.Keyspace;
import io.stargate.graphql.schema.SchemaFactory;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.cassandra.stargate.db.ConsistencyLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Manages the {@link GraphQL} instances used by our REST resources.
 *
 * 

This includes staying up to date with CQL schema changes. */ public class GraphqlCache implements EventListener { private static final Logger LOG = LoggerFactory.getLogger(GraphqlCache.class); private static final boolean DISABLE_DEFAULT_KEYSPACE = Boolean.getBoolean("stargate.graphql.default_keyspace.disabled"); private final Persistence persistence; private final AuthenticationService authenticationService; private final AuthorizationService authorizationService; private final DataStoreFactory dataStoreFactory; private final GraphQL ddlGraphql; private final String defaultKeyspace; private final ConcurrentMap dmlGraphqls; GraphqlCache( Persistence persistence, AuthenticationService authenticationService, AuthorizationService authorizationService, DataStoreFactory dataStoreFactory) { this.persistence = persistence; this.authenticationService = authenticationService; this.authorizationService = authorizationService; this.dataStoreFactory = dataStoreFactory; this.ddlGraphql = newGraphql( SchemaFactory.newDdlSchema( authenticationService, authorizationService, dataStoreFactory)); DataStore dataStore = dataStoreFactory.createInternal(); this.defaultKeyspace = findDefaultKeyspace(dataStore); this.dmlGraphqls = initDmlGraphqls( persistence, dataStore, authenticationService, authorizationService, dataStoreFactory); persistence.registerEventListener(this); } public GraphQL getDdl() { return ddlGraphql; } public GraphQL getDml(String keyspace, Map headers) { String decoratedName = persistence.decorateKeyspaceName(keyspace, headers); DmlGraphqlReference ref = dmlGraphqls.get(decoratedName); return ref == null ? null : ref.get(); } public GraphQL getDefaultDml() { return defaultKeyspace == null ? null : getDml(defaultKeyspace, Collections.emptyMap()); } /** Populate a default keyspace to allow for omitting the keyspace from the path of requests. */ private static String findDefaultKeyspace(DataStore dataStore) { if (DISABLE_DEFAULT_KEYSPACE) return null; try { CompletableFuture query = dataStore .queryBuilder() .select() .column("keyspace_name") .writeTimeColumn("durable_writes", "wt") .from("system_schema", "keyspaces") .build() .execute(ConsistencyLevel.LOCAL_QUORUM); ResultSet resultSet = query.get(); // Grab the oldest, non-system keyspace to use as default. Optional first = resultSet.rows().stream() .filter(r -> !r.isNull("wt")) .filter(r -> r.getLong("wt") > 0) .filter( r -> { String keyspaceName = r.getString("keyspace_name"); return !keyspaceName.equals("system") && !keyspaceName.equals("data_endpoint_auth") && !keyspaceName.equals("solr_admin") && !keyspaceName.startsWith("system_") && !keyspaceName.startsWith("dse_"); }) .min(Comparator.comparing(r -> r.getLong("wt"))); String defaultKeyspace = first.map(row -> row.getString("keyspace_name")).orElse(null); LOG.debug("Using default keyspace {}", defaultKeyspace); return defaultKeyspace; } catch (Exception e) { LOG.warn("Unable to get default keyspace", e); return null; } } private ConcurrentMap initDmlGraphqls( Persistence persistence, DataStore dataStore, AuthenticationService authenticationService, AuthorizationService authorizationService, DataStoreFactory dataStoreFactory) { ConcurrentMap map = new ConcurrentHashMap<>(); for (Keyspace keyspace : dataStore.schema().keyspaces()) { String keyspaceName = keyspace.name(); LOG.debug("Prepare GraphQL schema for {}", keyspaceName); map.put( keyspaceName, new DmlGraphqlReference( keyspace, persistence, authenticationService, authorizationService, dataStoreFactory)); } return map; } @FormatMethod private void addOrReplaceDmlGraphql( String keyspaceName, @FormatString String reason, Object... reasonArguments) { if (LOG.isDebugEnabled()) { LOG.debug( "Refreshing GraphQL schema for keyspace {} because {}", keyspaceName, String.format(reason, reasonArguments)); } try { DataStore dataStore = dataStoreFactory.createInternal(); Keyspace keyspace = dataStore.schema().keyspace(keyspaceName); if (keyspace == null) { // This happens when come from a notification for a keyspace that was just dropped LOG.debug("Removing GraphQL schema for keyspace {} because it was dropped", keyspaceName); dmlGraphqls.remove(keyspaceName); } else { dmlGraphqls.put( keyspaceName, new DmlGraphqlReference( keyspace, persistence, authenticationService, authorizationService, dataStoreFactory)); } LOG.debug("Done refreshing GraphQL schema for keyspace {}", keyspaceName); } catch (Exception e) { LOG.error("Error while refreshing GraphQL schema for keyspace {}", keyspaceName, e); } } // Schema change callbacks: we refresh a keyspace whenever it gets created or dropped, or anything // inside it changes. // TODO maybe add debouncing mechanism to amortize quick event bursts @Override public void onCreateKeyspace(String keyspaceName) { addOrReplaceDmlGraphql(keyspaceName, "it was created"); } @Override public void onDropKeyspace(String keyspaceName) { // Note that if the keyspace contained any children, we probably already removed the handler // while processing those children's DROP events. DmlGraphqlReference removed = dmlGraphqls.remove(keyspaceName); if (removed != null) { LOG.debug("Removing GraphQL schema for keyspace {} because it was dropped", keyspaceName); } } @Override public void onCreateTable(String keyspaceName, String table) { addOrReplaceDmlGraphql(keyspaceName, "table %s was created", table); } @Override public void onCreateView(String keyspaceName, String view) { addOrReplaceDmlGraphql(keyspaceName, "view %s was created", view); } @Override public void onCreateType(String keyspaceName, String type) { addOrReplaceDmlGraphql(keyspaceName, "type %s was created", type); } @Override public void onCreateFunction(String keyspaceName, String function, List argumentTypes) { addOrReplaceDmlGraphql(keyspaceName, "function %s was created", function); } @Override public void onCreateAggregate(String keyspaceName, String aggregate, List argumentTypes) { addOrReplaceDmlGraphql(keyspaceName, "aggregate %s was created", aggregate); } @Override public void onAlterTable(String keyspaceName, String table) { addOrReplaceDmlGraphql(keyspaceName, "table %s was altered", table); } @Override public void onAlterView(String keyspaceName, String view) { addOrReplaceDmlGraphql(keyspaceName, "view %s was altered", view); } @Override public void onAlterType(String keyspaceName, String type) { addOrReplaceDmlGraphql(keyspaceName, "type %s was altered", type); } @Override public void onAlterFunction(String keyspaceName, String function, List argumentTypes) { addOrReplaceDmlGraphql(keyspaceName, "function %s was altered", function); } @Override public void onAlterAggregate(String keyspaceName, String aggregate, List argumentTypes) { addOrReplaceDmlGraphql(keyspaceName, "aggregate %s was altered", aggregate); } @Override public void onDropTable(String keyspaceName, String table) { addOrReplaceDmlGraphql(keyspaceName, "table %s was dropped", table); } @Override public void onDropView(String keyspaceName, String view) { addOrReplaceDmlGraphql(keyspaceName, "view %s was dropped", view); } @Override public void onDropType(String keyspaceName, String type) { addOrReplaceDmlGraphql(keyspaceName, "type %s was dropped", type); } @Override public void onDropFunction(String keyspaceName, String function, List argumentTypes) { addOrReplaceDmlGraphql(keyspaceName, "function %s was dropped", function); } @Override public void onDropAggregate(String keyspaceName, String aggregate, List argumentTypes) { addOrReplaceDmlGraphql(keyspaceName, "aggregate %s was dropped", aggregate); } /** * Lazily holds the {@link GraphQL} instance for the DML operations on a particular keyspace: it * will only be initialized if the keyspace is queried. */ static class DmlGraphqlReference { private final Keyspace keyspace; private final Persistence persistence; private final AuthenticationService authenticationService; private final AuthorizationService authorizationService; private final DataStoreFactory dataStoreFactory; private volatile GraphQL graphql; DmlGraphqlReference( Keyspace keyspace, Persistence persistence, AuthenticationService authenticationService, AuthorizationService authorizationService, DataStoreFactory dataStoreFactory) { this.keyspace = keyspace; this.persistence = persistence; this.authenticationService = authenticationService; this.authorizationService = authorizationService; this.dataStoreFactory = dataStoreFactory; } GraphQL get() { // Double-checked locking GraphQL result = graphql; if (result != null) { return result; } synchronized (this) { if (graphql == null) { graphql = newGraphql( SchemaFactory.newDmlSchema( authenticationService, authorizationService, keyspace, dataStoreFactory)); } return graphql; } } } private static GraphQL newGraphql(GraphQLSchema schema) { return GraphQL.newGraphQL(schema) // Use parallel execution strategy for mutations (serial is default) .mutationExecutionStrategy(new AsyncExecutionStrategy()) .build(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy