diff --git a/graphql-java-support/pom.xml b/graphql-java-support/pom.xml index bdca94a2..dd12602a 100644 --- a/graphql-java-support/pom.xml +++ b/graphql-java-support/pom.xml @@ -26,11 +26,7 @@ org.junit.jupiter - junit-jupiter-api - - - org.junit.jupiter - junit-jupiter-engine + junit-jupiter org.slf4j diff --git a/pom.xml b/pom.xml index fefbc82c..f28d516e 100644 --- a/pom.xml +++ b/pom.xml @@ -61,10 +61,10 @@ ${java.version} 2.7 16.1 - 5.10.0 + 11.0.0 1.8 17.0.0 - 5.4.2 + 5.7.1 1.6 3.1.2 3.1.1 @@ -72,7 +72,7 @@ 2.22.1 1.6.8 1.7.30 - 2.1.6.RELEASE + 2.3.6.RELEASE @@ -80,19 +80,25 @@ com.graphql-java-kickstart graphql-spring-boot-starter - ${graphql-spring-boot.version} + ${graphql-java-kickstart.version} com.graphql-java-kickstart graphiql-spring-boot-starter - ${graphql-spring-boot.version} + ${graphql-java-kickstart.version} + runtime com.graphql-java-kickstart graphql-spring-boot-starter-test - ${graphql-spring-boot.version} + ${graphql-java-kickstart.version} test + + com.graphql-java-kickstart + graphql-java-tools + ${graphql-java-kickstart.version} + com.graphql-java graphql-java @@ -106,13 +112,7 @@ org.junit.jupiter - junit-jupiter-api - ${junit.version} - test - - - org.junit.jupiter - junit-jupiter-engine + junit-jupiter ${junit.version} test diff --git a/spring-example/README.md b/spring-example/README.md index 7d71e38f..33df17af 100644 --- a/spring-example/README.md +++ b/spring-example/README.md @@ -1,11 +1,16 @@ +This Spring Boot application contains examples for both a microservice using standard `graphql-java` and a microservice using `graphql-java-tools`; their code is separated under different subpackages and Spring profiles. -To run the example, cd to the root of this project, then... +To run the standard `graphql-java` example, `cd` to the root of this project, then... ``` ## compile and install project including example and dependencies mvn install -Dgpg.skip ## start local webserver on 9000 mvn -pl spring-example spring-boot:run ``` +To run the `graphql-java-tools` example, for the last step instead run: +``` +mvn -pl spring-example spring-boot:run -Dspring-boot.run.profiles=graphql-java-tools +``` Now you can query your local graph: ``` ## e.g. diff --git a/spring-example/pom.xml b/spring-example/pom.xml index ea438a69..51ca159d 100644 --- a/spring-example/pom.xml +++ b/spring-example/pom.xml @@ -23,7 +23,7 @@ org.junit.jupiter - junit-jupiter-api + junit-jupiter org.springframework.boot @@ -49,6 +49,10 @@ com.graphql-java-kickstart graphql-spring-boot-starter-test + + com.graphql-java-kickstart + graphql-java-tools + diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/App.java b/spring-example/src/main/java/com/apollographql/federation/springexample/App.java index 0dcc4b29..0896b2c7 100644 --- a/spring-example/src/main/java/com/apollographql/federation/springexample/App.java +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/App.java @@ -1,19 +1,11 @@ package com.apollographql.federation.springexample; -import com.apollographql.federation.graphqljava.tracing.FederatedTracingInstrumentation; -import graphql.execution.instrumentation.Instrumentation; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } - - @Bean - public Instrumentation addFederatedTracing() { - return new FederatedTracingInstrumentation(new FederatedTracingInstrumentation.Options(true)); - } } diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/InventorySchemaProvider.java b/spring-example/src/main/java/com/apollographql/federation/springexample/InventorySchemaProvider.java deleted file mode 100644 index d2c4665b..00000000 --- a/spring-example/src/main/java/com/apollographql/federation/springexample/InventorySchemaProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.apollographql.federation.springexample; - -import com.apollographql.federation.graphqljava.Federation; -import com.apollographql.federation.graphqljava._Entity; -import graphql.servlet.config.DefaultGraphQLSchemaProvider; -import graphql.servlet.config.GraphQLSchemaProvider; -import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.Resource; -import org.springframework.stereotype.Component; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -@Component -public class InventorySchemaProvider extends DefaultGraphQLSchemaProvider implements GraphQLSchemaProvider { - public InventorySchemaProvider(@Value("classpath:schemas/inventory.graphql") Resource sdl) throws IOException { - super(Federation.transform(sdl.getFile()) - .fetchEntities(env -> env.>>getArgument(_Entity.argumentName) - .stream() - .map(values -> { - if ("Product".equals(values.get("__typename"))) { - final Object upc = values.get("upc"); - if (upc instanceof String) { - return lookupProduct((String) upc); - } - } - return null; - }) - .collect(Collectors.toList())) - .resolveEntityType(env -> { - final Object src = env.getObject(); - if (src instanceof Product) { - return env.getSchema().getObjectType("Product"); - } - return null; - }) - .build()); - } - - @NotNull - private static Product lookupProduct(@NotNull String upc) { - try { - // Why not? - int quantity = Math.floorMod( - new BigInteger(1, - MessageDigest.getInstance("SHA1").digest(upc.getBytes()) - ).intValue(), - 10_000); - - return new Product(upc, quantity); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } -} diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljava/AppConfiguration.java b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljava/AppConfiguration.java new file mode 100644 index 00000000..786855b6 --- /dev/null +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljava/AppConfiguration.java @@ -0,0 +1,47 @@ +package com.apollographql.federation.springexample.graphqljava; + +import com.apollographql.federation.graphqljava.Federation; +import com.apollographql.federation.graphqljava._Entity; +import com.apollographql.federation.graphqljava.tracing.FederatedTracingInstrumentation; +import graphql.schema.GraphQLSchema; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Configuration +@Profile("graphql-java") +public class AppConfiguration { + @Bean + public GraphQLSchema graphQLSchema(@Value("classpath:schemas/graphql-java/inventory.graphql") Resource sdl) throws IOException { + return Federation.transform(sdl.getFile()) + .fetchEntities(env -> env.>>getArgument(_Entity.argumentName) + .stream() + .map(reference -> { + if ("Product".equals(reference.get("__typename"))) { + return Product.resolveReference(reference); + } + return null; + }) + .collect(Collectors.toList())) + .resolveEntityType(env -> { + final Object src = env.getObject(); + if (src instanceof Product) { + return env.getSchema().getObjectType("Product"); + } + return null; + }) + .build(); + } + + @Bean + public FederatedTracingInstrumentation federatedTracingInstrumentation() { + return new FederatedTracingInstrumentation(new FederatedTracingInstrumentation.Options(true)); + } +} diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljava/Product.java b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljava/Product.java new file mode 100644 index 00000000..24dca16a --- /dev/null +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljava/Product.java @@ -0,0 +1,49 @@ +package com.apollographql.federation.springexample.graphqljava; + +import org.jetbrains.annotations.NotNull; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; + +public class Product { + private final String upc; + private final int quantity; + + public Product(String upc, int quantity) { + this.upc = upc; + this.quantity = quantity; + } + + public String getUpc() { + return upc; + } + + public int getQuantity() { + return quantity; + } + + public boolean isInStock() { + return this.quantity > 0; + } + + public static Product resolveReference(@NotNull Map reference) { + if (!(reference.get("upc") instanceof String)) { + return null; + } + final String upc = (String) reference.get("upc"); + try { + // Why not? + int quantity = Math.floorMod( + new BigInteger(1, + MessageDigest.getInstance("SHA1").digest(upc.getBytes()) + ).intValue(), + 10_000); + + return new Product(upc, quantity); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/AppConfiguration.java b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/AppConfiguration.java new file mode 100644 index 00000000..2fca7c14 --- /dev/null +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/AppConfiguration.java @@ -0,0 +1,44 @@ +package com.apollographql.federation.springexample.graphqljavatools; + +import com.apollographql.federation.graphqljava.SchemaTransformer; +import com.apollographql.federation.graphqljava._Entity; +import com.apollographql.federation.graphqljava.tracing.FederatedTracingInstrumentation; +import graphql.schema.GraphQLSchema; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Configuration +@Profile("graphql-java-tools") +public class AppConfiguration { + @Bean + public GraphQLSchema graphQLSchema(SchemaTransformer schemaTransformer) { + return schemaTransformer + .fetchEntities(env -> env.>>getArgument(_Entity.argumentName) + .stream() + .map(reference -> { + if ("Product".equals(reference.get("__typename"))) { + return ProductReferenceResolver.resolveReference(reference); + } + return null; + }) + .collect(Collectors.toList())) + .resolveEntityType(env -> { + final Object src = env.getObject(); + if (src instanceof Product) { + return env.getSchema().getObjectType("Product"); + } + return null; + }) + .build(); + } + + @Bean + public FederatedTracingInstrumentation federatedTracingInstrumentation() { + return new FederatedTracingInstrumentation(new FederatedTracingInstrumentation.Options(true)); + } +} diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/GraphQLJavaToolsConfiguration.java b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/GraphQLJavaToolsConfiguration.java new file mode 100644 index 00000000..3893d8af --- /dev/null +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/GraphQLJavaToolsConfiguration.java @@ -0,0 +1,79 @@ +package com.apollographql.federation.springexample.graphqljavatools; + +import com.apollographql.federation.graphqljava.Federation; +import com.apollographql.federation.graphqljava.SchemaTransformer; +import graphql.Scalars; +import graphql.kickstart.tools.SchemaObjects; +import graphql.kickstart.tools.SchemaParser; +import graphql.kickstart.tools.SchemaParserDictionary; +import graphql.kickstart.tools.SchemaParserOptions; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLObjectType; +import graphql.schema.GraphQLSchema; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Some tips for running with graphql-java-tools: + * 1. You may find that graphql-java-tools removes types that are only used by the federation + * _entities field. This is because graphql-java-tools performs these optimizations before + * federation-graphql-java-support can alter the schema to include the _entities field. The + * simplest workaround here is to both (1) add the type to the {@link SchemaParserDictionary} + * bean and to (2) set the {@link SchemaParserOptions.Builder} bean to have includeUnusedTypes + * as true. However, the latter can't be done via properties.xml/yml since the getters/setters + * aren't named according to the JavaBeans API specification (i.e. getFoo()/setFoo()). You can + * work around this by providing the whole bean via code, or using a {@link BeanPostProcessor} + * as shown below to customize the auto-configuration bean. + * 2. If you have an empty query type, you don't need to add a dummy field to your + * {@link graphql.kickstart.tools.GraphQLQueryResolver} or to your schema string. However, you + * do need to (1) declare an empty {@link graphql.kickstart.tools.GraphQLQueryResolver}, (2) + * declare an empty "type Query" in your schema string, and (3) setup {@link SchemaTransformer} + * as shown below. We could potentially modify the federation-graphql-java-support API to accept + * {@link GraphQLSchema.Builder}s, but unfortunately those builders have no public getters, so + * we wouldn't be able to copy their data. + */ +@Configuration +public class GraphQLJavaToolsConfiguration { + @Bean + public SchemaParserDictionary schemaParserDictionary() { + return new SchemaParserDictionary().add("Product", Product.class); + } + + @Bean + public BeanPostProcessor schemaParserOptionsBuilderPostProcessor() { + return new BeanPostProcessor() { + @Override + public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException { + return bean instanceof SchemaParserOptions.Builder + ? ((SchemaParserOptions.Builder) bean).includeUnusedTypes(true) + : bean; + } + }; + } + + @Bean + public SchemaTransformer schemaTransformer(SchemaParser schemaParser) { + final SchemaObjects schemaObjects = schemaParser.parseSchemaObjects(); + final boolean queryTypeIsEmpty = schemaObjects.getQuery().getFieldDefinitions().isEmpty(); + final GraphQLObjectType newQuery = queryTypeIsEmpty + ? schemaObjects.getQuery().transform(graphQLObjectTypeBuilder -> + graphQLObjectTypeBuilder.field(GraphQLFieldDefinition.newFieldDefinition() + .name("_dummy") + .type(Scalars.GraphQLString) + .build() + ) + ) + : schemaObjects.getQuery(); + final GraphQLSchema graphQLSchema = GraphQLSchema.newSchema() + .query(newQuery) + .mutation(schemaObjects.getMutation()) + .subscription(schemaObjects.getSubscription()) + .additionalTypes(schemaObjects.getDictionary()) + .codeRegistry(schemaObjects.getCodeRegistryBuilder().build()) + .build(); + return Federation.transform(graphQLSchema, queryTypeIsEmpty); + } +} diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/Product.java b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/Product.java similarity index 71% rename from spring-example/src/main/java/com/apollographql/federation/springexample/Product.java rename to spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/Product.java index 7cb6b65f..8b9f2c32 100644 --- a/spring-example/src/main/java/com/apollographql/federation/springexample/Product.java +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/Product.java @@ -1,4 +1,4 @@ -package com.apollographql.federation.springexample; +package com.apollographql.federation.springexample.graphqljavatools; public class Product { private final String upc; @@ -16,8 +16,4 @@ public String getUpc() { public int getQuantity() { return quantity; } - - public boolean isInStock() { - return this.quantity > 0; - } } diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/ProductReferenceResolver.java b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/ProductReferenceResolver.java new file mode 100644 index 00000000..0c44e6a0 --- /dev/null +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/ProductReferenceResolver.java @@ -0,0 +1,29 @@ +package com.apollographql.federation.springexample.graphqljavatools; + +import org.jetbrains.annotations.NotNull; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Map; + +public class ProductReferenceResolver { + public static Product resolveReference(@NotNull Map reference) { + if (!(reference.get("upc") instanceof String)) { + return null; + } + final String upc = (String) reference.get("upc"); + try { + // Why not? + int quantity = Math.floorMod( + new BigInteger(1, + MessageDigest.getInstance("SHA1").digest(upc.getBytes()) + ).intValue(), + 10_000); + + return new Product(upc, quantity); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/ProductResolver.java b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/ProductResolver.java new file mode 100644 index 00000000..50200517 --- /dev/null +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/ProductResolver.java @@ -0,0 +1,11 @@ +package com.apollographql.federation.springexample.graphqljavatools; + +import graphql.kickstart.tools.GraphQLResolver; +import org.springframework.stereotype.Component; + +@Component +public class ProductResolver implements GraphQLResolver { + public boolean isInStock(Product product) { + return product.getQuantity() > 0; + } +} diff --git a/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/QueryResolver.java b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/QueryResolver.java new file mode 100644 index 00000000..a991e9bf --- /dev/null +++ b/spring-example/src/main/java/com/apollographql/federation/springexample/graphqljavatools/QueryResolver.java @@ -0,0 +1,8 @@ +package com.apollographql.federation.springexample.graphqljavatools; + +import graphql.kickstart.tools.GraphQLQueryResolver; +import org.springframework.stereotype.Component; + +@Component +public class QueryResolver implements GraphQLQueryResolver { +} diff --git a/spring-example/src/main/resources/application.yml b/spring-example/src/main/resources/application.yml index fe41530c..4a54964e 100644 --- a/spring-example/src/main/resources/application.yml +++ b/spring-example/src/main/resources/application.yml @@ -1,5 +1,7 @@ spring: application: name: federation-jvm-spring-example + profiles: + default: graphql-java server: port: 9000 diff --git a/spring-example/src/main/resources/schemas/graphql-java-tools/inventory.graphqls b/spring-example/src/main/resources/schemas/graphql-java-tools/inventory.graphqls new file mode 100644 index 00000000..6e6e616c --- /dev/null +++ b/spring-example/src/main/resources/schemas/graphql-java-tools/inventory.graphqls @@ -0,0 +1,7 @@ +type Query + +type Product @key(fields: "upc") @extends { + upc: String! @external + inStock: Boolean + quantity: Int +} diff --git a/spring-example/src/main/resources/schemas/inventory.graphql b/spring-example/src/main/resources/schemas/graphql-java/inventory.graphql similarity index 100% rename from spring-example/src/main/resources/schemas/inventory.graphql rename to spring-example/src/main/resources/schemas/graphql-java/inventory.graphql diff --git a/spring-example/src/test/java/com/apollographql/federation/springexample/AppTest.java b/spring-example/src/test/java/com/apollographql/federation/springexample/BaseAppTest.java similarity index 59% rename from spring-example/src/test/java/com/apollographql/federation/springexample/AppTest.java rename to spring-example/src/test/java/com/apollographql/federation/springexample/BaseAppTest.java index 3453a226..94432e69 100644 --- a/spring-example/src/test/java/com/apollographql/federation/springexample/AppTest.java +++ b/spring-example/src/test/java/com/apollographql/federation/springexample/BaseAppTest.java @@ -1,12 +1,7 @@ package com.apollographql.federation.springexample; import com.graphql.spring.boot.test.GraphQLResponse; -import com.graphql.spring.boot.test.GraphQLTest; import com.graphql.spring.boot.test.GraphQLTestTemplate; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.context.junit4.SpringRunner; import java.io.IOException; @@ -14,18 +9,15 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -@RunWith(SpringRunner.class) -@GraphQLTest -public class AppTest { - @Autowired - private GraphQLTestTemplate graphqlTestTemplate; - - @Test - public void lookupPlanckProduct() throws IOException { +public class BaseAppTest { + public void lookupPlanckProduct(GraphQLTestTemplate graphqlTestTemplate) throws IOException { final GraphQLResponse response = graphqlTestTemplate.postForResource("queries/LookupPlanckProduct.graphql"); assertNotNull(response, "response should not have been null"); - assertTrue(response.isOk(), "response should have been OK"); + assertTrue(response.isOk(), "response should have been HTTP 200 OK"); + response.assertThatNoErrorsArePresent(); final int quantity = response.get("$.data._entities[0].quantity", Integer.class); assertEquals(8658, quantity, "Inventory contains 8658 Plancks"); + final String federatedTrace = response.get("$.extensions.ftv1", String.class); + assertNotNull(federatedTrace, "response should not have had null federated trace"); } } diff --git a/spring-example/src/test/java/com/apollographql/federation/springexample/GraphQLJavaAppTest.java b/spring-example/src/test/java/com/apollographql/federation/springexample/GraphQLJavaAppTest.java new file mode 100644 index 00000000..2fb7ea69 --- /dev/null +++ b/spring-example/src/test/java/com/apollographql/federation/springexample/GraphQLJavaAppTest.java @@ -0,0 +1,24 @@ +package com.apollographql.federation.springexample; + +import com.graphql.spring.boot.test.GraphQLTest; +import com.graphql.spring.boot.test.GraphQLTestTemplate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +@RunWith(SpringRunner.class) +@GraphQLTest(profiles = "graphql-java") +@Import(App.class) +public class GraphQLJavaAppTest extends BaseAppTest { + @Autowired + private GraphQLTestTemplate graphqlTestTemplate; + + @Test + public void lookupPlanckProduct() throws IOException { + lookupPlanckProduct(graphqlTestTemplate); + } +} diff --git a/spring-example/src/test/java/com/apollographql/federation/springexample/GraphQLJavaToolsAppTest.java b/spring-example/src/test/java/com/apollographql/federation/springexample/GraphQLJavaToolsAppTest.java new file mode 100644 index 00000000..82879f07 --- /dev/null +++ b/spring-example/src/test/java/com/apollographql/federation/springexample/GraphQLJavaToolsAppTest.java @@ -0,0 +1,24 @@ +package com.apollographql.federation.springexample; + +import com.graphql.spring.boot.test.GraphQLTest; +import com.graphql.spring.boot.test.GraphQLTestTemplate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +@RunWith(SpringRunner.class) +@GraphQLTest(profiles = "graphql-java-tools") +@Import(App.class) +public class GraphQLJavaToolsAppTest extends BaseAppTest { + @Autowired + private GraphQLTestTemplate graphqlTestTemplate; + + @Test + public void lookupPlanckProduct() throws IOException { + lookupPlanckProduct(graphqlTestTemplate); + } +} diff --git a/spring-example/src/test/resources/logback.xml b/spring-example/src/test/resources/logback.xml new file mode 100644 index 00000000..215403c6 --- /dev/null +++ b/spring-example/src/test/resources/logback.xml @@ -0,0 +1,5 @@ + + + + +