/* * Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com * * 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/LICENSE2.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 com.sebastian_daschner.jaxrs_analyzer.analysis; import com.sebastian_daschner.jaxrs_analyzer.LogProvider; import com.sebastian_daschner.jaxrs_analyzer.builder.ResourceMethodBuilder; import com.sebastian_daschner.jaxrs_analyzer.builder.ResponseBuilder; import com.sebastian_daschner.jaxrs_analyzer.model.Types; import com.sebastian_daschner.jaxrs_analyzer.model.rest.*; import org.junit.Before; import org.junit.Test; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import java.io.File; import java.net.MalformedURLException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; import static org.junit.Assert.*; public class ProjectAnalyzerTest { private ProjectAnalyzer classUnderTest; private Path path; @Before public void setUp() throws MalformedURLException { LogProvider.injectDebugLogger(System.out::println); final String testClassPath = "src/test/jaxrs-test"; // invoke compilation for jaxrs-test classes final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); final List<JavaFileObject> compilationUnits = findClassFiles(testClassPath, fileManager); final JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, null, null, singletonList("-g"), null, compilationUnits); assertTrue("Could not compile test project", compilationTask.call()); path = Paths.get(testClassPath).toAbsolutePath(); final Set<Path> classPaths = Stream.of(System.getProperty("java.class.path").split(File.pathSeparator)) .map(Paths::get) .collect(Collectors.toSet()); classPaths.add(path); classUnderTest = new ProjectAnalyzer(classPaths); } @Test public void test() { final long startTime = System.currentTimeMillis(); final Resources actualResources = classUnderTest.analyze(singleton(path), singleton(path)); System.out.println("Project analysis took " + (System.currentTimeMillis() - startTime) + " ms"); final Resources expectedResources = getResources(); assertEquals(expectedResources.getBasePath(), actualResources.getBasePath()); assertEquals(expectedResources.getResources(), actualResources.getResources()); assertResourceEquals(expectedResources, actualResources); assertEquals(expectedResources.getTypeRepresentations().size(), actualResources.getTypeRepresentations().size()); } private static void assertResourceEquals(final Resources expectedResources, final Resources actualResources) { actualResources.getResources().forEach(r -> { final Set<ResourceMethod> expectedMethods = expectedResources.getMethods(r); final Set<ResourceMethod> actualMethods = actualResources.getMethods(r); final String resourceText = "Compared resource " + r; actualMethods.forEach(am -> { final String methodText = resourceText + ", method " + am.getMethod(); final ResourceMethod em = expectedMethods.stream().filter(m -> m.getMethod() == am.getMethod()).findAny() .orElseThrow(() -> new AssertionError(am.getMethod() + " method not found for resource " + r)); assertEquals(methodText, em.getMethodParameters(), am.getMethodParameters()); assertEquals(methodText, em.getRequestMediaTypes(), am.getRequestMediaTypes()); assertEquals(methodText, em.getResponseMediaTypes(), am.getResponseMediaTypes()); assertTypeIdentifierEquals(methodText, em.getRequestBody(), am.getRequestBody(), expectedResources.getTypeRepresentations(), actualResources.getTypeRepresentations()); assertEquals(methodText, em.getResponses().keySet(), am.getResponses().keySet()); assertEquals(methodText, em.getDescription(), am.getDescription()); assertEquals(methodText, em.getRequestBodyDescription(), am.getRequestBodyDescription()); am.getResponses().entrySet().forEach(ae -> { final Response ar = ae.getValue(); final Response er = em.getResponses().get(ae.getKey()); final String responseText = methodText + ", response " + ae.getKey(); assertEquals(responseText, er.getHeaders(), ar.getHeaders()); assertTypeIdentifierEquals(responseText, er.getResponseBody(), ar.getResponseBody(), expectedResources.getTypeRepresentations(), actualResources.getTypeRepresentations()); }); }); }); } private static void assertTypeIdentifierEquals(final String message, final TypeIdentifier expectedIdentifier, final TypeIdentifier actualIdentifier, final Map<TypeIdentifier, TypeRepresentation> expectedResources, final Map<TypeIdentifier, TypeRepresentation> actualResources) { if (expectedIdentifier == null || actualIdentifier == null) { assertNull(message, expectedIdentifier); assertNull(message, actualIdentifier); return; } if (!expectedIdentifier.getName().startsWith("$")) { assertEquals(message, expectedIdentifier, actualIdentifier); } assertRepresentationEquals(message, expectedResources.get(expectedIdentifier), actualResources.get(actualIdentifier), expectedResources, actualResources); } private static void assertRepresentationEquals(final String message, final TypeRepresentation expectedRepresentation, final TypeRepresentation actualRepresentation, final Map<TypeIdentifier, TypeRepresentation> expectedResources, final Map<TypeIdentifier, TypeRepresentation> actualResources) { if (expectedRepresentation == null || actualRepresentation == null) { assertNull(message, expectedRepresentation); assertNull(message, actualRepresentation); return; } assertEquals(expectedRepresentation.getClass(), actualRepresentation.getClass()); if (expectedRepresentation instanceof TypeRepresentation.CollectionTypeRepresentation) { assertRepresentationEquals(message, ((TypeRepresentation.CollectionTypeRepresentation) expectedRepresentation).getRepresentation(), ((TypeRepresentation.CollectionTypeRepresentation) actualRepresentation).getRepresentation(), expectedResources, actualResources); } else { final Map<String, TypeIdentifier> expectedProperties = ((TypeRepresentation.ConcreteTypeRepresentation) expectedRepresentation).getProperties(); final Map<String, TypeIdentifier> actualProperties = ((TypeRepresentation.ConcreteTypeRepresentation) actualRepresentation).getProperties(); assertEquals(message, expectedProperties.keySet(), actualProperties.keySet()); actualProperties.forEach((k, v) -> assertTypeIdentifierEquals(message, expectedProperties.get(k), v, expectedResources, actualResources)); } } private static Resources getResources() { final Resources resources = new Resources(); Map<String, TypeIdentifier> properties; final TypeIdentifier stringIdentifier = TypeIdentifier.ofType(Types.STRING); properties = new HashMap<>(); properties.put("id", TypeIdentifier.ofType(Types.PRIMITIVE_LONG)); properties.put("name", stringIdentifier); final TypeIdentifier modelIdentifier = TypeIdentifier.ofType("Lcom/sebastian_daschner/jaxrs_test/Model;"); final TypeRepresentation modelRepresentation = TypeRepresentation.ofConcrete(modelIdentifier, properties); resources.getTypeRepresentations().put(modelIdentifier, modelRepresentation); final TypeIdentifier modelListIdentifier = TypeIdentifier.ofType("Ljava/util/List<+Lcom/sebastian_daschner/jaxrs_test/Model;>;"); resources.getTypeRepresentations().put(modelListIdentifier, TypeRepresentation.ofCollection(modelListIdentifier, modelRepresentation)); final TypeIdentifier stringArrayListIdentifier = TypeIdentifier.ofType("Ljava/util/ArrayList<Ljava/lang/String;>;"); resources.getTypeRepresentations().put(stringArrayListIdentifier, TypeRepresentation.ofCollection(stringArrayListIdentifier, TypeRepresentation.ofConcrete(stringIdentifier))); resources.setBasePath("rest"); // test ResourceMethod firstGet = ResourceMethodBuilder.withMethod(HttpMethod.GET).andAcceptMediaTypes("application/json") .andResponseMediaTypes("application/json").andResponse(200, ResponseBuilder .withResponseBody(modelListIdentifier).build()).build(); ResourceMethod firstPost = ResourceMethodBuilder.withMethod(HttpMethod.POST).andRequestBodyType(Types.STRING) .andAcceptMediaTypes("application/json").andResponseMediaTypes("application/json") .andResponse(201, ResponseBuilder.newBuilder().andHeaders("Location").build()).build(); ResourceMethod firstPut = ResourceMethodBuilder.withMethod(HttpMethod.PUT).andRequestBodyType(modelIdentifier) .andAcceptMediaTypes("application/json").andResponseMediaTypes("application/json") .andResponse(202, ResponseBuilder.newBuilder().build()).build(); addMethods(resources, "test", firstGet, firstPost, firstPut);//, firstDelete); // test/{foobar} ResourceMethod firstDelete = ResourceMethodBuilder.withMethod(HttpMethod.DELETE, "Deletes a test.").andPathParam("foobar", Types.STRING, null, "The foo query") .andAcceptMediaTypes("application/json").andResponseMediaTypes("application/json") .andResponse(204, ResponseBuilder.newBuilder().build()).build(); addMethods(resources, "test/{foobar}", firstDelete); // test/{id} ResourceMethod secondGet = ResourceMethodBuilder.withMethod(HttpMethod.GET) .andResponse(200, ResponseBuilder.withResponseBody(modelIdentifier).build()) .andAcceptMediaTypes("application/json").andResponseMediaTypes("application/json") .andPathParam("id", Types.STRING).build(); ResourceMethod secondDelete = ResourceMethodBuilder.withMethod(HttpMethod.DELETE) .andAcceptMediaTypes("application/json").andResponseMediaTypes("application/json") .andPathParam("id", "Ljava/lang/String;") .andResponse(204, ResponseBuilder.newBuilder().build()) .andResponse(404, ResponseBuilder.newBuilder().andHeaders("X-Message").build()) .andResponse(500, ResponseBuilder.newBuilder().build()).build(); addMethods(resources, "test/{id}", secondGet, secondDelete); // test/{id}/test ResourceMethod thirdDelete = ResourceMethodBuilder.withMethod(HttpMethod.DELETE, "Deletes another test.") .andAcceptMediaTypes("application/json").andResponseMediaTypes("application/json") .andPathParam("id", Types.STRING, null, "The ID").andQueryParam("query", Types.PRIMITIVE_INT, null, "The deletion query") .andResponse(204, ResponseBuilder.newBuilder().build()).build(); addMethods(resources, "test/{id}/test", thirdDelete); // test/test ResourceMethod fourthGet = ResourceMethodBuilder.withMethod(HttpMethod.GET, "Returns a test string with plain text.").andAcceptMediaTypes("application/json") .andResponseMediaTypes("text/plain").andResponse(200, ResponseBuilder.withResponseBody(stringIdentifier).build()).build(); addMethods(resources, "test/test", fourthGet); // complex ResourceMethod eighthGet = ResourceMethodBuilder.withMethod(HttpMethod.GET).andResponseMediaTypes("application/json") .andResponse(200, ResponseBuilder.withResponseBody(stringArrayListIdentifier).build()).build(); ResourceMethod secondPut = ResourceMethodBuilder.withMethod(HttpMethod.PUT) .andResponse(204, ResponseBuilder.newBuilder().build()).build(); addMethods(resources, "complex", eighthGet, secondPut); // complex/string ResourceMethod ninthGet = ResourceMethodBuilder.withMethod(HttpMethod.GET).andResponseMediaTypes("application/json") .andResponse(200, ResponseBuilder.withResponseBody(stringIdentifier).build()).build(); addMethods(resources, "complex/string", ninthGet); // complex/status ResourceMethod fifthGet = ResourceMethodBuilder.withMethod(HttpMethod.GET) .andResponse(200, ResponseBuilder.withResponseBody(stringIdentifier).build()).build(); addMethods(resources, "complex/status", fifthGet); // complex/{info} ResourceMethod sixthGet = ResourceMethodBuilder.withMethod(HttpMethod.GET) .andPathParam("info", Types.STRING).andResponse(200, ResponseBuilder.newBuilder().andHeaders("X-Info").build()).build(); addMethods(resources, "complex/{info}", sixthGet); // complex/sub ResourceMethod secondPost = ResourceMethodBuilder.withMethod(HttpMethod.POST, "Creates a sub resource.").andRequestBodyType(Types.STRING, "The entity") .andQueryParam("query", Types.STRING, null, "The query param.") .andResponse(202, ResponseBuilder.newBuilder().andHeaders("X-Info").build()).build(); addMethods(resources, "complex/sub", secondPost); // subsub addMethods(resources, "subsub", secondPost); // complex/sub/{name} ResourceMethod seventhGet = ResourceMethodBuilder.withMethod(HttpMethod.GET, "Gets a sub resource.").andPathParam("name", Types.STRING, null, "The name") .andQueryParam("query", Types.STRING, null, "The query param.") .andResponse(200, ResponseBuilder.withResponseBody(stringIdentifier).build()).build(); addMethods(resources, "complex/sub/{name}", seventhGet); // subsub/{name} addMethods(resources, "subsub/{name}", seventhGet); // complex/anotherSub ResourceMethod thirdPost = ResourceMethodBuilder.withMethod(HttpMethod.POST, "Creates a sub resource.").andRequestBodyType(Types.STRING, "The entity") .andQueryParam("query", Types.STRING, null, "The query param.") .andResponse(202, ResponseBuilder.newBuilder().andHeaders("X-Info").build()).build(); addMethods(resources, "complex/anotherSub", thirdPost); // complex/anotherSub/{name} ResourceMethod tenthGet = ResourceMethodBuilder.withMethod(HttpMethod.GET, "Gets a sub resource.").andPathParam("name", Types.STRING, null, "The name") .andQueryParam("query", Types.STRING, null, "The query param.") .andResponse(200, ResponseBuilder.withResponseBody(stringIdentifier).build()).build(); addMethods(resources, "complex/anotherSub/{name}", tenthGet); // complex/anotherSubres ResourceMethod fourthPost = ResourceMethodBuilder.withMethod(HttpMethod.POST, "Creates a sub resource.").andRequestBodyType(Types.STRING, "The entity") .andQueryParam("query", Types.STRING, null, "The query param.") .andResponse(202, ResponseBuilder.newBuilder().andHeaders("X-Info").build()).build(); addMethods(resources, "complex/anotherSubres", fourthPost); // complex/anotherSubres/{name} ResourceMethod eleventhGet = ResourceMethodBuilder.withMethod(HttpMethod.GET, "Gets a sub resource.").andPathParam("name", Types.STRING, null, "The name") .andQueryParam("query", Types.STRING, null, "The query param.") .andResponse(200, ResponseBuilder.withResponseBody(stringIdentifier).build()).build(); addMethods(resources, "complex/anotherSubres/{name}", eleventhGet); // json_tests final TypeIdentifier firstIdentifier = TypeIdentifier.ofDynamic(); properties = new HashMap<>(); properties.put("key", stringIdentifier); // All numbers are treat as double (JSON type number) properties.put("duke", TypeIdentifier.ofType(Types.DOUBLE)); resources.getTypeRepresentations().put(firstIdentifier, TypeRepresentation.ofConcrete(firstIdentifier, properties)); ResourceMethod twelfthGet = ResourceMethodBuilder.withMethod(HttpMethod.GET).andResponse(200, ResponseBuilder.withResponseBody(firstIdentifier).build()).build(); final TypeIdentifier secondIdentifier = TypeIdentifier.ofDynamic(); properties = new HashMap<>(); properties.put("key", stringIdentifier); resources.getTypeRepresentations().put(secondIdentifier, TypeRepresentation.ofConcrete(secondIdentifier, properties)); // TODO type should be Object because JsonArray is interpreted as collection type final TypeIdentifier thirdIdentifier = TypeIdentifier.ofDynamic(); resources.getTypeRepresentations().put(thirdIdentifier, TypeRepresentation.ofCollection(thirdIdentifier, TypeRepresentation.ofConcrete(stringIdentifier))); ResourceMethod fifthPost = ResourceMethodBuilder.withMethod(HttpMethod.POST) .andResponse(202, ResponseBuilder.withResponseBody(secondIdentifier).build()) .andResponse(500, ResponseBuilder.newBuilder().build()) .andResponse(200, ResponseBuilder.withResponseBody(thirdIdentifier).build()).build(); addMethods(resources, "json_tests", twelfthGet, fifthPost); // json_tests/info final TypeIdentifier fourthIdentifier = TypeIdentifier.ofDynamic(); properties = new HashMap<>(); properties.put("key", stringIdentifier); properties.put("duke", stringIdentifier); properties.put("hello", stringIdentifier); resources.getTypeRepresentations().put(fourthIdentifier, TypeRepresentation.ofConcrete(fourthIdentifier, properties)); ResourceMethod thirteenthGet = ResourceMethodBuilder.withMethod(HttpMethod.GET).andResponse(200, ResponseBuilder.withResponseBody(fourthIdentifier).build()) .build(); addMethods(resources, "json_tests/info", thirteenthGet); return resources; } private static void addMethods(final Resources resources, final String path, final ResourceMethod... methods) { Stream.of(methods).forEach(m -> resources.addMethod(path, m)); } private static List<JavaFileObject> findClassFiles(final String classPath, final StandardJavaFileManager fileManager, final String... packages) { final Iterable<? extends JavaFileObject> fileObjects = fileManager.getJavaFileObjects(Paths.get(classPath, packages).toFile().listFiles((dir, name) -> name.endsWith(".java"))); List<JavaFileObject> classFiles = new LinkedList<>(); fileObjects.forEach(classFiles::add); Stream.of(Paths.get(classPath, packages).toFile().listFiles(File::isDirectory)).map(File::getName) .map(n -> { final String[] packagesNames = Arrays.copyOf(packages, packages.length + 1); packagesNames[packages.length] = n; return packagesNames; }).forEach(p -> classFiles.addAll(findClassFiles(classPath, fileManager, p))); return classFiles; } }