I have a project running with Spring Boot and have implemented a GraphQL API with com.graphql-java.graphql-java:12.0.
I want to set the field visibility for some Mutators and maybe some fields now, but unfortunately I find no tutorial, doc or example where I find a working explanation how to to this.
For explanation, I have following example entries in schema:
type Query {
login(username: String!, password: String!): String
organization(id: Int!): ApiOrganization
}
type Mutation {
updateProfile(profileData: ProfileInputDto): ID
updateAdminStuff(adminData: AdminStuffDto): ID
}
The Query entries shall now be visible in the schema for all users who use the api and also the Mutation to updateProfile shall be visible.
But the Mutation updateAdminStuff shall only be visible if the user is logged in as Admin Role, so that a normal user doesn't even know that this mutation exists. Additionally it can happen that only some fields of some schema types shall be made visible for some roles only.
I found out that there is a chance to set something like that by GraphqlFieldVisibility (https://www.graphql-java.com/documentation/v12/fieldvisibility/). First versions I found said to set it in GraphQLSchema, but there it seems deprecated and I should use GraphQLCodeRegistry to set the visibility. For GraphQLCodeRegistry I found out on https://www.graphql-java.com/documentation/v12/execution/
GraphQLCodeRegistry codeRegistry = newCodeRegistry()
.dataFetcher(
coordinates("CreateReviewForEpisodeMutation", "createReview"),
mutationDataFetcher()
)
.build();
GraphQLSchema schema = GraphQLSchema.newSchema()
.query(queryType)
.mutation(createReviewForEpisodeMutation)
.codeRegistry(codeRegistry)
.build();
But unfortunately I find no way to set this method for the schema generation I use.
Can someone please give me a hint (example, tutorial, documentation) where I can find a tipp for soulution? (if something is possible at all in GraphQL)
Here some additional infos about the project:
I have a schmea definition saved as schema.graphqls. I have a GraphQLProvider and it prepares the Scehma and a GraphQL the following way:
private GraphQL graphQL;
#Bean
public GraphQL graphQL() {
return graphQL;
}
#PostConstruct
public void init() throws IOException {
URL url = Resources.getResource("graphql/schema.graphqls");
String sdl = Resources.toString(url, Charsets.UTF_8);
GraphQLSchema graphQLSchema = buildSchema(sdl);
this.graphQL = GraphQL.newGraphQL(graphQLSchema).build();
}
private GraphQLSchema buildSchema(String sdl) {
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl);
RuntimeWiring runtimeWiring = buildWiring();
SchemaGenerator schemaGenerator = new SchemaGenerator();
return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring);
}
In my controller I fetch the data by
ExecutionInput executionInput = ExecutionInput.newExecutionInput().context(request).query(body.getQuery())
.build();
ExecutionResult executionResult = graphQL.execute(executionInput);
where body is a GraphQLQuery and graphQL is the bean of the code before.
Thanks for your help and best regards.
Ok got the answer in the GraphQL-Java chat.
I used this tutorial https://www.graphql-java.com/tutorials/getting-started-with-spring-boot/ to build the graphQl API and using this I build the runtime wiring by myself and there I can set the visibility.
I now implemented it this way:
GraphqlFieldVisibility blockedFields = BlockedFields.newBlock()
.addPattern("ApiField.secretfield")
.addPattern(".*\\.secretAdminMutation")
.build();
private RuntimeWiring buildWiring() {
return RuntimeWiring.newRuntimeWiring().fieldVisibility(blockedFields)....
and it works great!
Related
I have a swagger.yaml file containing definition of my API. I'm using the following piece of code to add it as a swagger resource and display it in swagger-ui:
#Primary
#Bean
public SwaggerResourcesProvider swaggerResourcesProvider(InMemorySwaggerResourcesProvider defaultResourcesProvider) {
return () -> {
SwaggerResource wsResource = new SwaggerResource();
wsResource.setName("Documentation");
wsResource.setSwaggerVersion("3.0");
wsResource.setLocation("/swagger.yaml");
List<SwaggerResource> resources = new ArrayList<>(defaultResourcesProvider.get());
resources.add(wsResource);
return resources;
};
}
However, I need to have a different security schema authorization URL for each instance of my application (dev, test, prod). The question is how to achieve it? Is it possible to programmatically add the security schema, which will be parametrized based on f.e. env variables?
You can do that using the following:
#Bean
public OpenAPI openAPI() {
return new OpenAPI()
.components(new Components().addSecuritySchemes("jwtScheme",
new SecurityScheme().type(SecurityScheme.Type.APIKEY).in(SecurityScheme.In.HEADER).name("Authorization")));
}
You can get whatever environment variables you want to parameterize this configuration. You can read more about this at https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api#10-swagger-ui-with-an-oauth-secured-api.
I am working on some legacy project, which uses 1.5.15.RELEASE Spring Boot version. Due to this I am using spring-data-dynamodb version 4.5.7. My problem is kind of sibling to this.
I tried the solutions provided in that topic - including setting TableNameOverride and also custom TableNameResolver. Unfortunately all the findBy methods handled automatically by spring-data-dynamodb:4.5.7 are querying static table name - let's call it Item.
I also have some custom methods implemented in RepositoryImpl class, where I #Autowire bean of DynamoDBMapper. Suprisingly, queries implemented manually (not handled by spring-data-dynamodb:4.5.7) are querying table with correct prefix i.e. TEST_Item.
ItemsRepository (this methods do not apply prefix, when querying - does not work, still queries Item table):
public interface ItemRepository extends CrudRepository<Item, ItemId>, ItemCustomRepository {
Iterable<Item> findAllByInstanceId(Long instanceId);
Iterable<Item> findAllByInstanceIdOrderByInsertedDateDesc(Long instanceId);
}
ItemsRepositoryImpl (this method do apply prefix, when querying - works fine, queries TEST_Item):
public class ItemRepositoryImpl implements ItemCustomRepository {
#Autowired
private DynamoDBMapper dynamoDBMapper;
#Override
public List<Item> findByInstanceIdAndMessageLike(Long instanceId, String search, String stepCode) {
DynamoDBQueryExpression<Item> queryExpression = new DynamoDBQueryExpression<Item>()
//... here building some query - not important;
PaginatedQueryList<Item> queryList = dynamoDBMapper.query(Item.class, queryExpression);
return response;
}
Configuration class:
#Configuration
#EnableDynamoDBRepositories(amazonDynamoDBRef = "amazonDynamoDB", dynamoDBMapperConfigRef = "dynamoDBMapperConfig", basePackages = "com.shem.datasource.nosql.repository")
public class DynamoDBConfig {
#Value("${amazon.dynamodb.region}")
private String amazonDynamoDBRegion;
#Value("${dynamo.proxy.host:}")
private String proxyHost;
#Value("${dynamo.proxy.port:80}")
private Integer proxyPort;
#Value("${dynamo.proxy.enabled:false}")
private Boolean proxyEnabled;
#Value("${amazon.dynamodb.tableNamePrefix:}")
private String tableNamePrefix;
#Bean
public AmazonDynamoDB amazonDynamoDB() {
ClientConfiguration clientConfig = new ClientConfiguration();
if (proxyEnabled) {
clientConfig.setProxyHost(proxyHost);
clientConfig.setProxyPort(proxyPort);
clientConfig.setProxyProtocol(Protocol.HTTP);
}
AmazonDynamoDB amazonDynamoDB = AmazonDynamoDBClientBuilder.standard()
.withClientConfiguration(clientConfig)
.withRegion(amazonDynamoDBRegion)
.build();
return amazonDynamoDB;
}
#Bean
public DynamoDB dynamoDB(AmazonDynamoDB amazonDynamoDB) {
return new DynamoDB(amazonDynamoDB);
}
#Bean
public DynamoDBMapperConfig dynamoDBMapperConfig() {
DynamoDBMapperConfig.Builder builder = new DynamoDBMapperConfig.Builder();
String finalPrefix = StringUtils.isNotBlank(tableNamePrefix) ? tableNamePrefix + "_" : "";
builder.setPaginationLoadingStrategy(PaginationLoadingStrategy.ITERATION_ONLY);
builder.setTableNameOverride(DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix(finalPrefix));
System.out.println("Prefix set to " + finalPrefix);
return builder.build();
}
#Bean
public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB, DynamoDBMapperConfig dynamoDBMapperConfig) {
return new DynamoDBMapper(amazonDynamoDB, dynamoDBMapperConfig);
}
}
Seems like the problem is somewhere in spring-data-dynamodb:4.5.7, but maybe someone already faced such issue and can help. Any thoughts how to overcome this issue?
The only solutions coming to my mind are from category not the best ideas:
remove spring-data-dynamodb at all and implement everything with just AWS Java SDK
try to change SpringBoot version to >2 and then try using newer version of spring-data-dynamodb, but project glows red when trying - timeconsuming..
I recommend using the Amazon DynamoDB Java API V2. You can develop Spring Boot apps that are able to use the latest features of this API, such as the enhanced client, which maps fields in a class to items in a table.
Here is a developer article that walks you through step by step how to develop a Spring Boot app that uses DynamoDB Java API V2.
Creating the DynamoDB web application item tracker
I want to introduce a scalar type to the graphql scheme and provide resolver for it.
I believe what I need to do is to provide the corresponding resolver for the new type as described here
I use spring boot starter to implement the server.
com.graphql-java:graphql-spring-boot-starter:5.0.2
There is schema.graphqls:
type Query {
pets(last: Int): [Pet]
}
type Pet {
id: ID
name: String
}
and QueryResolver:
#Component
public class Query implements GraphQLQueryResolver {
#Autowired
PetStore petStore;
public Pet pet(long id) {
return petStore.pet(id);
}
public List<Pet> pets(Integer last) {
return petStore.pets().stream()
.limit(last != null ? last : Integer.MAX_VALUE)
.collect(Collectors.toList());
}
}
I believe that there is a way to update a scheme like this:
type Pet {
id: ID
name: String
dateOfBirth: LocalDateTime
}
scalar LocalDateTime
and provide resolver to define how new field value should be processed, like this:
#Component
public class DateResolver extends GraphQLScalarType {
// serialize/deserialize logic
}
Unfochinatly I get following exception:
com.coxautodev.graphql.tools.SchemaClassScannerError: Expected a user-defined GraphQL scalar type with name 'LocalDateTime' but found none!
After some research, I found resolvers for the java types in the graphql-java-9.2.jar!/graphql/Scalars.class and inspired of it.
Not sure what your setup looks like but for me this is how I quickly resolved that exception:
I extended the GraphQlHttpServlet and there is where I load my graphql schema (overriding the getConfiguration() method etc to start the servlet... sorry I can't go into much detail but I'm sure looking at the docs will make what I just said make sense). In getConfiguration() I have:
return GraphQLConfiguration.with(createSchema())
.with(customGraphQLContextBuilder)
.build();
and in createSchema I load the graphql schema and I PARSE it:
.
.
return SchemaParser.newParser()
.schemaString(schemaString)
.scalars(CustomDateTimeScalar.getDateTimeScalar())
.resolvers(myQueryResolverXYZ)
.build()
.makeExecutableSchema();
.
.
Just in case this sheds light on someone else that runs into this.
I have started converting an existing Spring Boot(1.5.4.RELEASE) application to support multi-tenant capabilities. So i am using MySQL as the database and Spring Data JPA as the data access mechanism. i am using the schema based multi-tenant approach. As Hibernate document suggests below
https://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html
I have implemented MultiTenantConnectionProvider and CurrentTenantIdentifierResolver interfaces and I am using ThreadLocal variable to maintain the current tenant for the incoming request.
public class TenantContext {
final public static String DEFAULT_TENANT = "master";
private static ThreadLocal<Tenant> tenantConfig = new ThreadLocal<Tenant>() {
#Override
protected Tenant initialValue() {
Tenant tenant = new Tenant();
tenant.setSchemaName(DEFAULT_TENANT);
return tenant;
}
};
public static Tenant getTenant() {
return tenantConfig.get();
}
public static void setTenant(Tenant tenant) {
tenantConfig.set(tenant);
}
public static String getTenantSchema() {
return tenantConfig.get().getSchemaName();
}
public static void clear() {
tenantConfig.remove();
}
}
Then i have implemented a filter and there i set the tenant dynamically looking at a request header as below
String targetTenantName = request.getHeader(TENANT_HTTP_HEADER);
Tenant tenant = new Tenant();
tenant.setSchemaName(targetTenantName);
TenantContext.setTenant(tenant);
This works fine and now my application points to different schema based on the request header value.
However there is a master schema where i store the some global settings and i need to access that schema while in a middle of a request for a tenant. Therefore i tried to hard code the Threadlocal variable just before that database call in the code as below.
Tenant tenant = new Tenant();
tenant.setSchemaName("master");
TenantContext.setTenant(tenant);
However this does not point to the master schema and instead it tries to access the original schema set during the filter. What is the reason for this?
As per my understanding Hibernate invokes openSession() during the first database call to a tenant and after i try to invoke another database call for "master" it still use the previous tenant as CurrentTenantIdentifierResolver invokes only during the openSession(). However these different database calls does not invoke within a transaction.
Can you please help me to understand the issue with my approach and any suggestions to fix the issue
Thanks
Keth
#JonathanJohx actually i am trying to override the TenantContext set by the filter in one of the controllers. First i am loging in a tenant where TenantContext is set to that particular tenant. While the request is in that tenant i am requesting data from master. In order to do that i am simply hard code the tenant as below
#RestController
#RequestMapping("/jobTemplates")
public class JobTemplateController {
#Autowired
JobTemplateService jobTemplateService;
#GetMapping
public JobTemplateList list(Pageable pageable){
Tenant tenant = new Tenant();
tenant.setSchemaName(multitenantMasterDb);
TenantContext.setTenant(tenant);
return jobTemplateService.list(pageable);
}
I'm trying to use GraphQL like client of a REST API. My backend return JSON but in my application client i write graphQL and in client i translate graphQL queries to HTTP call's.
My schema:
type Query {
students: [Student]
}
type Student {
name: String
}
POJO example:
public class Student {
private Integer id;
private String name;
}
My resolver:
public class Query implements GraphQLQueryResolver {
public List<Post> students() {
// HTTP Request
}
}
In all library's implementations i need create a POJO for Student and write a resolver for request in my API.
A way exist to don't need create a POJO and a create global execute resolver?
If you're using libraries like graphql-java-tools (which seems to be the case), you need POJOs, as this is where the library gets its type mappings from. But if you're just using graphql-java itself, you can wire it any way you like - including having a single global resolver (i.e. DataFetcher).
For an idea how to do this, see http://graphql-java.readthedocs.io/en/latest/schema.html#idl
You want something like:
SchemaParser schemaParser = new SchemaParser();
SchemaGenerator schemaGenerator = new SchemaGenerator();
File schemaFile = loadSchema("yourStudentSchema.graphqls");
TypeDefinitionRegistry typeRegistry = schemaParser.parse(schemaFile);
RuntimeWiring wiring = buildRuntimeWiring();
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeRegistry, wiring);
RuntimeWiring is where you hook the resolvers, e.g:
RuntimeWiring buildRuntimeWiring() {
return RuntimeWiring.newRuntimeWiring()
// this uses builder function lambda syntax
.type("Query", typeWiring -> typeWiring
.dataFetcher("students", env -> fetchDataSomeHow(env)))
.build();
}
So you can provide the same DataFetcher implementation to each dataFetcher call f that's what you're after. graphql-java itself makes no assumptions about way it's wired and implemented, e.g. it does not enforce POJOs or anything else.