diff --git a/.travis.yml b/.travis.yml index f66409c03..b9a537e20 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,5 @@ env: - secure: HDAaT4hx/r01MoihfDKUDbEQG1dfoGdhjxO5RBjE7871WRV4cv8rk7u22Le/H/PhCMHKDFjELnpz9kZKGwqjtHonVLC8smJNNkKUg6q9Uit6mwsqS6S+dPhB6V5hjqHskROHReHyfMM9if25L/p2mKWWhTQIdsdzDxJSHcDEKlIWWPlCBRWYimu3i4cn/EvwUAwSe2Zw5OleBzCbGtRAlAuXpHO+7yHJuCtNw1mClSgCTvSptef4FYVVXgbcPu4gvvX94lOgvHuZ0rgOlBCX+s8dBWVTD3x+J1VzCqoWjWsl21B5ADQHXRiQNkE4tnSKPhjwRo8rZyobFOMCxMz8Esvi3mXMVw+OAzIj2LnwIWNTd33mS2OF4sc4VFYGYpz1VdHnx7gPfUOnBhz8tCMThwQYYHpx0bTE8x0LdtRLV5zGKLs2XzR/CSqZuiI7PoOVXUvUjvYMG86OlYDxF3gtmOJybSI9qynU88kJAFpDFj/ldecAiLVYZqLd3qL1L0wa8oVHgogsLHUTGmud+P8URg6GYTIwbvomp8drpVzYyn5FNTSugBvZQxPzkCyCzXgFdQvrS9EyrbZibu44S8TjOjg507Zuo9+tjM6ouYOZsbkffvYle3WS/RjtDS3ROi0MTWVN4/7IZCG9I6R9UuKukEsPmJ/+YZcgS8HFa7YeCSI= after_success: + - ./gradlew coveralls - ./deploy.sh diff --git a/build.gradle b/build.gradle index eb2264298..96078453f 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,7 @@ buildscript { classpath 'com.moowork.gradle:gradle-node-plugin:1.1.1' classpath 'org.ajoberstar:gradle-git-publish:0.2.1' classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.5-rc1' + classpath "org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.8.1" } } @@ -30,8 +31,7 @@ ext { SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss") if (ext.timestampedVersion) { version = BUILD_VERSION_PREFIX + "." + format.format(new Date()) -} -else { +} else { version = '0.0.0-SNAPSHOT' } group = GROUP_ID @@ -50,7 +50,7 @@ gradle.beforeProject { Project project -> def docs = project.name == 'crnk-documentation' def ui = project.name == 'crnk-ui' - def test = project.name == 'crnk-test' + def testProject = project.name == 'crnk-test' def examples = project.name.contains('example') if (!docs) { @@ -63,9 +63,15 @@ gradle.beforeProject { Project project -> testCompile group: 'org.mockito', name: 'mockito-core', version: '1.10.19' testCompile group: 'org.assertj', name: 'assertj-core', version: '2.2.0' } + + test { + testLogging { + exceptionFormat = 'full' + } + } } - if (!docs && !examples && !test) { + if (!docs && !examples && !testProject) { // https://about.sonarqube.com/get-started/ apply plugin: "org.sonarqube" apply plugin: "jacoco" @@ -74,6 +80,26 @@ gradle.beforeProject { Project project -> toolVersion = "0.7.6.201602180812" } + rootProject.tasks.jacocoMerge.executionData tasks.withType(Test) + rootProject.tasks.jacocoRootReport.additionalSourceDirs files(sourceSets.main.allSource.srcDirs) + + def sourceDirs = rootProject.tasks.jacocoRootReport.sourceDirectories + def classDirs = rootProject.tasks.jacocoRootReport.classDirectories + + def mainOutput = files(files(sourceSets.main.output).collect { + fileTree(dir: it, exclude: '**/legacy/**') + }) + + if (sourceDirs == null) { + rootProject.tasks.jacocoRootReport.sourceDirectories = files(sourceSets.main.allSource.srcDirs) + rootProject.tasks.jacocoRootReport.classDirectories = mainOutput + + } else { + rootProject.tasks.jacocoRootReport.sourceDirectories = sourceDirs.plus(files(sourceSets.main.allSource.srcDirs)) + rootProject.tasks.jacocoRootReport.classDirectories = classDirs.plus(mainOutput) + } + rootProject.coveralls.sourceDirs.addAll(sourceSets.main.allSource.srcDirs.flatten()) + sonarqube { properties { @@ -81,6 +107,12 @@ gradle.beforeProject { Project project -> } } + jacocoTestReport { + reports { + xml.enabled = true // coveralls plugin depends on xml format report + html.enabled = true + } + } } apply plugin: 'maven-publish' @@ -171,7 +203,7 @@ gradle.beforeProject { Project project -> } tasks.publish.dependsOn(tasks.uploadArchives) - }else{ + } else { apply plugin: 'maven-publish' publishing { @@ -188,3 +220,49 @@ gradle.beforeProject { Project project -> } } +// coveralls setup +apply plugin: "jacoco" +apply plugin: "com.github.kt3k.coveralls" + +def publishedProjects = subprojects.findAll { + it.name != 'crnk-documentation' && it.name != 'crnk-ui' && it.name != 'crnk-test' && !it.name.contains('example') +} + +task jacocoMerge(type: JacocoMerge) { + destinationFile = new File(project.buildDir, 'jacoco/test.exec') + doFirst { + executionData = files(executionData.findAll { it.exists() }) + } + for (publishedProject in publishedProjects) { + dependsOn publishedProject.path + ":check" + } +} + + +task jacocoRootReport(type: JacocoReport, group: 'Coverage reports') { + description = 'Generates an aggregate report from all subprojects' + + dependsOn tasks.jacocoMerge + + executionData tasks.jacocoMerge.destinationFile + + reports { + html.enabled = true // human readable + xml.enabled = true // required by coveralls + } + +} + + +coveralls { + jacocoReportPath = "${buildDir}/reports/jacoco/jacocoRootReport/jacocoRootReport.xml" +} + +tasks.coveralls { + group = 'Coverage reports' + description = 'Uploads the aggregated coverage report to Coveralls' + + dependsOn jacocoRootReport +} + + diff --git a/crnk-cdi/src/test/java/io/crnk/internal/boot/cdi/CdiServiceDiscoveryTest.java b/crnk-cdi/src/test/java/io/crnk/internal/boot/cdi/CdiServiceDiscoveryTest.java index df5a7c61f..eb576918c 100644 --- a/crnk-cdi/src/test/java/io/crnk/internal/boot/cdi/CdiServiceDiscoveryTest.java +++ b/crnk-cdi/src/test/java/io/crnk/internal/boot/cdi/CdiServiceDiscoveryTest.java @@ -2,7 +2,9 @@ import io.crnk.cdi.internal.CdiServiceDiscovery; import io.crnk.core.boot.CrnkBoot; +import io.crnk.core.engine.error.ExceptionMapper; import io.crnk.core.engine.error.JsonApiExceptionMapper; +import io.crnk.core.module.Module; import io.crnk.core.module.discovery.DefaultServiceDiscoveryFactory; import io.crnk.core.module.discovery.ServiceDiscovery; import io.crnk.core.repository.Repository; @@ -44,6 +46,10 @@ public void testExceptionMapper() { CrnkBoot boot = new CrnkBoot(); boot.boot(); + CdiServiceDiscovery d = new CdiServiceDiscovery(); + System.out.println(d.getInstancesByType(ExceptionMapper.class)); + System.out.println(d.getInstancesByType(Module.class)); + Optional mapper = boot.getExceptionMapperRegistry().findMapperFor(IllegalStateException.class); Assert.assertTrue(mapper.isPresent()); Assert.assertTrue(mapper.get() instanceof CdiTestExceptionMapper); diff --git a/crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java b/crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java index b730a2228..0b425ab75 100644 --- a/crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java +++ b/crnk-core/src/main/java/io/crnk/core/boot/CrnkBoot.java @@ -196,12 +196,12 @@ private void setupResourceRegistry() { hierarchialPart.putPart(entry.getKey(), entry.getValue()); } if (!registryParts.containsKey("")) { - moduleRegistry.getContext().addRegistryPart("", new DefaultResourceRegistryPart() ); + moduleRegistry.getContext().addRegistryPart("", new DefaultResourceRegistryPart()); } rootPart = hierarchialPart; } - for(RegistryEntry entry : moduleRegistry.getRegistryEntries()){ + for (RegistryEntry entry : moduleRegistry.getRegistryEntries()) { rootPart.addEntry(entry); } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/error/ErrorResponse.java b/crnk-core/src/main/java/io/crnk/core/engine/error/ErrorResponse.java index 767021a7f..1b7ea15ae 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/error/ErrorResponse.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/error/ErrorResponse.java @@ -1,18 +1,12 @@ package io.crnk.core.engine.error; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - import io.crnk.core.engine.dispatcher.Response; import io.crnk.core.engine.document.Document; import io.crnk.core.engine.document.ErrorData; -import io.crnk.core.engine.internal.dispatcher.path.JsonPath; -import io.crnk.core.engine.query.QueryAdapter; import io.crnk.core.repository.response.JsonApiResponse; +import java.util.*; + public final class ErrorResponse { public static final String ERRORS = "errors"; @@ -46,14 +40,6 @@ public JsonApiResponse getResponse() { .setEntity(data); } - public JsonPath getJsonPath() { - return null; - } - - public QueryAdapter getQueryAdapter() { - return null; - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/BaseController.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/BaseController.java index 61e4a8043..1b8c58410 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/BaseController.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/BaseController.java @@ -40,23 +40,15 @@ public abstract Response handle(JsonPath jsonPath, QueryAdapter queryAdapter, Re parameterProvider, Document document); - protected void verifyTypes(HttpMethod methodType, String resourceEndpointName, RegistryEntry endpointRegistryEntry, + protected void verifyTypes(HttpMethod methodType, RegistryEntry endpointRegistryEntry, RegistryEntry bodyRegistryEntry) { if (endpointRegistryEntry.equals(bodyRegistryEntry)) { return; } if (bodyRegistryEntry == null || !bodyRegistryEntry.isParent(endpointRegistryEntry)) { String message = String.format("Inconsistent type definition between path and body: body type: " + - "%s, request type: %s", methodType, resourceEndpointName); - throw new RequestBodyException(methodType, resourceEndpointName, message); - } - } - - protected Object extractResource(Object responseOrResource) { - if (responseOrResource instanceof JsonApiResponse) { - return ((JsonApiResponse) responseOrResource).getEntity(); - } else { - return responseOrResource; + "%s, request type: %s", methodType, endpointRegistryEntry.getResourceInformation().getResourceType()); + throw new RequestBodyException(methodType, endpointRegistryEntry.getResourceInformation().getResourceType(), message); } } } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/FieldResourceGet.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/FieldResourceGet.java index 1f256ffa7..dc52a3b14 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/FieldResourceGet.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/FieldResourceGet.java @@ -43,9 +43,7 @@ public Response handle(JsonPath jsonPath, QueryAdapter queryAdapter, RepositoryM RegistryEntry registryEntry = resourceRegistry.getEntry(resourceName); Serializable castedResourceId = getResourceId(resourceIds, registryEntry); ResourceField relationshipField = registryEntry.getResourceInformation().findRelationshipFieldByName(elementName); - if (relationshipField == null) { - throw new ResourceFieldNotFoundException(elementName); - } + verifyFieldNotNull(relationshipField, elementName); // TODO remove Class usage and replace by resourceId Class baseRelationshipFieldClass = relationshipField.getType(); diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/FieldResourcePost.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/FieldResourcePost.java index f951ec33b..67e1e4fad 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/FieldResourcePost.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/FieldResourcePost.java @@ -1,8 +1,5 @@ package io.crnk.core.engine.internal.dispatcher.controller; -import java.io.Serializable; -import java.util.Collections; - import com.fasterxml.jackson.databind.ObjectMapper; import io.crnk.core.engine.dispatcher.Response; import io.crnk.core.engine.document.Document; @@ -17,16 +14,18 @@ import io.crnk.core.engine.internal.document.mapper.DocumentMapper; import io.crnk.core.engine.internal.repository.RelationshipRepositoryAdapter; import io.crnk.core.engine.internal.repository.ResourceRepositoryAdapter; -import io.crnk.core.engine.internal.utils.ClassUtils; +import io.crnk.core.engine.internal.utils.PreconditionUtil; import io.crnk.core.engine.parser.TypeParser; import io.crnk.core.engine.properties.PropertiesProvider; import io.crnk.core.engine.query.QueryAdapter; import io.crnk.core.engine.registry.RegistryEntry; import io.crnk.core.engine.registry.ResourceRegistry; -import io.crnk.core.exception.ResourceFieldNotFoundException; import io.crnk.core.repository.response.JsonApiResponse; import io.crnk.legacy.internal.RepositoryMethodParameterProvider; +import java.io.Serializable; +import java.util.Collections; + /** * Creates a new post in a similar manner as in {@link ResourcePost}, but additionally adds a relation to a field. */ @@ -39,9 +38,7 @@ public FieldResourcePost(ResourceRegistry resourceRegistry, PropertiesProvider p @Override public boolean isAcceptable(JsonPath jsonPath, String requestType) { - if (jsonPath == null) { - throw new IllegalArgumentException(); - } + PreconditionUtil.assertNotNull("path cannot be null", jsonPath); return !jsonPath.isCollection() && FieldPath.class.equals(jsonPath.getClass()) && HttpMethod.POST.name() @@ -61,15 +58,15 @@ public Response handle(JsonPath jsonPath, QueryAdapter queryAdapter, Serializable castedResourceId = getResourceId(resourceIds, endpointRegistryEntry); ResourceField relationshipField = endpointRegistryEntry.getResourceInformation() .findRelationshipFieldByName(jsonPath.getElementName()); - if (relationshipField == null) { - throw new ResourceFieldNotFoundException(jsonPath.getElementName()); - } + verifyFieldNotNull(relationshipField, jsonPath.getElementName()); Class baseRelationshipFieldClass = relationshipField.getType(); RegistryEntry relationshipRegistryEntry = resourceRegistry.getEntry(relationshipField.getOppositeResourceType()); String relationshipResourceType = relationshipField.getOppositeResourceType(); + verifyTypes(HttpMethod.POST, relationshipRegistryEntry, bodyRegistryEntry); + Object newResource = buildNewResource(relationshipRegistryEntry, resourceBody, relationshipResourceType); setAttributes(resourceBody, newResource, relationshipRegistryEntry.getResourceInformation()); ResourceRepositoryAdapter resourceRepository = relationshipRegistryEntry.getResourceRepository(parameterProvider); diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/RelationshipsResourceGet.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/RelationshipsResourceGet.java index a815bde08..8c3fed073 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/RelationshipsResourceGet.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/RelationshipsResourceGet.java @@ -40,9 +40,7 @@ public Response handle(JsonPath jsonPath, QueryAdapter queryAdapter, RepositoryM Serializable castedResourceId = getResourceId(resourceIds, registryEntry); String elementName = jsonPath.getElementName(); ResourceField relationshipField = registryEntry.getResourceInformation().findRelationshipFieldByName(elementName); - if (relationshipField == null) { - throw new ResourceFieldNotFoundException(elementName); - } + verifyFieldNotNull(relationshipField, elementName); boolean isCollection = Iterable.class.isAssignableFrom(relationshipField.getType()); diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/RelationshipsResourceUpsert.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/RelationshipsResourceUpsert.java index 75bd71194..8c22aca35 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/RelationshipsResourceUpsert.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/RelationshipsResourceUpsert.java @@ -1,8 +1,5 @@ package io.crnk.core.engine.internal.dispatcher.controller; -import java.io.Serializable; -import java.util.Collections; - import io.crnk.core.engine.dispatcher.Response; import io.crnk.core.engine.document.Document; import io.crnk.core.engine.document.ResourceIdentifier; @@ -16,7 +13,6 @@ import io.crnk.core.engine.internal.document.mapper.DocumentMapper; import io.crnk.core.engine.internal.repository.RelationshipRepositoryAdapter; import io.crnk.core.engine.internal.repository.ResourceRepositoryAdapter; -import io.crnk.core.engine.internal.utils.ClassUtils; import io.crnk.core.engine.internal.utils.PreconditionUtil; import io.crnk.core.engine.parser.TypeParser; import io.crnk.core.engine.query.QueryAdapter; @@ -26,6 +22,9 @@ import io.crnk.core.exception.ResourceFieldNotFoundException; import io.crnk.legacy.internal.RepositoryMethodParameterProvider; +import java.io.Serializable; +import java.util.Collections; + public abstract class RelationshipsResourceUpsert extends ResourceIncludeField { RelationshipsResourceUpsert(ResourceRegistry resourceRegistry, TypeParser typeParser, DocumentMapper documentMapper) { @@ -87,12 +86,10 @@ public final Response handle(JsonPath jsonPath, QueryAdapter queryAdapter, Serializable castedResourceId = getResourceId(resourceIds, registryEntry); ResourceField relationshipField = registryEntry.getResourceInformation().findRelationshipFieldByName(jsonPath .getElementName()); - if (relationshipField == null) { - throw new ResourceFieldNotFoundException(jsonPath.getElementName()); - } + verifyFieldNotNull(relationshipField, jsonPath.getElementName()); ResourceRepositoryAdapter resourceRepository = registryEntry.getResourceRepository(parameterProvider); @SuppressWarnings("unchecked") - Object resource = extractResource(resourceRepository.findOne(castedResourceId, queryAdapter)); + Object resource = resourceRepository.findOne(castedResourceId, queryAdapter).getEntity(); ResourceInformation targetInformation = getRegistryEntry(relationshipField.getOppositeResourceType()).getResourceInformation(); @@ -116,6 +113,8 @@ public final Response handle(JsonPath jsonPath, QueryAdapter queryAdapter, return new Response(new Document(), HttpStatus.NO_CONTENT_204); } + + private Serializable getResourceId(PathIds resourceIds, RegistryEntry registryEntry) { String resourceId = resourceIds.getIds().get(0); @SuppressWarnings("unchecked") Class idClass = (Class) registryEntry diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourceIncludeField.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourceIncludeField.java index 7345822cd..62ba70d31 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourceIncludeField.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourceIncludeField.java @@ -2,12 +2,14 @@ import io.crnk.core.engine.document.Document; import io.crnk.core.engine.http.HttpMethod; +import io.crnk.core.engine.information.resource.ResourceField; import io.crnk.core.engine.internal.document.mapper.DocumentMapper; import io.crnk.core.engine.parser.TypeParser; import io.crnk.core.engine.registry.RegistryEntry; import io.crnk.core.engine.registry.ResourceRegistry; import io.crnk.core.exception.RepositoryNotFoundException; import io.crnk.core.exception.RequestBodyNotFoundException; +import io.crnk.core.exception.ResourceFieldNotFoundException; public abstract class ResourceIncludeField extends BaseController { @@ -23,6 +25,12 @@ public ResourceIncludeField(ResourceRegistry resourceRegistry, TypeParser typePa this.documentMapper = documentMapper; } + protected static void verifyFieldNotNull(ResourceField field, String elementName) { + if (field == null) { + throw new ResourceFieldNotFoundException(elementName); + } + } + protected void assertRequestDocument(Document requestDocument, HttpMethod method, String resourceType) { if (requestDocument == null) { throw new RequestBodyNotFoundException(method, resourceType); diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourcePatch.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourcePatch.java index 37f8b4998..5e489c7d0 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourcePatch.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourcePatch.java @@ -54,9 +54,11 @@ public Response handle(JsonPath jsonPath, QueryAdapter queryAdapter, ResourceInformation resourceInformation = bodyRegistryEntry.getResourceInformation(); Serializable resourceId = resourceInformation.parseIdString(idString); + verifyTypes(HttpMethod.PATCH, endpointRegistryEntry, bodyRegistryEntry); + ResourceRepositoryAdapter resourceRepository = endpointRegistryEntry.getResourceRepository(parameterProvider); JsonApiResponse resourceFindResponse = resourceRepository.findOne(resourceId, queryAdapter); - Object resource = extractResource(resourceFindResponse); + Object resource = resourceFindResponse.getEntity(); if (resource == null) { throw new ResourceNotFoundException(jsonPath.toString()); } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourcePost.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourcePost.java index dceaa8daa..42c6f27a4 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourcePost.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourcePost.java @@ -48,6 +48,7 @@ public Response handle(JsonPath jsonPath, QueryAdapter queryAdapter, RegistryEntry endpointRegistryEntry = getRegistryEntry(jsonPath); Resource resourceBody = getRequestBody(requestDocument, jsonPath, HttpMethod.POST); RegistryEntry bodyRegistryEntry = resourceRegistry.getEntry(resourceBody.getType()); + verifyTypes(HttpMethod.POST, endpointRegistryEntry, bodyRegistryEntry); ResourceRepositoryAdapter resourceRepository = endpointRegistryEntry.getResourceRepository(parameterProvider); diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourceUpsert.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourceUpsert.java index b3e992276..703ba4a70 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourceUpsert.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/dispatcher/controller/ResourceUpsert.java @@ -1,14 +1,5 @@ package io.crnk.core.engine.internal.dispatcher.controller; -import java.io.Serializable; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.crnk.core.boot.CrnkProperties; @@ -23,6 +14,8 @@ import io.crnk.core.engine.internal.dispatcher.path.JsonPath; import io.crnk.core.engine.internal.document.mapper.DocumentMapper; import io.crnk.core.engine.internal.information.resource.ResourceAttributesBridge; +import io.crnk.core.engine.internal.utils.ClassUtils; +import io.crnk.core.engine.internal.utils.PreconditionUtil; import io.crnk.core.engine.internal.utils.PropertyUtils; import io.crnk.core.engine.parser.TypeParser; import io.crnk.core.engine.properties.PropertiesProvider; @@ -38,6 +31,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Serializable; +import java.util.*; +import java.util.Map.Entry; + public abstract class ResourceUpsert extends ResourceIncludeField { @@ -50,7 +47,7 @@ public abstract class ResourceUpsert extends ResourceIncludeField { private PropertiesProvider propertiesProvider; public ResourceUpsert(ResourceRegistry resourceRegistry, PropertiesProvider propertiesProvider, TypeParser typeParser, - ObjectMapper objectMapper, DocumentMapper documentMapper) { + ObjectMapper objectMapper, DocumentMapper documentMapper) { super(resourceRegistry, typeParser, documentMapper); this.propertiesProvider = propertiesProvider; this.objectMapper = objectMapper; @@ -58,28 +55,21 @@ public ResourceUpsert(ResourceRegistry resourceRegistry, PropertiesProvider prop protected Resource getRequestBody(Document requestDocument, JsonPath path, HttpMethod method) { String resourceType = path.getResourceType(); - RegistryEntry endpointRegistryEntry = getRegistryEntry(path); assertRequestDocument(requestDocument, method, resourceType); - if (requestDocument.getData() instanceof Collection) { + if (!requestDocument.getData().isPresent() || requestDocument.getData().get() == null) { + throw new RequestBodyException(method, resourceType, "No data field in the body."); + } + if (requestDocument.getData().get() instanceof Collection) { throw new RequestBodyException(method, resourceType, "Multiple data in body"); } Resource resourceBody = (Resource) requestDocument.getData().get(); - if (resourceBody == null) { - throw new RequestBodyException(method, resourceType, "No data field in the body."); - } RegistryEntry bodyRegistryEntry = resourceRegistry.getEntry(resourceBody.getType()); if (bodyRegistryEntry == null) { throw new RepositoryNotFoundException(resourceBody.getType()); } - - if (path.getElementName() == null) { - // TODO add relationship type validation as well - verifyTypes(method, resourceType, endpointRegistryEntry, bodyRegistryEntry); - } - return resourceBody; } @@ -125,8 +115,7 @@ protected void setAttributes(Resource dataBody, Object instance, ResourceInforma ResourceField field = resourceInformation.findAttributeFieldByName(attributeName); if (canModifyField(resourceInformation, attributeName, field)) { resourceAttributesBridge.setProperty(objectMapper, instance, entry.getValue(), entry.getKey()); - } - else { + } else { handleImmutableField(entry.getKey()); } } @@ -142,8 +131,7 @@ private void handleImmutableField(String fieldName) { : ResourceFieldImmutableWriteBehavior.IGNORE; if (behavior == ResourceFieldImmutableWriteBehavior.IGNORE) { logger.debug("attribute '{}' is immutable", fieldName); - } - else { + } else { throw new BadRequestException("attribute '" + fieldName + "' is immutable"); } } @@ -157,30 +145,19 @@ private void handleImmutableField(String fieldName) { Object buildNewResource(RegistryEntry registryEntry, Resource dataBody, String resourceName) { - if (dataBody == null) { - throw new ResourceException("No data field in the body."); - } - if (!resourceName.equals(dataBody.getType())) { - throw new ResourceException(String.format("Inconsistent type definition between path and body: body type: " + - "%s, request type: %s", - dataBody.getType(), - resourceName)); - } - try { - return registryEntry.getResourceInformation() - .getResourceClass() - .newInstance(); - } - catch (InstantiationException | IllegalAccessException e) { - throw new ResourceException( - String.format("couldn't create a new instance of %s", registryEntry.getResourceInformation() - .getResourceClass())); - } + PreconditionUtil.verify(dataBody != null, "No data field in the body."); + PreconditionUtil.verify(resourceName.equals(dataBody.getType()), "Inconsistent type definition between path and body: body type: " + + "%s, request type: %s", + dataBody.getType(), + resourceName); + Class resourceClass = registryEntry.getResourceInformation() + .getResourceClass(); + return ClassUtils.newInstance(resourceClass); } protected void setRelations(Object newResource, RegistryEntry registryEntry, Resource resource, QueryAdapter queryAdapter, - RepositoryMethodParameterProvider parameterProvider) { + RepositoryMethodParameterProvider parameterProvider) { if (resource.getRelationships() != null) { for (Map.Entry property : resource.getRelationships().entrySet()) { String propertyName = property.getKey(); @@ -200,8 +177,7 @@ protected void setRelations(Object newResource, RegistryEntry registryEntry, Res property, queryAdapter, parameterProvider); - } - else { + } else { //noinspection unchecked setRelationField(newResource, registryEntry, propertyName, relationship, queryAdapter, parameterProvider); @@ -212,8 +188,8 @@ protected void setRelations(Object newResource, RegistryEntry registryEntry, Res } protected void setRelationsField(Object newResource, RegistryEntry registryEntry, - Map.Entry property, QueryAdapter queryAdapter, - RepositoryMethodParameterProvider parameterProvider) { + Map.Entry property, QueryAdapter queryAdapter, + RepositoryMethodParameterProvider parameterProvider) { Relationship relationship = property.getValue(); if (relationship.getData().isPresent()) { String propertyName = property.getKey(); @@ -235,8 +211,8 @@ protected void setRelationsField(Object newResource, RegistryEntry registryEntry } protected void setRelationField(Object newResource, RegistryEntry registryEntry, - String relationshipName, Relationship relationship, QueryAdapter queryAdapter, - RepositoryMethodParameterProvider parameterProvider) { + String relationshipName, Relationship relationship, QueryAdapter queryAdapter, + RepositoryMethodParameterProvider parameterProvider) { if (relationship.getData().isPresent()) { ResourceIdentifier relationshipId = (ResourceIdentifier) relationship.getData().get(); @@ -257,8 +233,7 @@ protected void setRelationField(Object newResource, RegistryEntry registryEntry, Serializable castedRelationshipId = typeParser.parse(relationshipId.getId(), idFieldType); relationObject = fetchRelatedObject(entry, castedRelationshipId, parameterProvider, queryAdapter); - } - else { + } else { relationObject = null; } relationshipFieldByName.getAccessor().setValue(newResource, relationObject); @@ -266,8 +241,8 @@ protected void setRelationField(Object newResource, RegistryEntry registryEntry, } protected Object fetchRelatedObject(RegistryEntry entry, Serializable relationId, - RepositoryMethodParameterProvider parameterProvider, - QueryAdapter queryAdapter) { + RepositoryMethodParameterProvider parameterProvider, + QueryAdapter queryAdapter) { return entry.getResourceRepository(parameterProvider).findOne(relationId, queryAdapter).getEntity(); } } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperRegistry.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperRegistry.java index a9f4ccd0f..5b72c5cf6 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperRegistry.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperRegistry.java @@ -61,6 +61,7 @@ public Optional> findMapperFor(ErrorRes int getDistanceBetweenExceptions(Class clazz, Class mapperTypeClazz) { int distance = 0; Class superClazz = clazz; + if (!mapperTypeClazz.isAssignableFrom(clazz)) { return Integer.MAX_VALUE; } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperRegistryBuilder.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperRegistryBuilder.java index 34969d7b8..c674d8583 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperRegistryBuilder.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperRegistryBuilder.java @@ -1,15 +1,14 @@ package io.crnk.core.engine.internal.exception; +import io.crnk.core.engine.error.JsonApiExceptionMapper; +import io.crnk.core.engine.internal.utils.ClassUtils; +import io.crnk.legacy.internal.DefaultExceptionMapperLookup; + import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashSet; import java.util.Set; -import io.crnk.core.engine.error.ExceptionMapper; -import io.crnk.core.engine.error.JsonApiExceptionMapper; -import io.crnk.core.engine.internal.utils.TypeUtils; -import io.crnk.legacy.internal.DefaultExceptionMapperLookup; - public final class ExceptionMapperRegistryBuilder { private final Set exceptionMappers = new HashSet<>(); @@ -32,7 +31,7 @@ private void addDefaultMappers() { private void registerExceptionMapper(JsonApiExceptionMapper exceptionMapper) { Class mapperClass = exceptionMapper.getClass(); Class exceptionClass = getGenericType(mapperClass); - if(exceptionClass == null && mapperClass.getName().contains("$$")){ + if (exceptionClass == null && isProxy(mapperClass)) { // deal if dynamic proxies, like in CDI mapperClass = (Class) mapperClass.getSuperclass(); exceptionClass = getGenericType(mapperClass); @@ -41,6 +40,11 @@ private void registerExceptionMapper(JsonApiExceptionMapper exceptionMappers.add(new ExceptionMapperType(exceptionClass, exceptionMapper)); } + private boolean isProxy(Class mapperClass) { + return mapperClass.getName().contains("$$") + && JsonApiExceptionMapper.class.isAssignableFrom(mapperClass.getSuperclass()); + } + private Class getGenericType(Class mapper) { Type[] types = mapper.getGenericInterfaces(); if (null == types || 0 == types.length) { @@ -48,15 +52,20 @@ private Class getGenericType(Class rawType = ClassUtils.getRawType(type); + if (type instanceof ParameterizedType && JsonApiExceptionMapper.class.isAssignableFrom(rawType)) + { //noinspection unchecked return (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; } } + + if(isProxy(mapper)){ + return getGenericType((Class) mapper.getSuperclass()); + } + //Won't get in here - return null; + throw new IllegalStateException("unable to discover exception class for " + mapper.getName()); } } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperType.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperType.java index 161c0a7a5..de7034fb4 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperType.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/exception/ExceptionMapperType.java @@ -1,6 +1,7 @@ package io.crnk.core.engine.internal.exception; import io.crnk.core.engine.error.JsonApiExceptionMapper; +import io.crnk.core.engine.internal.utils.PreconditionUtil; import java.util.Objects; @@ -11,6 +12,7 @@ final class ExceptionMapperType { public ExceptionMapperType(Class exceptionClass, JsonApiExceptionMapper exceptionMapper) { this.exceptionMapper = exceptionMapper; this.exceptionClass = exceptionClass; + PreconditionUtil.assertNotNull("exceptionClass must not be null", exceptionClass); } public Class getExceptionClass() { @@ -41,9 +43,9 @@ public int hashCode() { @Override public String toString() { - return "ExceptionMapperType{" + - "exceptionClass=" + exceptionClass + + return "ExceptionMapperType[" + + "exceptionClass=" + exceptionClass.getName() + ", exceptionMapper=" + exceptionMapper + - '}'; + ']'; } } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/DefaultInformationBuilder.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/DefaultInformationBuilder.java index 59815dd14..438e41fc3 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/DefaultInformationBuilder.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/DefaultInformationBuilder.java @@ -157,10 +157,14 @@ public class DefaultField implements InformationBuilder.Field { private ResourceFieldAccess access = new ResourceFieldAccess(true, true, true, true); public ResourceField build() { - return new ResourceFieldImpl(jsonName, underlyingName, fieldType, type, + ResourceFieldImpl impl = new ResourceFieldImpl(jsonName, underlyingName, fieldType, type, genericType, oppositeResourceType, oppositeName, lazy, includeByDefault, lookupIncludeBehavior, access); + if (accessor != null) { + impl.setAccessor(accessor); + } + return impl; } public void jsonName(String jsonName) { diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/repository/RelationshipRepositoryInformationImpl.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/repository/RelationshipRepositoryInformationImpl.java index 1c5d93032..9b90510e5 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/repository/RelationshipRepositoryInformationImpl.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/repository/RelationshipRepositoryInformationImpl.java @@ -19,13 +19,6 @@ public RelationshipRepositoryInformationImpl(Class sourceResourceClass, String s this.targetResourceType = targetResourceType; } - public RelationshipRepositoryInformationImpl(String sourceResourceType, - String targetResourceType) { - this.sourceResourceClass = Optional.empty(); - this.sourceResourceType = sourceResourceType; - this.targetResourceType = targetResourceType; - } - @Override public Optional getSourceResourceClass() { return sourceResourceClass; diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/repository/ResourceRepositoryInformationImpl.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/repository/ResourceRepositoryInformationImpl.java index 23e232e5b..05685a9c8 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/repository/ResourceRepositoryInformationImpl.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/repository/ResourceRepositoryInformationImpl.java @@ -10,21 +10,21 @@ public class ResourceRepositoryInformationImpl implements ResourceRepositoryInformation { - private final Optional resourceInformation; - private final String resourceType; + private Optional resourceInformation; + private String resourceType; private String path; private Map actions; + @Deprecated public ResourceRepositoryInformationImpl(String path, ResourceInformation resourceInformation) { this(path, resourceInformation, new HashMap()); } + @Deprecated public ResourceRepositoryInformationImpl(String path, ResourceInformation resourceInformation, Map actions) { - this.path = path; - this.actions = actions; + this(path, resourceInformation.getResourceType(), actions); this.resourceInformation = Optional.of(resourceInformation); - this.resourceType = resourceInformation.getResourceType(); } public ResourceRepositoryInformationImpl(String path, String resourceType, Map actions) { diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/resource/AnnotationResourceInformationBuilder.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/resource/AnnotationResourceInformationBuilder.java index 58694b2a3..23bdbbd83 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/resource/AnnotationResourceInformationBuilder.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/resource/AnnotationResourceInformationBuilder.java @@ -18,10 +18,7 @@ import io.crnk.core.engine.information.resource.ResourceInformation; import io.crnk.core.engine.information.resource.ResourceInformationBuilder; import io.crnk.core.engine.information.resource.ResourceInformationBuilderContext; -import io.crnk.core.engine.internal.utils.ClassUtils; -import io.crnk.core.engine.internal.utils.FieldOrderedComparator; -import io.crnk.core.engine.internal.utils.PropertyUtils; -import io.crnk.core.engine.internal.utils.StringUtils; +import io.crnk.core.engine.internal.utils.*; import io.crnk.core.exception.RepositoryAnnotationNotFoundException; import io.crnk.core.exception.ResourceIdNotFoundException; import io.crnk.core.resource.annotations.JsonApiField; @@ -224,9 +221,7 @@ private List getGetterResourceFields(Class resourceClas List fieldWrappers = new ArrayList<>(classGetters.size()); for (Method getter : classGetters) { String underlyingName = ClassUtils.getGetterFieldName(getter); - if (underlyingName == null) { - continue; - } + PreconditionUtil.assertNotNull("not a valid getter", underlyingName); String jsonName = resourceFieldNameTransformer.getName(getter); fieldWrappers.add(getResourceField(resourceClass, getter, jsonName, underlyingName, getter.getReturnType(), getter.getGenericReturnType(), Arrays.asList(getter.getAnnotations()))); } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/resource/RawResourceFieldAccessor.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/resource/RawResourceFieldAccessor.java index 14f8a1183..385694c18 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/information/resource/RawResourceFieldAccessor.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/information/resource/RawResourceFieldAccessor.java @@ -6,6 +6,7 @@ import io.crnk.core.engine.document.Relationship; import io.crnk.core.engine.document.Resource; import io.crnk.core.engine.information.resource.ResourceFieldType; +import io.crnk.core.engine.internal.utils.PreconditionUtil; import java.io.IOException; import java.util.Map; @@ -46,14 +47,17 @@ public Object getValue(Object objResource) { return null; case META_INFORMATION: return toObject(resource.getMeta()); - case LINKS_INFORMATION: + default: + PreconditionUtil.assertEquals("invalid type", fieldType, ResourceFieldType.LINKS_INFORMATION); return toObject(resource.getLinks()); } - throw new IllegalStateException(); } private Object toObject(JsonNode node) { try { + if (node == null) { + return null; + } return reader.readValue(node); } catch (IOException e) { throw new IllegalStateException(e); diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/jackson/ErrorDataDeserializer.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/jackson/ErrorDataDeserializer.java index d9ed102d9..049d09055 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/jackson/ErrorDataDeserializer.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/jackson/ErrorDataDeserializer.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import io.crnk.core.engine.document.ErrorData; -import io.crnk.core.engine.error.ErrorResponse; import java.io.IOException; import java.util.Map; @@ -59,12 +58,7 @@ private static String readStringIfExists(String fieldName, JsonNode errorNode) t @Override public ErrorData deserialize(JsonParser jp, DeserializationContext context) throws IOException { - JsonNode errorNode = jp.readValueAsTree(); - if (errorNode == null) { - return null; - } - String id = readStringIfExists(ErrorDataSerializer.ID, errorNode); String aboutLink = readAboutLink(errorNode); String status = readStringIfExists(ErrorDataSerializer.STATUS, errorNode); @@ -77,8 +71,4 @@ public ErrorData deserialize(JsonParser jp, DeserializationContext context) thro return new ErrorData(id, aboutLink, status, code, title, detail, sourcePointer, sourceParameter, meta); } - @Override - public Class handledType() { - return ErrorResponse.class; - } } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/registry/ResourceRegistryImpl.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/registry/ResourceRegistryImpl.java index d7dcc1ff6..faefe0984 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/registry/ResourceRegistryImpl.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/registry/ResourceRegistryImpl.java @@ -7,7 +7,6 @@ import io.crnk.core.engine.registry.ResourceRegistryPart; import io.crnk.core.engine.url.ServiceUrlProvider; import io.crnk.core.exception.RepositoryNotFoundException; -import io.crnk.core.exception.ResourceNotFoundInitializationException; import io.crnk.core.module.ModuleRegistry; import io.crnk.core.utils.Optional; @@ -56,11 +55,9 @@ protected RegistryEntry findEntry(Class clazz, boolean allowNull) { /** * Searches the registry for a resource identified by a JSON API resource * class. If a resource cannot be found, - * {@link ResourceNotFoundInitializationException} is thrown. * * @param clazz resource type * @return registry entry - * @throws ResourceNotFoundInitializationException if resource is not found */ public RegistryEntry findEntry(Class clazz) { return findEntry(clazz, false); diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/repository/RepositoryRequestSpecImpl.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/repository/RepositoryRequestSpecImpl.java index 2c17a5fc1..bf2177241 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/repository/RepositoryRequestSpecImpl.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/repository/RepositoryRequestSpecImpl.java @@ -121,9 +121,6 @@ public ResourceField getRelationshipField() { @Override public QuerySpec getQuerySpec(ResourceInformation targetResourceInformation) { - if (queryAdapter == null) { - return null; - } Class targetResourceClass = targetResourceInformation.getResourceClass(); if (queryAdapter instanceof QuerySpecAdapter) { QuerySpec querySpec = ((QuerySpecAdapter) queryAdapter).getQuerySpec(); @@ -136,10 +133,6 @@ public QuerySpec getQuerySpec(ResourceInformation targetResourceInformation) { @Override public QueryParams getQueryParams() { - if (queryAdapter == null) { - return null; - } - return queryAdapter.toQueryParams(); } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/ClassUtils.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/ClassUtils.java index 2a73cee25..41a14268d 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/ClassUtils.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/ClassUtils.java @@ -1,18 +1,14 @@ package io.crnk.core.engine.internal.utils; +import io.crnk.core.exception.ResourceException; +import io.crnk.core.utils.Optional; + import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import io.crnk.core.exception.ResourceException; -import io.crnk.core.utils.Optional; +import java.util.*; /** @@ -55,202 +51,25 @@ private ClassUtils() { } - /** - * Returns whether the given {@code type} is a primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character}, {@link Short}, - * {@link Integer}, {@link Long}, {@link Double}, {@link Float}). - * - * @param type - * The class to query or null. - * @return true if the given {@code type} is a primitive wrapper ({@link Boolean}, {@link Byte}, {@link Character}, {@link Short}, - * {@link Integer}, {@link Long}, {@link Double}, {@link Float}). - * @since 3.1 - */ - public static boolean isPrimitiveWrapper(final Class type) { - return wrapperPrimitiveMap.containsKey(type); - } - - - /** - *

Checks if one {@code Class} can be assigned to a variable of - * another {@code Class}.

- * - *

Unlike the {@link Class#isAssignableFrom(java.lang.Class)} method, - * this method takes into account widenings of primitive classes and - * {@code null}s.

- * - *

Primitive widenings allow an int to be assigned to a long, float or - * double. This method returns the correct result for these cases.

- * - *

{@code Null} may be assigned to any reference type. This method - * will return {@code true} if {@code null} is passed in and the - * toClass is non-primitive.

- * - *

Specifically, this method tests whether the type represented by the - * specified {@code Class} parameter can be converted to the type - * represented by this {@code Class} object via an identity conversion - * widening primitive or widening reference conversion. See - * The Java Language Specification, - * sections 5.1.1, 5.1.2 and 5.1.4 for details.

- * - * @param cls the Class to check, may be null - * @param toClass the Class to try to assign into, returns false if null - * @return {@code true} if assignment possible - */ - public static boolean isAssignable(Class cls, final Class toClass) { - final boolean autoboxing = true; - if (toClass == null) { - return false; - } - // have to check for null, as isAssignableFrom doesn't - if (cls == null) { - return !toClass.isPrimitive(); - } - //autoboxing: - if (autoboxing) { - if (cls.isPrimitive() && !toClass.isPrimitive()) { - cls = primitiveToWrapper(cls); - if (cls == null) { - return false; - } - } - if (toClass.isPrimitive() && !cls.isPrimitive()) { - cls = wrapperToPrimitive(cls); - if (cls == null) { - return false; - } - } - } - if (cls.equals(toClass)) { + public static boolean existsClass(String className) { + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + loadClass(classLoader, className); return true; - } - if (cls.isPrimitive()) { - if (toClass.isPrimitive() == false) { - return false; - } - if (Integer.TYPE.equals(cls)) { - return Long.TYPE.equals(toClass) - || Float.TYPE.equals(toClass) - || Double.TYPE.equals(toClass); - } - if (Long.TYPE.equals(cls)) { - return Float.TYPE.equals(toClass) - || Double.TYPE.equals(toClass); - } - if (Boolean.TYPE.equals(cls)) { - return false; - } - if (Double.TYPE.equals(cls)) { - return false; - } - if (Float.TYPE.equals(cls)) { - return Double.TYPE.equals(toClass); - } - if (Character.TYPE.equals(cls)) { - return Integer.TYPE.equals(toClass) - || Long.TYPE.equals(toClass) - || Float.TYPE.equals(toClass) - || Double.TYPE.equals(toClass); - } - if (Short.TYPE.equals(cls)) { - return Integer.TYPE.equals(toClass) - || Long.TYPE.equals(toClass) - || Float.TYPE.equals(toClass) - || Double.TYPE.equals(toClass); - } - if (Byte.TYPE.equals(cls)) { - return Short.TYPE.equals(toClass) - || Integer.TYPE.equals(toClass) - || Long.TYPE.equals(toClass) - || Float.TYPE.equals(toClass) - || Double.TYPE.equals(toClass); - } - // should never get here + } catch (IllegalStateException e) { return false; } - return toClass.isAssignableFrom(cls); } - public static Class primitiveToWrapper(Class cls) { - Class convertedClass = cls; - if (cls != null && cls.isPrimitive()) { - convertedClass = (Class) primitiveWrapperMap.get(cls); - } - - return convertedClass; - } - - public static Class[] primitivesToWrappers(Class... classes) { - if (classes == null) { - return null; - } - else if (classes.length == 0) { - return classes; - } - else { - Class[] convertedClasses = new Class[classes.length]; - - for (int i = 0; i < classes.length; ++i) { - convertedClasses[i] = primitiveToWrapper(classes[i]); - } - - return convertedClasses; - } - } - - public static Class wrapperToPrimitive(Class cls) { - return (Class) wrapperPrimitiveMap.get(cls); - } - - public static Class[] wrappersToPrimitives(Class... classes) { - if (classes == null) { - return null; - } - else if (classes.length == 0) { - return classes; - } - else { - Class[] convertedClasses = new Class[classes.length]; - - for (int i = 0; i < classes.length; ++i) { - convertedClasses[i] = wrapperToPrimitive(classes[i]); - } - - return convertedClasses; - } - } - - - @Deprecated // at least current use cases should be eliminated and replace by resourceType - public static Class getResourceClass(Type genericType, Class baseClass) { - if (Iterable.class.isAssignableFrom(baseClass)) { - if (genericType instanceof ParameterizedType) { - ParameterizedType aType = (ParameterizedType) genericType; - Type[] fieldArgTypes = aType.getActualTypeArguments(); - if (fieldArgTypes.length == 1 && fieldArgTypes[0] instanceof Class) { - return (Class) fieldArgTypes[0]; - } - else { - throw new IllegalArgumentException("Wrong type: " + aType); - } - } - else { - throw new IllegalArgumentException("The relationship must be parametrized (cannot be wildcard or array): " - + genericType); - } - } - return baseClass; - } - - public static boolean existsClass(String className) { + public static Class loadClass(ClassLoader classLoader, String className) { try { - Class.forName(className); - return true; - } - catch (ClassNotFoundException e) { - return false; + return classLoader.loadClass(className); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(className); } } + /** * Returns a list of class fields. Supports inheritance and doesn't return synthetic fields. * @@ -281,9 +100,9 @@ public static List getClassFields(Class beanClass) { /** * Returns an instance of bean's annotation * - * @param beanClass class to be searched for + * @param beanClass class to be searched for * @param annotationClass type of an annotation - * @param type of an annotation + * @param type of an annotation * @return an instance of an annotation */ public static Optional getAnnotation(Class beanClass, Class annotationClass) { @@ -388,8 +207,7 @@ public static Method findSetter(Class beanClass, String fieldName, Class f try { return beanClass.getMethod("set" + upperCaseName, fieldType); - } - catch (NoSuchMethodException e1) { + } catch (NoSuchMethodException e1) { return null; } } @@ -423,7 +241,7 @@ public static List getClassGetters(Class beanClass) { } private static void getDeclaredClassGetters(Class currentClass, Map resultMap, - LinkedList results) { + LinkedList results) { for (Method method : currentClass.getDeclaredMethods()) { if (!method.isSynthetic() && isGetter(method)) { Method v = resultMap.get(method.getName()); @@ -465,7 +283,7 @@ public static List getClassSetters(Class beanClass) { /** * Return a first occurrence of a method annotated with specified annotation * - * @param searchClass class to be searched + * @param searchClass class to be searched * @param annotationClass annotation class * @return annotated method or null */ @@ -485,14 +303,13 @@ public static Method findMethodWith(Class searchClass, Class new instance class + * @param new instance class * @return new instance */ public static T newInstance(Class clazz) { try { return clazz.newInstance(); - } - catch (InstantiationException | IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { throw new ResourceException(String.format("couldn't create a new instance of %s", clazz)); } } @@ -541,11 +358,9 @@ private static boolean isSetter(Method method) { public static Class getRawType(Type type) { if (type instanceof Class) { return (Class) type; - } - else if (type instanceof ParameterizedType) { + } else if (type instanceof ParameterizedType) { return getRawType(((ParameterizedType) type).getRawType()); - } - else { + } else { throw new IllegalStateException("unknown type: " + type); } } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/PreconditionUtil.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/PreconditionUtil.java index da5c2dc80..9000b1e7c 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/PreconditionUtil.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/PreconditionUtil.java @@ -67,9 +67,7 @@ public static void assertNotNull(String message, Object object) { * @param condition condition to be checked */ public static void assertTrue(String message, boolean condition) { - if (!condition) { - fail(message); - } + verify(condition, message); } /** @@ -95,4 +93,11 @@ public static void assertFalse(String message, boolean condition) { public static void assertNull(String message, Object object) { assertTrue(message, object == null); } + + public static void verify(boolean condition, String messageFormat, Object... args) { + if (!condition) { + String message = messageFormat != null ? String.format(messageFormat, args) : null; + fail(message); + } + } } diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/TypeUtils.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/TypeUtils.java deleted file mode 100644 index 3aed1028a..000000000 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/utils/TypeUtils.java +++ /dev/null @@ -1,1179 +0,0 @@ -package io.crnk.core.engine.internal.utils; - -import java.lang.reflect.Array; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - - -/** - *

Utility methods focusing on type inspection, particularly with regard to - * generics.

- */ -public class TypeUtils { - - /** - *

{@code TypeUtils} instances should NOT be constructed in standard - * programming. Instead, the class should be used as - * {@code TypeUtils.isAssignable(cls, toClass)}.

This - * constructor is public to permit tools that require a JavaBean instance to - * operate.

- */ - public TypeUtils() { - super(); - } - - /** - *

Checks if the subject type may be implicitly cast to the target type - * following the Java generics rules. If both types are {@link Class} - * objects, the method returns the result of - * {@link ClassUtils#isAssignable(Class, Class)}.

- * - * @param type the subject type to be assigned to the target type - * @param toType the target type - * @return {@code true} if {@code type} is assignable to {@code toType}. - */ - public static boolean isAssignable(final Type type, final Type toType) { - return isAssignable(type, toType, null); - } - - /** - *

Checks if the subject type may be implicitly cast to the target type - * following the Java generics rules.

- * - * @param type the subject type to be assigned to the target type - * @param toType the target type - * @param typeVarAssigns optional map of type variable assignments - * @return {@code true} if {@code type} is assignable to {@code toType}. - */ - private static boolean isAssignable(final Type type, final Type toType, - final Map, Type> typeVarAssigns) { - if (toType == null || toType instanceof Class) { - return isAssignable(type, (Class) toType); - } - - if (toType instanceof ParameterizedType) { - return isAssignable(type, (ParameterizedType) toType, typeVarAssigns); - } - - if (toType instanceof GenericArrayType) { - return isAssignable(type, (GenericArrayType) toType, typeVarAssigns); - } - - if (toType instanceof WildcardType) { - return isAssignable(type, (WildcardType) toType, typeVarAssigns); - } - - if (toType instanceof TypeVariable) { - return isAssignable(type, (TypeVariable) toType, typeVarAssigns); - } - - throw new IllegalStateException("found an unhandled type: " + toType); - } - - /** - *

Checks if the subject type may be implicitly cast to the target class - * following the Java generics rules.

- * - * @param type the subject type to be assigned to the target type - * @param toClass the target class - * @return {@code true} if {@code type} is assignable to {@code toClass}. - */ - private static boolean isAssignable(final Type type, final Class toClass) { - if (type == null) { - // consistency with ClassUtils.isAssignable() behavior - return toClass == null || !toClass.isPrimitive(); - } - - // only a null type can be assigned to null type which - // would have cause the previous to return true - if (toClass == null) { - return false; - } - - // all types are assignable to themselves - if (toClass.equals(type)) { - return true; - } - - if (type instanceof Class) { - // just comparing two classes - return ClassUtils.isAssignable((Class) type, toClass); - } - - if (type instanceof ParameterizedType) { - // only have to compare the raw type to the class - return isAssignable(getRawType((ParameterizedType) type), toClass); - } - - // * - if (type instanceof TypeVariable) { - // if any of the bounds are assignable to the class, then the - // type is assignable to the class. - for (final Type bound : ((TypeVariable) type).getBounds()) { - if (isAssignable(bound, toClass)) { - return true; - } - } - - return false; - } - - // the only classes to which a generic array type can be assigned - // are class Object and array classes - if (type instanceof GenericArrayType) { - return toClass.equals(Object.class) - || toClass.isArray() - && isAssignable(((GenericArrayType) type).getGenericComponentType(), toClass - .getComponentType()); - } - - // wildcard types are not assignable to a class (though one would think - // "? super Object" would be assignable to Object) - if (type instanceof WildcardType) { - return false; - } - - throw new IllegalStateException("found an unhandled type: " + type); - } - - /** - *

Checks if the subject type may be implicitly cast to the target - * parameterized type following the Java generics rules.

- * - * @param type the subject type to be assigned to the target type - * @param toParameterizedType the target parameterized type - * @param typeVarAssigns a map with type variables - * @return {@code true} if {@code type} is assignable to {@code toType}. - */ - private static boolean isAssignable(final Type type, final ParameterizedType toParameterizedType, - final Map, Type> typeVarAssigns) { - if (type == null) { - return true; - } - - // only a null type can be assigned to null type which - // would have cause the previous to return true - if (toParameterizedType == null) { - return false; - } - - // all types are assignable to themselves - if (toParameterizedType.equals(type)) { - return true; - } - - // get the target type's raw type - final Class toClass = getRawType(toParameterizedType); - // get the subject type's type arguments including owner type arguments - // and supertype arguments up to and including the target class. - final Map, Type> fromTypeVarAssigns = getTypeArguments(type, toClass, null); - - // null means the two types are not compatible - if (fromTypeVarAssigns == null) { - return false; - } - - // compatible types, but there's no type arguments. this is equivalent - // to comparing Map< ?, ? > to Map, and raw types are always assignable - // to parameterized types. - if (fromTypeVarAssigns.isEmpty()) { - return true; - } - - // get the target type's type arguments including owner type arguments - final Map, Type> toTypeVarAssigns = getTypeArguments(toParameterizedType, - toClass, typeVarAssigns); - - // now to check each type argument - for (final TypeVariable var : toTypeVarAssigns.keySet()) { - final Type toTypeArg = unrollVariableAssignments(var, toTypeVarAssigns); - final Type fromTypeArg = unrollVariableAssignments(var, fromTypeVarAssigns); - - if (toTypeArg == null && fromTypeArg instanceof Class) { - continue; - } - - // parameters must either be absent from the subject type, within - // the bounds of the wildcard type, or be an exact match to the - // parameters of the target type. - if (fromTypeArg != null - && !toTypeArg.equals(fromTypeArg) - && !(toTypeArg instanceof WildcardType && isAssignable(fromTypeArg, toTypeArg, - typeVarAssigns))) { - return false; - } - } - return true; - } - - /** - * Look up {@code var} in {@code typeVarAssigns} transitively, - * i.e. keep looking until the value found is not a type variable. - * @param var the type variable to look up - * @param typeVarAssigns the map used for the look up - * @return Type or {@code null} if some variable was not in the map - * @since 3.2 - */ - private static Type unrollVariableAssignments(TypeVariable var, final Map, Type> typeVarAssigns) { - Type result; - do { - result = typeVarAssigns.get(var); - if (result instanceof TypeVariable && !result.equals(var)) { - var = (TypeVariable) result; - continue; - } - break; - } while (true); - return result; - } - - /** - *

Checks if the subject type may be implicitly cast to the target - * generic array type following the Java generics rules.

- * - * @param type the subject type to be assigned to the target type - * @param toGenericArrayType the target generic array type - * @param typeVarAssigns a map with type variables - * @return {@code true} if {@code type} is assignable to - * {@code toGenericArrayType}. - */ - private static boolean isAssignable(final Type type, final GenericArrayType toGenericArrayType, - final Map, Type> typeVarAssigns) { - if (type == null) { - return true; - } - - // only a null type can be assigned to null type which - // would have cause the previous to return true - if (toGenericArrayType == null) { - return false; - } - - // all types are assignable to themselves - if (toGenericArrayType.equals(type)) { - return true; - } - - final Type toComponentType = toGenericArrayType.getGenericComponentType(); - - if (type instanceof Class) { - final Class cls = (Class) type; - - // compare the component types - return cls.isArray() - && isAssignable(cls.getComponentType(), toComponentType, typeVarAssigns); - } - - if (type instanceof GenericArrayType) { - // compare the component types - return isAssignable(((GenericArrayType) type).getGenericComponentType(), - toComponentType, typeVarAssigns); - } - - if (type instanceof WildcardType) { - // so long as one of the upper bounds is assignable, it's good - for (final Type bound : getImplicitUpperBounds((WildcardType) type)) { - if (isAssignable(bound, toGenericArrayType)) { - return true; - } - } - - return false; - } - - if (type instanceof TypeVariable) { - // probably should remove the following logic and just return false. - // type variables cannot specify arrays as bounds. - for (final Type bound : getImplicitBounds((TypeVariable) type)) { - if (isAssignable(bound, toGenericArrayType)) { - return true; - } - } - - return false; - } - - if (type instanceof ParameterizedType) { - // the raw type of a parameterized type is never an array or - // generic array, otherwise the declaration would look like this: - // Collection[]< ? extends String > collection; - return false; - } - - throw new IllegalStateException("found an unhandled type: " + type); - } - - /** - *

Checks if the subject type may be implicitly cast to the target - * wildcard type following the Java generics rules.

- * - * @param type the subject type to be assigned to the target type - * @param toWildcardType the target wildcard type - * @param typeVarAssigns a map with type variables - * @return {@code true} if {@code type} is assignable to - * {@code toWildcardType}. - */ - private static boolean isAssignable(final Type type, final WildcardType toWildcardType, - final Map, Type> typeVarAssigns) { - if (type == null) { - return true; - } - - // only a null type can be assigned to null type which - // would have cause the previous to return true - if (toWildcardType == null) { - return false; - } - - // all types are assignable to themselves - if (toWildcardType.equals(type)) { - return true; - } - - final Type[] toUpperBounds = getImplicitUpperBounds(toWildcardType); - final Type[] toLowerBounds = getImplicitLowerBounds(toWildcardType); - - if (type instanceof WildcardType) { - final WildcardType wildcardType = (WildcardType) type; - final Type[] upperBounds = getImplicitUpperBounds(wildcardType); - final Type[] lowerBounds = getImplicitLowerBounds(wildcardType); - - for (Type toBound : toUpperBounds) { - // if there are assignments for unresolved type variables, - // now's the time to substitute them. - toBound = substituteTypeVariables(toBound, typeVarAssigns); - - // each upper bound of the subject type has to be assignable to - // each - // upper bound of the target type - for (final Type bound : upperBounds) { - if (!isAssignable(bound, toBound, typeVarAssigns)) { - return false; - } - } - } - - for (Type toBound : toLowerBounds) { - // if there are assignments for unresolved type variables, - // now's the time to substitute them. - toBound = substituteTypeVariables(toBound, typeVarAssigns); - - // each lower bound of the target type has to be assignable to - // each - // lower bound of the subject type - for (final Type bound : lowerBounds) { - if (!isAssignable(toBound, bound, typeVarAssigns)) { - return false; - } - } - } - return true; - } - - for (final Type toBound : toUpperBounds) { - // if there are assignments for unresolved type variables, - // now's the time to substitute them. - if (!isAssignable(type, substituteTypeVariables(toBound, typeVarAssigns), - typeVarAssigns)) { - return false; - } - } - - for (final Type toBound : toLowerBounds) { - // if there are assignments for unresolved type variables, - // now's the time to substitute them. - if (!isAssignable(substituteTypeVariables(toBound, typeVarAssigns), type, - typeVarAssigns)) { - return false; - } - } - return true; - } - - /** - *

Checks if the subject type may be implicitly cast to the target type - * variable following the Java generics rules.

- * - * @param type the subject type to be assigned to the target type - * @param toTypeVariable the target type variable - * @param typeVarAssigns a map with type variables - * @return {@code true} if {@code type} is assignable to - * {@code toTypeVariable}. - */ - private static boolean isAssignable(final Type type, final TypeVariable toTypeVariable, - final Map, Type> typeVarAssigns) { - if (type == null) { - return true; - } - - // only a null type can be assigned to null type which - // would have cause the previous to return true - if (toTypeVariable == null) { - return false; - } - - // all types are assignable to themselves - if (toTypeVariable.equals(type)) { - return true; - } - - if (type instanceof TypeVariable) { - // a type variable is assignable to another type variable, if - // and only if the former is the latter, extends the latter, or - // is otherwise a descendant of the latter. - final Type[] bounds = getImplicitBounds((TypeVariable) type); - - for (final Type bound : bounds) { - if (isAssignable(bound, toTypeVariable, typeVarAssigns)) { - return true; - } - } - } - - if (type instanceof Class || type instanceof ParameterizedType - || type instanceof GenericArrayType || type instanceof WildcardType) { - return false; - } - - throw new IllegalStateException("found an unhandled type: " + type); - } - - /** - *

Find the mapping for {@code type} in {@code typeVarAssigns}.

- * - * @param type the type to be replaced - * @param typeVarAssigns the map with type variables - * @return the replaced type - * @throws IllegalArgumentException if the type cannot be substituted - */ - private static Type substituteTypeVariables(final Type type, final Map, Type> typeVarAssigns) { - if (type instanceof TypeVariable && typeVarAssigns != null) { - final Type replacementType = typeVarAssigns.get(type); - - if (replacementType == null) { - throw new IllegalArgumentException("missing assignment type for type variable " - + type); - } - return replacementType; - } - return type; - } - - /** - *

Retrieves all the type arguments for this parameterized type - * including owner hierarchy arguments such as - * {@code Outer.Inner.DeepInner} . - * The arguments are returned in a - * {@link Map} specifying the argument type for each {@link TypeVariable}. - *

- * - * @param type specifies the subject parameterized type from which to - * harvest the parameters. - * @return a {@code Map} of the type arguments to their respective type - * variables. - */ - public static Map, Type> getTypeArguments(final ParameterizedType type) { - return getTypeArguments(type, getRawType(type), null); - } - - /** - *

Gets the type arguments of a class/interface based on a subtype. For - * instance, this method will determine that both of the parameters for the - * interface {@link Map} are {@link Object} for the subtype - * {@link java.util.Properties Properties} even though the subtype does not - * directly implement the {@code Map} interface.

- *

This method returns {@code null} if {@code type} is not assignable to - * {@code toClass}. It returns an empty map if none of the classes or - * interfaces in its inheritance hierarchy specify any type arguments.

- *

A side effect of this method is that it also retrieves the type - * arguments for the classes and interfaces that are part of the hierarchy - * between {@code type} and {@code toClass}. So with the above - * example, this method will also determine that the type arguments for - * {@link java.util.Hashtable Hashtable} are also both {@code Object}. - * In cases where the interface specified by {@code toClass} is - * (indirectly) implemented more than once (e.g. where {@code toClass} - * specifies the interface {@link java.lang.Iterable Iterable} and - * {@code type} specifies a parameterized type that implements both - * {@link java.util.Set Set} and {@link java.util.Collection Collection}), - * this method will look at the inheritance hierarchy of only one of the - * implementations/subclasses; the first interface encountered that isn't a - * subinterface to one of the others in the {@code type} to - * {@code toClass} hierarchy.

- * - * @param type the type from which to determine the type parameters of - * {@code toClass} - * @param toClass the class whose type parameters are to be determined based - * on the subtype {@code type} - * @return a {@code Map} of the type assignments for the type variables in - * each type in the inheritance hierarchy from {@code type} to - * {@code toClass} inclusive. - */ - public static Map, Type> getTypeArguments(final Type type, final Class toClass) { - return getTypeArguments(type, toClass, null); - } - - /** - *

Return a map of the type arguments of {@code type} in the context of {@code toClass}.

- * - * @param type the type in question - * @param toClass the class - * @param subtypeVarAssigns a map with type variables - * @return the {@code Map} with type arguments - */ - private static Map, Type> getTypeArguments(final Type type, final Class toClass, - final Map, Type> subtypeVarAssigns) { - if (type instanceof Class) { - return getTypeArguments((Class) type, toClass, subtypeVarAssigns); - } - - if (type instanceof ParameterizedType) { - return getTypeArguments((ParameterizedType) type, toClass, subtypeVarAssigns); - } - - if (type instanceof GenericArrayType) { - return getTypeArguments(((GenericArrayType) type).getGenericComponentType(), toClass - .isArray() ? toClass.getComponentType() : toClass, subtypeVarAssigns); - } - - // since wildcard types are not assignable to classes, should this just - // return null? - if (type instanceof WildcardType) { - for (final Type bound : getImplicitUpperBounds((WildcardType) type)) { - // find the first bound that is assignable to the target class - if (isAssignable(bound, toClass)) { - return getTypeArguments(bound, toClass, subtypeVarAssigns); - } - } - - return null; - } - - if (type instanceof TypeVariable) { - for (final Type bound : getImplicitBounds((TypeVariable) type)) { - // find the first bound that is assignable to the target class - if (isAssignable(bound, toClass)) { - return getTypeArguments(bound, toClass, subtypeVarAssigns); - } - } - - return null; - } - throw new IllegalStateException("found an unhandled type: " + type); - } - - /** - *

Return a map of the type arguments of a parameterized type in the context of {@code toClass}.

- * - * @param parameterizedType the parameterized type - * @param toClass the class - * @param subtypeVarAssigns a map with type variables - * @return the {@code Map} with type arguments - */ - private static Map, Type> getTypeArguments( - final ParameterizedType parameterizedType, final Class toClass, - final Map, Type> subtypeVarAssigns) { - final Class cls = getRawType(parameterizedType); - - // make sure they're assignable - if (!isAssignable(cls, toClass)) { - return null; - } - - final Type ownerType = parameterizedType.getOwnerType(); - Map, Type> typeVarAssigns; - - if (ownerType instanceof ParameterizedType) { - // get the owner type arguments first - final ParameterizedType parameterizedOwnerType = (ParameterizedType) ownerType; - typeVarAssigns = getTypeArguments(parameterizedOwnerType, - getRawType(parameterizedOwnerType), subtypeVarAssigns); - } else { - // no owner, prep the type variable assignments map - typeVarAssigns = subtypeVarAssigns == null ? new HashMap, Type>() - : new HashMap<>(subtypeVarAssigns); - } - - // get the subject parameterized type's arguments - final Type[] typeArgs = parameterizedType.getActualTypeArguments(); - // and get the corresponding type variables from the raw class - final TypeVariable[] typeParams = cls.getTypeParameters(); - - // map the arguments to their respective type variables - for (int i = 0; i < typeParams.length; i++) { - final Type typeArg = typeArgs[i]; - typeVarAssigns.put(typeParams[i], typeVarAssigns.containsKey(typeArg) ? typeVarAssigns - .get(typeArg) : typeArg); - } - - if (toClass.equals(cls)) { - // target class has been reached. Done. - return typeVarAssigns; - } - - // walk the inheritance hierarchy until the target class is reached - return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns); - } - - /** - *

Return a map of the type arguments of a class in the context of {@code toClass}.

- * - * @param cls the class in question - * @param toClass the context class - * @param subtypeVarAssigns a map with type variables - * @return the {@code Map} with type arguments - */ - private static Map, Type> getTypeArguments(Class cls, final Class toClass, - final Map, Type> subtypeVarAssigns) { - // make sure they're assignable - if (!isAssignable(cls, toClass)) { - return null; - } - - // can't work with primitives - if (cls.isPrimitive()) { - // both classes are primitives? - if (toClass.isPrimitive()) { - // dealing with widening here. No type arguments to be - // harvested with these two types. - return new HashMap<>(); - } - - // work with wrapper the wrapper class instead of the primitive - cls = ClassUtils.primitiveToWrapper(cls); - } - - // create a copy of the incoming map, or an empty one if it's null - final HashMap, Type> typeVarAssigns = subtypeVarAssigns == null ? new HashMap, Type>() - : new HashMap<>(subtypeVarAssigns); - - // has target class been reached? - if (toClass.equals(cls)) { - return typeVarAssigns; - } - - // walk the inheritance hierarchy until the target class is reached - return getTypeArguments(getClosestParentType(cls, toClass), toClass, typeVarAssigns); - } - - /** - *

Tries to determine the type arguments of a class/interface based on a - * super parameterized type's type arguments. This method is the inverse of - * {@link #getTypeArguments(Type, Class)} which gets a class/interface's - * type arguments based on a subtype. It is far more limited in determining - * the type arguments for the subject class's type variables in that it can - * only determine those parameters that map from the subject {@link Class} - * object to the supertype.

Example: {@link java.util.TreeSet - * TreeSet} sets its parameter as the parameter for - * {@link java.util.NavigableSet NavigableSet}, which in turn sets the - * parameter of {@link java.util.SortedSet}, which in turn sets the - * parameter of {@link Set}, which in turn sets the parameter of - * {@link java.util.Collection}, which in turn sets the parameter of - * {@link java.lang.Iterable}. Since {@code TreeSet}'s parameter maps - * (indirectly) to {@code Iterable}'s parameter, it will be able to - * determine that based on the super type {@code Iterable>>}, the parameter of - * {@code TreeSet} is {@code ? extends Map>}.

- * - * @param cls the class whose type parameters are to be determined, not {@code null} - * @param superType the super type from which {@code cls}'s type - * arguments are to be determined, not {@code null} - * @return a {@code Map} of the type assignments that could be determined - * for the type variables in each type in the inheritance hierarchy from - * {@code type} to {@code toClass} inclusive. - */ - public static Map, Type> determineTypeArguments(final Class cls, - final ParameterizedType superType) { - - final Class superClass = getRawType(superType); - - // compatibility check - if (!isAssignable(cls, superClass)) { - return null; - } - - if (cls.equals(superClass)) { - return getTypeArguments(superType, superClass, null); - } - - // get the next class in the inheritance hierarchy - final Type midType = getClosestParentType(cls, superClass); - - // can only be a class or a parameterized type - if (midType instanceof Class) { - return determineTypeArguments((Class) midType, superType); - } - - final ParameterizedType midParameterizedType = (ParameterizedType) midType; - final Class midClass = getRawType(midParameterizedType); - // get the type variables of the mid class that map to the type - // arguments of the super class - final Map, Type> typeVarAssigns = determineTypeArguments(midClass, superType); - // map the arguments of the mid type to the class type variables - mapTypeVariablesToArguments(cls, midParameterizedType, typeVarAssigns); - - return typeVarAssigns; - } - - /** - *

Performs a mapping of type variables.

- * - * @param the generic type of the class in question - * @param cls the class in question - * @param parameterizedType the parameterized type - * @param typeVarAssigns the map to be filled - */ - private static void mapTypeVariablesToArguments(final Class cls, - final ParameterizedType parameterizedType, final Map, Type> typeVarAssigns) { - // capture the type variables from the owner type that have assignments - final Type ownerType = parameterizedType.getOwnerType(); - - if (ownerType instanceof ParameterizedType) { - // recursion to make sure the owner's owner type gets processed - mapTypeVariablesToArguments(cls, (ParameterizedType) ownerType, typeVarAssigns); - } - - // parameterizedType is a generic interface/class (or it's in the owner - // hierarchy of said interface/class) implemented/extended by the class - // cls. Find out which type variables of cls are type arguments of - // parameterizedType: - final Type[] typeArgs = parameterizedType.getActualTypeArguments(); - - // of the cls's type variables that are arguments of parameterizedType, - // find out which ones can be determined from the super type's arguments - final TypeVariable[] typeVars = getRawType(parameterizedType).getTypeParameters(); - - // use List view of type parameters of cls so the contains() method can be used: - final List>> typeVarList = Arrays.asList(cls - .getTypeParameters()); - - for (int i = 0; i < typeArgs.length; i++) { - final TypeVariable typeVar = typeVars[i]; - final Type typeArg = typeArgs[i]; - - // argument of parameterizedType is a type variable of cls - if (typeVarList.contains(typeArg) - // type variable of parameterizedType has an assignment in - // the super type. - && typeVarAssigns.containsKey(typeVar)) { - // map the assignment to the cls's type variable - typeVarAssigns.put((TypeVariable) typeArg, typeVarAssigns.get(typeVar)); - } - } - } - - /** - *

Get the closest parent type to the - * super class specified by {@code superClass}.

- * - * @param cls the class in question - * @param superClass the super class - * @return the closes parent type - */ - private static Type getClosestParentType(final Class cls, final Class superClass) { - // only look at the interfaces if the super class is also an interface - if (superClass.isInterface()) { - // get the generic interfaces of the subject class - final Type[] interfaceTypes = cls.getGenericInterfaces(); - // will hold the best generic interface match found - Type genericInterface = null; - - // find the interface closest to the super class - for (final Type midType : interfaceTypes) { - Class midClass = null; - - if (midType instanceof ParameterizedType) { - midClass = getRawType((ParameterizedType) midType); - } else if (midType instanceof Class) { - midClass = (Class) midType; - } else { - throw new IllegalStateException("Unexpected generic" - + " interface type found: " + midType); - } - - // check if this interface is further up the inheritance chain - // than the previously found match - if (isAssignable(midClass, superClass) - && isAssignable(genericInterface, (Type) midClass)) { - genericInterface = midType; - } - } - - // found a match? - if (genericInterface != null) { - return genericInterface; - } - } - - // none of the interfaces were descendants of the target class, so the - // super class has to be one, instead - return cls.getGenericSuperclass(); - } - - /** - *

Checks if the given value can be assigned to the target type - * following the Java generics rules.

- * - * @param value the value to be checked - * @param type the target type - * @return {@code true} if {@code value} is an instance of {@code type}. - */ - public static boolean isInstance(final Object value, final Type type) { - if (type == null) { - return false; - } - - return value == null ? !(type instanceof Class) || !((Class) type).isPrimitive() - : isAssignable(value.getClass(), type, null); - } - - /** - *

This method strips out the redundant upper bound types in type - * variable types and wildcard types (or it would with wildcard types if - * multiple upper bounds were allowed).

Example, with the variable - * type declaration: - * - *

<K extends java.util.Collection<String> &
-	 * java.util.List<String>>
- * - *

- * since {@code List} is a subinterface of {@code Collection}, - * this method will return the bounds as if the declaration had been: - *

- * - *
<K extends java.util.List<String>>
- * - * @param bounds an array of types representing the upper bounds of either - * {@link WildcardType} or {@link TypeVariable}, not {@code null}. - * @return an array containing the values from {@code bounds} minus the - * redundant types. - */ - public static Type[] normalizeUpperBounds(final Type[] bounds) { - // don't bother if there's only one (or none) type - if (bounds.length < 2) { - return bounds; - } - - final Set types = new HashSet<>(bounds.length); - - for (final Type type1 : bounds) { - boolean subtypeFound = false; - - for (final Type type2 : bounds) { - if (type1 != type2 && isAssignable(type2, type1, null)) { - subtypeFound = true; - break; - } - } - - if (!subtypeFound) { - types.add(type1); - } - } - - return types.toArray(new Type[types.size()]); - } - - /** - *

Returns an array containing the sole type of {@link Object} if - * {@link TypeVariable#getBounds()} returns an empty array. Otherwise, it - * returns the result of {@link TypeVariable#getBounds()} passed into - * {@link #normalizeUpperBounds}.

- * - * @param typeVariable the subject type variable, not {@code null} - * @return a non-empty array containing the bounds of the type variable. - */ - public static Type[] getImplicitBounds(final TypeVariable typeVariable) { - final Type[] bounds = typeVariable.getBounds(); - - return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds); - } - - /** - *

Returns an array containing the sole value of {@link Object} if - * {@link WildcardType#getUpperBounds()} returns an empty array. Otherwise, - * it returns the result of {@link WildcardType#getUpperBounds()} - * passed into {@link #normalizeUpperBounds}.

- * - * @param wildcardType the subject wildcard type, not {@code null} - * @return a non-empty array containing the upper bounds of the wildcard - * type. - */ - public static Type[] getImplicitUpperBounds(final WildcardType wildcardType) { - final Type[] bounds = wildcardType.getUpperBounds(); - - return bounds.length == 0 ? new Type[] { Object.class } : normalizeUpperBounds(bounds); - } - - /** - *

Returns an array containing a single value of {@code null} if - * {@link WildcardType#getLowerBounds()} returns an empty array. Otherwise, - * it returns the result of {@link WildcardType#getLowerBounds()}.

- * - * @param wildcardType the subject wildcard type, not {@code null} - * @return a non-empty array containing the lower bounds of the wildcard - * type. - */ - public static Type[] getImplicitLowerBounds(final WildcardType wildcardType) { - final Type[] bounds = wildcardType.getLowerBounds(); - - return bounds.length == 0 ? new Type[] { null } : bounds; - } - - /** - *

Determines whether or not specified types satisfy the bounds of their - * mapped type variables. When a type parameter extends another (such as - * {@code }), uses another as a type parameter (such as - * {@code >}), or otherwise depends on - * another type variable to be specified, the dependencies must be included - * in {@code typeVarAssigns}.

- * - * @param typeVarAssigns specifies the potential types to be assigned to the - * type variables, not {@code null}. - * @return whether or not the types can be assigned to their respective type - * variables. - */ - public static boolean typesSatisfyVariables(final Map, Type> typeVarAssigns) { - // all types must be assignable to all the bounds of the their mapped - // type variable. - for (final Map.Entry, Type> entry : typeVarAssigns.entrySet()) { - final TypeVariable typeVar = entry.getKey(); - final Type type = entry.getValue(); - - for (final Type bound : getImplicitBounds(typeVar)) { - if (!isAssignable(type, substituteTypeVariables(bound, typeVarAssigns), - typeVarAssigns)) { - return false; - } - } - } - return true; - } - - /** - *

Transforms the passed in type to a {@link Class} object. Type-checking method of convenience.

- * - * @param parameterizedType the type to be converted - * @return the corresponding {@code Class} object - * @throws IllegalStateException if the conversion fails - */ - private static Class getRawType(final ParameterizedType parameterizedType) { - final Type rawType = parameterizedType.getRawType(); - - // check if raw type is a Class object - // not currently necessary, but since the return type is Type instead of - // Class, there's enough reason to believe that future versions of Java - // may return other Type implementations. And type-safety checking is - // rarely a bad idea. - if (!(rawType instanceof Class)) { - throw new IllegalStateException("Wait... What!? Type of rawType: " + rawType); - } - - return (Class) rawType; - } - - /** - *

Get the raw type of a Java type, given its context. Primarily for use - * with {@link TypeVariable}s and {@link GenericArrayType}s, or when you do - * not know the runtime type of {@code type}: if you know you have a - * {@link Class} instance, it is already raw; if you know you have a - * {@link ParameterizedType}, its raw type is only a method call away.

- * - * @param type to resolve - * @param assigningType type to be resolved against - * @return the resolved {@link Class} object or {@code null} if - * the type could not be resolved - */ - public static Class getRawType(final Type type, final Type assigningType) { - if (type instanceof Class) { - // it is raw, no problem - return (Class) type; - } - - if (type instanceof ParameterizedType) { - // simple enough to get the raw type of a ParameterizedType - return getRawType((ParameterizedType) type); - } - - if (type instanceof TypeVariable) { - if (assigningType == null) { - return null; - } - - // get the entity declaring this type variable - final Object genericDeclaration = ((TypeVariable) type).getGenericDeclaration(); - - // can't get the raw type of a method- or constructor-declared type - // variable - if (!(genericDeclaration instanceof Class)) { - return null; - } - - // get the type arguments for the declaring class/interface based - // on the enclosing type - final Map, Type> typeVarAssigns = getTypeArguments(assigningType, - (Class) genericDeclaration); - - // enclosingType has to be a subclass (or subinterface) of the - // declaring type - if (typeVarAssigns == null) { - return null; - } - - // get the argument assigned to this type variable - final Type typeArgument = typeVarAssigns.get(type); - - if (typeArgument == null) { - return null; - } - - // get the argument for this type variable - return getRawType(typeArgument, assigningType); - } - - if (type instanceof GenericArrayType) { - // get raw component type - final Class rawComponentType = getRawType(((GenericArrayType) type) - .getGenericComponentType(), assigningType); - - // create array type from raw component type and return its class - return Array.newInstance(rawComponentType, 0).getClass(); - } - - // (hand-waving) this is not the method you're looking for - if (type instanceof WildcardType) { - return null; - } - - throw new IllegalArgumentException("unknown type: " + type); - } - - /** - * Learn whether the specified type denotes an array type. - * @param type the type to be checked - * @return {@code true} if {@code type} is an array class or a {@link GenericArrayType}. - */ - public static boolean isArrayType(final Type type) { - return type instanceof GenericArrayType || type instanceof Class && ((Class) type).isArray(); - } - - /** - * Get the array component type of {@code type}. - * @param type the type to be checked - * @return component type or null if type is not an array type - */ - public static Type getArrayComponentType(final Type type) { - if (type instanceof Class) { - final Class clazz = (Class) type; - return clazz.isArray() ? clazz.getComponentType() : null; - } - if (type instanceof GenericArrayType) { - return ((GenericArrayType) type).getGenericComponentType(); - } - return null; - } - - - - /** - * Check equality of types. - * - * @param t1 the first type - * @param t2 the second type - * @return boolean - * @since 3.2 - */ - public static boolean equals(final Type t1, final Type t2) { - if (Objects.equals(t1, t2)) { - return true; - } - if (t1 instanceof ParameterizedType) { - return equals((ParameterizedType) t1, t2); - } - if (t1 instanceof GenericArrayType) { - return equals((GenericArrayType) t1, t2); - } - if (t1 instanceof WildcardType) { - return equals((WildcardType) t1, t2); - } - return false; - } - - /** - * Learn whether {@code t} equals {@code p}. - * @param p LHS - * @param t RHS - * @return boolean - * @since 3.2 - */ - private static boolean equals(final ParameterizedType p, final Type t) { - if (t instanceof ParameterizedType) { - final ParameterizedType other = (ParameterizedType) t; - if (equals(p.getRawType(), other.getRawType()) && equals(p.getOwnerType(), other.getOwnerType())) { - return equals(p.getActualTypeArguments(), other.getActualTypeArguments()); - } - } - return false; - } - - /** - * Learn whether {@code t} equals {@code a}. - * @param a LHS - * @param t RHS - * @return boolean - * @since 3.2 - */ - private static boolean equals(final GenericArrayType a, final Type t) { - return t instanceof GenericArrayType - && equals(a.getGenericComponentType(), ((GenericArrayType) t).getGenericComponentType()); - } - - /** - * Learn whether {@code t} equals {@code w}. - * @param w LHS - * @param t RHS - * @return boolean - * @since 3.2 - */ - private static boolean equals(final WildcardType w, final Type t) { - if (t instanceof WildcardType) { - final WildcardType other = (WildcardType) t; - return equals(getImplicitLowerBounds(w), getImplicitLowerBounds(other)) - && equals(getImplicitUpperBounds(w), getImplicitUpperBounds(other)); - } - return false; - } - - /** - * Learn whether {@code t1} equals {@code t2}. - * @param t1 LHS - * @param t2 RHS - * @return boolean - * @since 3.2 - */ - private static boolean equals(final Type[] t1, final Type[] t2) { - if (t1.length == t2.length) { - for (int i = 0; i < t1.length; i++) { - if (!equals(t1[i], t2[i])) { - return false; - } - } - return true; - } - return false; - } - - - - -} \ No newline at end of file diff --git a/crnk-core/src/main/java/io/crnk/core/exception/ResourceNotFoundInitializationException.java b/crnk-core/src/main/java/io/crnk/core/exception/ResourceNotFoundInitializationException.java deleted file mode 100644 index f8ab94d27..000000000 --- a/crnk-core/src/main/java/io/crnk/core/exception/ResourceNotFoundInitializationException.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.crnk.core.exception; - -public class ResourceNotFoundInitializationException extends CrnkInitializationException { - - public ResourceNotFoundInitializationException(String className) { - super("Resource of class not found: " + className); - } -} diff --git a/crnk-core/src/main/java/io/crnk/core/module/ModuleRegistry.java b/crnk-core/src/main/java/io/crnk/core/module/ModuleRegistry.java index 5a0c83c8c..01979fd99 100644 --- a/crnk-core/src/main/java/io/crnk/core/module/ModuleRegistry.java +++ b/crnk-core/src/main/java/io/crnk/core/module/ModuleRegistry.java @@ -117,9 +117,7 @@ public List getJacksonModules() { * has not yet been called. */ protected void checkNotInitialized() { - if (initialized) { - throw new IllegalStateException("already initialized, cannot be changed anymore"); - } + PreconditionUtil.verify(!initialized, "already initialized, cannot be changed anymore"); } /** @@ -586,10 +584,8 @@ public void addResourceLookup(ResourceLookup resourceLookup) { @Override public void addJacksonModule(com.fasterxml.jackson.databind.Module module) { + checkNotInitialized(); aggregatedModule.addJacksonModule(module); - if (objectMapper != null) { - objectMapper.registerModule(module); - } } @Override diff --git a/crnk-core/src/main/java/io/crnk/core/queryspec/DefaultQuerySpecDeserializer.java b/crnk-core/src/main/java/io/crnk/core/queryspec/DefaultQuerySpecDeserializer.java index 2ee16e81c..3a95160cf 100644 --- a/crnk-core/src/main/java/io/crnk/core/queryspec/DefaultQuerySpecDeserializer.java +++ b/crnk-core/src/main/java/io/crnk/core/queryspec/DefaultQuerySpecDeserializer.java @@ -1,13 +1,5 @@ package io.crnk.core.queryspec; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - import io.crnk.core.engine.information.resource.ResourceField; import io.crnk.core.engine.information.resource.ResourceInformation; import io.crnk.core.engine.internal.utils.PropertyException; @@ -22,6 +14,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.*; +import java.util.Map.Entry; + /** * Maps url parameters to QuerySpec. */ @@ -156,10 +151,13 @@ public QuerySpec deserialize(ResourceInformation resourceInformation, Map parameters = parseParameters(parameterMap, resourceInformation); for (Parameter parameter : parameters) { - QuerySpec querySpec = rootQuerySpec.getQuerySpec(parameter.resourceInformation); - if (querySpec == null) { - querySpec = rootQuerySpec.getOrCreateQuerySpec(parameter.resourceInformation); - setupDefaults(querySpec); + QuerySpec querySpec = rootQuerySpec; + if (parameter.resourceInformation != null) { + querySpec = rootQuerySpec.getQuerySpec(parameter.resourceInformation); + if (querySpec == null) { + querySpec = rootQuerySpec.getOrCreateQuerySpec(parameter.resourceInformation); + setupDefaults(querySpec); + } } switch (parameter.paramType) { case sort: @@ -293,7 +291,7 @@ private void deserializeSort(QuerySpec querySpec, Parameter parameter) { } protected void deserializeUnknown(QuerySpec querySpec, Parameter parameter) { - throw new IllegalStateException(parameter.paramType.toString()); + throw new ParametersDeserializationException(parameter.paramType.toString()); } private List parseParameters(Map> params, ResourceInformation rootResourceInformation) { @@ -308,13 +306,20 @@ private List parseParameters(Map> params, Resourc private Parameter parseParameter(String parameterName, Set values, ResourceInformation rootResourceInformation) { int typeSep = parameterName.indexOf('['); String strParamType = typeSep != -1 ? parameterName.substring(0, typeSep) : parameterName; - RestrictedQueryParamsMembers paramType = RestrictedQueryParamsMembers.valueOf(strParamType.toLowerCase()); + + RestrictedQueryParamsMembers paramType; + try { + paramType = RestrictedQueryParamsMembers.valueOf(strParamType.toLowerCase()); + } catch (IllegalArgumentException e) { + paramType = RestrictedQueryParamsMembers.unknown; + } List elements = parseParameterNameArguments(parameterName, typeSep); Parameter param = new Parameter(); - param.fullKey = parameterName; + param.name = parameterName; param.paramType = paramType; + param.strParamType = strParamType; param.values = values; if (paramType == RestrictedQueryParamsMembers.filter && elements.size() >= 1) { @@ -325,6 +330,8 @@ private Parameter parseParameter(String parameterName, Set values, Resou } else if (paramType == RestrictedQueryParamsMembers.page && elements.size() == 2) { param.resourceInformation = getResourceInformation(elements.get(0), parameterName); param.pageParameter = elements.get(1); + } else if (paramType == RestrictedQueryParamsMembers.unknown) { + param.resourceInformation = null; } else if (elements.size() == 1) { param.resourceInformation = getResourceInformation(elements.get(0), parameterName); } else { @@ -351,14 +358,14 @@ private void parseFilterParameterName(Parameter param, List elements, Re parseFilterOperator(param, elements); if (elements.isEmpty()) { - throw new ParametersDeserializationException("failed to parse " + param.fullKey + ", expected " + throw new ParametersDeserializationException("failed to parse " + param.name + ", expected " + "([resourceType])[attr1.attr2]([operator])"); } if (enforceDotPathSeparator && elements.size() > 2) { - throw new ParametersDeserializationException("failed to parse " + param.fullKey + ", expected ([resourceType])[attr1.attr2]([operator])"); + throw new ParametersDeserializationException("failed to parse " + param.name + ", expected ([resourceType])[attr1.attr2]([operator])"); } if (enforceDotPathSeparator && elements.size() == 2) { - param.resourceInformation = getResourceInformation(elements.get(0), param.fullKey); + param.resourceInformation = getResourceInformation(elements.get(0), param.name); param.attributePath = Arrays.asList(elements.get(1).split("\\.")); } else if (enforceDotPathSeparator && elements.size() == 1) { param.resourceInformation = rootResourceInformation; @@ -373,7 +380,7 @@ private void legacyParseFilterParameterName(Parameter param, List elemen // can cause problems if names clash, so use // enforceDotPathSeparator! if (isResourceType(elements.get(0))) { - param.resourceInformation = getResourceInformation(elements.get(0), param.fullKey); + param.resourceInformation = getResourceInformation(elements.get(0), param.name); elements.remove(0); } else { param.resourceInformation = rootResourceInformation; @@ -430,21 +437,23 @@ public void setIgnoreParseExceptions(boolean ignoreParseExceptions) { public class Parameter { - String pageParameter; + private String pageParameter; - String fullKey; + private String name; - RestrictedQueryParamsMembers paramType; + private RestrictedQueryParamsMembers paramType; - ResourceInformation resourceInformation; + private String strParamType; - FilterOperator operator; + private ResourceInformation resourceInformation; - List attributePath; + private FilterOperator operator; - Set values; + private List attributePath; - public Long getLongValue() { + private Set values; + + private Long getLongValue() { if (values.size() != 1) { throw new ParametersDeserializationException("expected a Long for " + toString()); } @@ -457,7 +466,35 @@ public Long getLongValue() { @Override public String toString() { - return fullKey + "=" + values; + return name + "=" + values; + } + + public String getName() { + return name; + } + + public RestrictedQueryParamsMembers getParamType() { + return paramType; + } + + public String getStrParamType() { + return strParamType; + } + + public ResourceInformation getResourceInformation() { + return resourceInformation; + } + + public FilterOperator getOperator() { + return operator; + } + + public List getAttributePath() { + return attributePath; + } + + public Set getValues() { + return values; } } } \ No newline at end of file diff --git a/crnk-core/src/main/java/io/crnk/core/queryspec/QuerySpec.java b/crnk-core/src/main/java/io/crnk/core/queryspec/QuerySpec.java index dde810375..c120d0ea4 100644 --- a/crnk-core/src/main/java/io/crnk/core/queryspec/QuerySpec.java +++ b/crnk-core/src/main/java/io/crnk/core/queryspec/QuerySpec.java @@ -274,14 +274,15 @@ public QuerySpec getOrCreateQuerySpec(ResourceInformation resourceInformation) { @Override public String toString() { return "QuerySpec{" + - "resourceClass=" + resourceClass + - ", limit=" + limit + - ", offset=" + offset + - ", filters=" + filters + - ", sort=" + sort + - ", includedFields=" + includedFields + - ", includedRelations=" + includedRelations + - ", relatedSpecs=" + relatedSpecs + + (resourceClass != null ? "resourceClass=" + resourceClass.getName() : "") + + (resourceType != null ? "resourceType=" + resourceType : "") + + (limit != null ? ", limit=" + limit : "") + + (offset > 0 ? ", offset=" + offset : "") + + (!filters.isEmpty() ? ", filters=" + filters : "") + + (!sort.isEmpty() ? ", sort=" + sort : "") + + (!includedFields.isEmpty() ? ", includedFields=" + includedFields : "") + + (!includedRelations.isEmpty() ? ", includedRelations=" + includedRelations : "") + + (!relatedSpecs.isEmpty() ? ", relatedSpecs=" + relatedSpecs : "") + '}'; } } diff --git a/crnk-core/src/main/java/io/crnk/core/resource/RestrictedQueryParamsMembers.java b/crnk-core/src/main/java/io/crnk/core/resource/RestrictedQueryParamsMembers.java index a1da588ce..233273d41 100644 --- a/crnk-core/src/main/java/io/crnk/core/resource/RestrictedQueryParamsMembers.java +++ b/crnk-core/src/main/java/io/crnk/core/resource/RestrictedQueryParamsMembers.java @@ -12,6 +12,7 @@ public enum RestrictedQueryParamsMembers { /** * Field to group by the collection */ + @Deprecated group, // NOSONAR ok in this case /** * Pagination properties @@ -21,7 +22,7 @@ public enum RestrictedQueryParamsMembers { * List of specified fields to include in models */ fields,// NOSONAR ok in this case - /** + unknown, /** * Additional resources that should be attached to response */ include// NOSONAR ok in this case diff --git a/crnk-core/src/test/java/io/crnk/core/boot/CrnkBootTest.java b/crnk-core/src/test/java/io/crnk/core/boot/CrnkBootTest.java index 3c1169cce..47eb773ff 100644 --- a/crnk-core/src/test/java/io/crnk/core/boot/CrnkBootTest.java +++ b/crnk-core/src/test/java/io/crnk/core/boot/CrnkBootTest.java @@ -1,11 +1,9 @@ package io.crnk.core.boot; -import java.util.Arrays; -import java.util.List; -import java.util.Properties; - import com.fasterxml.jackson.databind.ObjectMapper; import io.crnk.core.engine.dispatcher.RequestDispatcher; +import io.crnk.core.engine.error.ErrorResponse; +import io.crnk.core.engine.error.ExceptionMapper; import io.crnk.core.engine.error.JsonApiExceptionMapper; import io.crnk.core.engine.filter.DocumentFilter; import io.crnk.core.engine.information.resource.ResourceFieldNameTransformer; @@ -38,6 +36,10 @@ import org.junit.Test; import org.mockito.Mockito; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + public class CrnkBootTest { private ServiceDiscoveryFactory serviceDiscoveryFactory; @@ -106,6 +108,15 @@ public void setServiceDiscoveryFactory() { Assert.assertNotNull(boot.getServiceDiscovery()); } + @Test + public void getPropertiesProvider() { + CrnkBoot boot = new CrnkBoot(); + boot.setServiceDiscoveryFactory(serviceDiscoveryFactory); + boot.setDefaultServiceUrlProvider(Mockito.mock(ServiceUrlProvider.class)); + boot.boot(); + Assert.assertNotNull(boot.getPropertiesProvider()); + } + @Test public void setInvalidRepository() { SimpleModule module = new SimpleModule("test"); @@ -170,7 +181,7 @@ public void testServiceDiscovery() { Module module = Mockito.mock(Module.class); DocumentFilter filter = Mockito.mock(DocumentFilter.class); - JsonApiExceptionMapper exceptionMapper = Mockito.mock(JsonApiExceptionMapper.class); + JsonApiExceptionMapper exceptionMapper = new TestExceptionMapper(); Mockito.when(serviceDiscovery.getInstancesByType(Mockito.eq(DocumentFilter.class))).thenReturn(Arrays.asList(filter)); Mockito.when(serviceDiscovery.getInstancesByType(Mockito.eq(Module.class))).thenReturn(Arrays.asList(module)); Mockito.when(serviceDiscovery.getInstancesByType(Mockito.eq(JsonApiExceptionMapper.class))) @@ -183,6 +194,24 @@ public void testServiceDiscovery() { Assert.assertTrue(moduleRegistry.getExceptionMapperLookup().getExceptionMappers().contains(exceptionMapper)); } + class TestExceptionMapper implements ExceptionMapper { + + @Override + public ErrorResponse toErrorResponse(IllegalStateException exception) { + return null; + } + + @Override + public IllegalStateException fromErrorResponse(ErrorResponse errorResponse) { + return null; + } + + @Override + public boolean accepts(ErrorResponse errorResponse) { + return false; + } + } + @Test public void setDefaultServiceUrlProvider() { CrnkBoot boot = new CrnkBoot(); diff --git a/crnk-core/src/test/java/io/crnk/core/engine/internal/dispatcher/controller/resource/ResourcePostTest.java b/crnk-core/src/test/java/io/crnk/core/engine/internal/dispatcher/controller/resource/ResourcePostTest.java index d1b3ffd4f..e49d618fe 100644 --- a/crnk-core/src/test/java/io/crnk/core/engine/internal/dispatcher/controller/resource/ResourcePostTest.java +++ b/crnk-core/src/test/java/io/crnk/core/engine/internal/dispatcher/controller/resource/ResourcePostTest.java @@ -14,10 +14,7 @@ import io.crnk.core.engine.internal.dispatcher.path.ResourcePath; import io.crnk.core.engine.properties.PropertiesProvider; import io.crnk.core.engine.properties.ResourceFieldImmutableWriteBehavior; -import io.crnk.core.exception.BadRequestException; -import io.crnk.core.exception.RepositoryNotFoundException; -import io.crnk.core.exception.RequestBodyNotFoundException; -import io.crnk.core.exception.ResourceException; +import io.crnk.core.exception.*; import io.crnk.core.mock.models.Pojo; import io.crnk.core.mock.models.Task; import io.crnk.core.mock.repository.PojoRepository; @@ -31,6 +28,7 @@ import org.junit.Ignore; import org.junit.Test; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -83,6 +81,23 @@ public void onInconsistentResourceTypesShouldThrowException() throws Exception { sut.handle(projectPath, new QueryParamsAdapter(REQUEST_PARAMS), null, newProjectBody); } + @Test + public void onMultipleDataThrowException() throws Exception { + // GIVEN + Document newProjectBody = new Document(); + Resource data = createProject(); + newProjectBody.setData(Nullable.of((Object) new ArrayList<>())); + + JsonPath projectPath = pathBuilder.build("/tasks"); + ResourcePost sut = new ResourcePost(resourceRegistry, PROPERTIES_PROVIDER, typeParser, objectMapper, documentMapper); + + // THEN + expectedException.expect(RequestBodyException.class); + + // WHEN + sut.handle(projectPath, new QueryParamsAdapter(REQUEST_PARAMS), null, newProjectBody); + } + @Test public void onNonExistentResourceShouldThrowException() throws Exception { // GIVEN diff --git a/crnk-core/src/test/java/io/crnk/core/engine/internal/exception/ExceptionMapperTypeTest.java b/crnk-core/src/test/java/io/crnk/core/engine/internal/exception/ExceptionMapperTypeTest.java index 27eada872..6b2070cc7 100644 --- a/crnk-core/src/test/java/io/crnk/core/engine/internal/exception/ExceptionMapperTypeTest.java +++ b/crnk-core/src/test/java/io/crnk/core/engine/internal/exception/ExceptionMapperTypeTest.java @@ -1,7 +1,10 @@ package io.crnk.core.engine.internal.exception; +import io.crnk.core.engine.error.JsonApiExceptionMapper; import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; public class ExceptionMapperTypeTest { @@ -9,4 +12,12 @@ public class ExceptionMapperTypeTest { public void shouldFulfillHashCodeEqualsContract() throws Exception { EqualsVerifier.forClass(ExceptionMapperType.class).verify(); } + + @Test + public void checkToString() throws Exception { + JsonApiExceptionMapper mapper = Mockito.mock(JsonApiExceptionMapper.class); + Mockito.when(mapper.toString()).thenReturn("customMapper"); + ExceptionMapperType type = new ExceptionMapperType(IllegalStateException.class, mapper); + Assert.assertEquals("ExceptionMapperType[exceptionClass=java.lang.IllegalStateException, exceptionMapper=customMapper]", type.toString()); + } } \ No newline at end of file diff --git a/crnk-core/src/test/java/io/crnk/core/engine/internal/information/DefaultInformationBuilderTest.java b/crnk-core/src/test/java/io/crnk/core/engine/internal/information/DefaultInformationBuilderTest.java new file mode 100644 index 000000000..85862eff6 --- /dev/null +++ b/crnk-core/src/test/java/io/crnk/core/engine/internal/information/DefaultInformationBuilderTest.java @@ -0,0 +1,74 @@ +package io.crnk.core.engine.internal.information; + +import io.crnk.core.engine.information.InformationBuilder; +import io.crnk.core.engine.information.resource.*; +import io.crnk.core.engine.parser.TypeParser; +import io.crnk.core.mock.models.Project; +import io.crnk.core.mock.models.Task; +import io.crnk.core.resource.annotations.LookupIncludeBehavior; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +public class DefaultInformationBuilderTest { + + private DefaultInformationBuilder builder; + + @Before + public void setup() { + TypeParser parser = new TypeParser(); + builder = new DefaultInformationBuilder(parser); + + } + + @Test + public void resource() { + InformationBuilder.Resource resource = builder.createResource(Task.class, "tasks"); + resource.superResourceType("superTask"); + resource.resourceType("changedTasks"); + resource.resourceClass(Project.class); + + InformationBuilder.Field idField = resource.addField("id", ResourceFieldType.ID, String.class); + idField.lazy(false); + idField.setAccess(new ResourceFieldAccess(true, true, false, false)); + + ResourceFieldAccessor accessor = Mockito.mock(ResourceFieldAccessor.class); + InformationBuilder.Field projectField = resource.addField("project", ResourceFieldType.RELATIONSHIP, Project.class); + projectField.lazy(true); + projectField.setAccess(new ResourceFieldAccess(false, true, false, false)); + projectField.setOppositeName("tasks"); + projectField.lookupIncludeBehavior(LookupIncludeBehavior.AUTOMATICALLY_ALWAYS); + projectField.includeByDefault(true); + projectField.setAccessor(accessor); + + ResourceInformation info = resource.build(); + Assert.assertEquals("changedTasks", info.getResourceType()); + Assert.assertEquals(Project.class, info.getResourceClass()); + Assert.assertEquals("superTask", info.getSuperResourceType()); + + ResourceField idInfo = info.findFieldByName("id"); + Assert.assertEquals("id", idInfo.getUnderlyingName()); + Assert.assertEquals(String.class, idInfo.getType()); + Assert.assertFalse(idInfo.getAccess().isFilterable()); + Assert.assertFalse(idInfo.getAccess().isSortable()); + Assert.assertTrue(idInfo.getAccess().isPostable()); + Assert.assertTrue(idInfo.getAccess().isPatchable()); + Assert.assertFalse(idInfo.isLazy()); + Assert.assertFalse(idInfo.isCollection()); + + ResourceField projectInfo = info.findFieldByName("project"); + Assert.assertEquals("project", projectInfo.getUnderlyingName()); + Assert.assertEquals("tasks", projectInfo.getOppositeName()); + Assert.assertEquals(LookupIncludeBehavior.AUTOMATICALLY_ALWAYS, projectInfo.getLookupIncludeAutomatically()); + Assert.assertTrue(projectInfo.getIncludeByDefault()); + Assert.assertEquals(Project.class, projectInfo.getType()); + Assert.assertSame(accessor, projectInfo.getAccessor()); + Assert.assertFalse(projectInfo.getAccess().isFilterable()); + Assert.assertFalse(projectInfo.getAccess().isSortable()); + Assert.assertFalse(projectInfo.getAccess().isPostable()); + Assert.assertTrue(projectInfo.getAccess().isPatchable()); + Assert.assertTrue(projectInfo.isLazy()); + Assert.assertFalse(projectInfo.isCollection()); + } +} diff --git a/crnk-core/src/test/java/io/crnk/core/engine/internal/resource/RawResourceFieldAccessorTest.java b/crnk-core/src/test/java/io/crnk/core/engine/internal/resource/RawResourceFieldAccessorTest.java new file mode 100644 index 000000000..396ac72bb --- /dev/null +++ b/crnk-core/src/test/java/io/crnk/core/engine/internal/resource/RawResourceFieldAccessorTest.java @@ -0,0 +1,75 @@ +package io.crnk.core.engine.internal.resource; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.crnk.core.engine.document.Resource; +import io.crnk.core.engine.document.ResourceIdentifier; +import io.crnk.core.engine.information.resource.ResourceFieldType; +import io.crnk.core.engine.internal.information.resource.RawResourceFieldAccessor; +import io.crnk.core.resource.links.LinksInformation; +import io.crnk.core.resource.meta.MetaInformation; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +public class RawResourceFieldAccessorTest { + + private Resource resource; + + @Before + public void setup() throws IOException { + String json = "{'id': 'someId', 'type': 'test', 'attributes': {'name': 'Doe'},'meta': {'name': 'someMeta'},'links': {'name': 'someLink'}, 'relationships': {'address': {'data': {'id':'zurich', 'type' : 'address'}}}}".replace('\'', '\"'); + + ObjectMapper mapper = new ObjectMapper(); + resource = mapper.readerFor(Resource.class).readValue(json); + } + + @Test + public void attribute() { + RawResourceFieldAccessor accessor = new RawResourceFieldAccessor("name", ResourceFieldType.ATTRIBUTE, String.class); + Assert.assertEquals("Doe", accessor.getValue(resource)); + } + + @Test + public void id() { + RawResourceFieldAccessor accessor = new RawResourceFieldAccessor("id", ResourceFieldType.ID, String.class); + Assert.assertEquals("someId", accessor.getValue(resource)); + } + + @Test + public void relationship() { + RawResourceFieldAccessor accessor = new RawResourceFieldAccessor("address", ResourceFieldType.RELATIONSHIP, ResourceIdentifier.class); + ResourceIdentifier value = (ResourceIdentifier) accessor.getValue(resource); + Assert.assertEquals("zurich", value.getId()); + } + + @Test + public void meta() { + RawResourceFieldAccessor accessor = new RawResourceFieldAccessor("meta", ResourceFieldType.META_INFORMATION, TestMeta.class); + TestMeta value = (TestMeta) accessor.getValue(resource); + Assert.assertEquals("someMeta", value.name); + } + + @Test + public void links() { + RawResourceFieldAccessor accessor = new RawResourceFieldAccessor("links", ResourceFieldType.LINKS_INFORMATION, TestLinks.class); + TestLinks value = (TestLinks) accessor.getValue(resource); + Assert.assertEquals("someLink", value.name); + } + + public static class TestMeta implements MetaInformation { + public String name; + } + + public static class TestLinks implements LinksInformation { + public String name; + } + + @Test(expected = UnsupportedOperationException.class) + public void writeNotSupported() { + RawResourceFieldAccessor accessor = new RawResourceFieldAccessor("name", ResourceFieldType.ATTRIBUTE, String.class); + Object value = "Doe"; + accessor.setValue(resource, value); + } +} diff --git a/crnk-core/src/test/java/io/crnk/core/engine/internal/utils/ClassUtilsTest.java b/crnk-core/src/test/java/io/crnk/core/engine/internal/utils/ClassUtilsTest.java index 6ef547de8..9ce1206dc 100644 --- a/crnk-core/src/test/java/io/crnk/core/engine/internal/utils/ClassUtilsTest.java +++ b/crnk-core/src/test/java/io/crnk/core/engine/internal/utils/ClassUtilsTest.java @@ -29,33 +29,6 @@ public void hasPrivateConstructor() { CoreClassTestUtils.assertPrivateConstructor(ClassUtils.class); } - @Test - public void onGenericClassShouldReturnFirstParameter() throws Exception { - // WHEN - Class clazz = ClassUtils - .getResourceClass(SampleGenericClass.class.getDeclaredField("strings").getGenericType(), List.class); - - // THEN - assertThat(clazz).isEqualTo(String.class); - } - - @Test - public void onGenericWildcardClassShouldThrowException() throws Exception { - // THEN - expectedException.expect(RuntimeException.class); - - // WHEN - ClassUtils.getResourceClass(SampleGenericClass.class.getDeclaredField("stringsWildcard").getGenericType(), - List.class); - } - - private static class SampleGenericClass { - - private List strings; - - private List stringsWildcard; - } - @Test public void stringMustExist() { Assert.assertTrue(ClassUtils.existsClass(String.class.getName())); diff --git a/crnk-core/src/test/java/io/crnk/core/engine/internal/utils/StringUtilsTest.java b/crnk-core/src/test/java/io/crnk/core/engine/internal/utils/StringUtilsTest.java index b54b26488..bf24a0613 100644 --- a/crnk-core/src/test/java/io/crnk/core/engine/internal/utils/StringUtilsTest.java +++ b/crnk-core/src/test/java/io/crnk/core/engine/internal/utils/StringUtilsTest.java @@ -1,5 +1,6 @@ package io.crnk.core.engine.internal.utils; +import org.junit.Assert; import org.junit.Test; import java.util.Arrays; @@ -46,5 +47,17 @@ public void onIsBlankValues() throws Exception { assertFalse(StringUtils.isBlank(" crnk ")); } + @Test + public void onJoinOfNulls() throws Exception { + Assert.assertEquals("null,null", StringUtils.join(",", Arrays.asList(null, null))); + } + + @Test + public void checkDecapitalize() throws Exception { + Assert.assertEquals("", StringUtils.decapitalize("")); + Assert.assertEquals("test", StringUtils.decapitalize("Test")); + Assert.assertEquals("someTest", StringUtils.decapitalize("SomeTest")); + } + } diff --git a/crnk-core/src/test/java/io/crnk/core/engine/registry/HierarchicalResourceRegistryPartTest.java b/crnk-core/src/test/java/io/crnk/core/engine/registry/HierarchicalResourceRegistryPartTest.java new file mode 100644 index 000000000..a89c6a352 --- /dev/null +++ b/crnk-core/src/test/java/io/crnk/core/engine/registry/HierarchicalResourceRegistryPartTest.java @@ -0,0 +1,85 @@ +package io.crnk.core.engine.registry; + + +import io.crnk.core.engine.information.resource.ResourceInformation; +import io.crnk.core.module.TestResource; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.Collection; + +public class HierarchicalResourceRegistryPartTest { + + @Test(expected = IllegalStateException.class) + public void testDuplicatePartThrowsException() { + HierarchicalResourceRegistryPart part = new HierarchicalResourceRegistryPart(); + part.putPart("", new DefaultResourceRegistryPart()); + part.putPart("", new DefaultResourceRegistryPart()); + } + + @Test + public void checkHasEntryForNonExistentEntry() { + HierarchicalResourceRegistryPart part = new HierarchicalResourceRegistryPart(); + Assert.assertFalse(part.hasEntry("doesNotExists")); + Assert.assertFalse(part.hasEntry(String.class)); + Assert.assertNull(part.getEntry("doesNotExists")); // TODO consider exception + Assert.assertNull(part.getEntry(String.class)); // TODO consider exception + } + + + @Test + public void testRootPart() { + HierarchicalResourceRegistryPart part = new HierarchicalResourceRegistryPart(); + part.putPart("", new DefaultResourceRegistryPart()); + + ResourceInformation information = Mockito.mock(ResourceInformation.class); + Mockito.when(information.getResourceType()).thenReturn("test"); + Mockito.when(information.getResourceClass()).thenReturn((Class) TestResource.class); + RegistryEntry entry = Mockito.mock(RegistryEntry.class); + Mockito.when(entry.getResourceInformation()).thenReturn(information); + RegistryEntry savedEntry = part.addEntry(entry); + Assert.assertSame(savedEntry, entry); + + Collection resources = part.getResources(); + Assert.assertEquals(1, resources.size()); + Assert.assertSame(entry, part.getEntry("test")); + Assert.assertSame(entry, part.getEntry(TestResource.class)); + Assert.assertTrue(part.hasEntry("test")); + Assert.assertTrue(part.hasEntry(TestResource.class)); + } + + @Test + public void testChildPart() { + HierarchicalResourceRegistryPart part = new HierarchicalResourceRegistryPart(); + part.putPart("child", new DefaultResourceRegistryPart()); + + ResourceInformation information = Mockito.mock(ResourceInformation.class); + Mockito.when(information.getResourceType()).thenReturn("child/test"); + Mockito.when(information.getResourceClass()).thenReturn((Class) TestResource.class); + RegistryEntry entry = Mockito.mock(RegistryEntry.class); + Mockito.when(entry.getResourceInformation()).thenReturn(information); + RegistryEntry savedEntry = part.addEntry(entry); + Assert.assertSame(savedEntry, entry); + + Collection resources = part.getResources(); + Assert.assertEquals(1, resources.size()); + Assert.assertSame(entry, part.getEntry("child/test")); + Assert.assertSame(entry, part.getEntry(TestResource.class)); + Assert.assertTrue(part.hasEntry("child/test")); + Assert.assertTrue(part.hasEntry(TestResource.class)); + } + + @Test(expected = IllegalStateException.class) + public void testMissingChildPart() { + HierarchicalResourceRegistryPart part = new HierarchicalResourceRegistryPart(); + part.putPart("otherChild", new DefaultResourceRegistryPart()); + + ResourceInformation information = Mockito.mock(ResourceInformation.class); + Mockito.when(information.getResourceType()).thenReturn("child/test"); + Mockito.when(information.getResourceClass()).thenReturn((Class) TestResource.class); + RegistryEntry entry = Mockito.mock(RegistryEntry.class); + Mockito.when(entry.getResourceInformation()).thenReturn(information); + part.addEntry(entry); + } +} diff --git a/crnk-core/src/test/java/io/crnk/core/module/ModuleRegistryTest.java b/crnk-core/src/test/java/io/crnk/core/module/ModuleRegistryTest.java index d1cc9be5b..dbbdb9e28 100644 --- a/crnk-core/src/test/java/io/crnk/core/module/ModuleRegistryTest.java +++ b/crnk-core/src/test/java/io/crnk/core/module/ModuleRegistryTest.java @@ -98,6 +98,7 @@ public void getModuleContext() { public void repositoryInformationBuilderAccept() { RepositoryInformationBuilder builder = moduleRegistry.getRepositoryInformationBuilder(); Assert.assertFalse(builder.accept("no resource")); + Assert.assertFalse(builder.accept(String.class)); Assert.assertTrue(builder.accept(TaskRepository.class)); Assert.assertTrue(builder.accept(ProjectRepository.class)); Assert.assertTrue(builder.accept(TaskToProjectRepository.class)); @@ -105,6 +106,12 @@ public void repositoryInformationBuilderAccept() { Assert.assertTrue(builder.accept(new TaskToProjectRepository())); } + @Test(expected = UnsupportedOperationException.class) + public void buildWithInvalidRepositoryClass() { + RepositoryInformationBuilderContext context = Mockito.mock(RepositoryInformationBuilderContext.class); + moduleRegistry.getRepositoryInformationBuilder().build(String.class, context); + } + @Test public void buildResourceRepositoryInformationFromClass() { RepositoryInformationBuilder builder = moduleRegistry.getRepositoryInformationBuilder(); diff --git a/crnk-core/src/test/java/io/crnk/core/queryspec/QuerySpecTest.java b/crnk-core/src/test/java/io/crnk/core/queryspec/QuerySpecTest.java index a74cfdcb9..78f4998e9 100644 --- a/crnk-core/src/test/java/io/crnk/core/queryspec/QuerySpecTest.java +++ b/crnk-core/src/test/java/io/crnk/core/queryspec/QuerySpecTest.java @@ -18,6 +18,32 @@ public void testEqualContract() throws NoSuchFieldException { EqualsVerifier.forClass(QuerySpec.class).usingGetClass().suppress(Warning.NONFINAL_FIELDS).verify(); } + + @Test + public void checkToString() { + QuerySpec spec = new QuerySpec("projects"); + Assert.assertEquals("QuerySpec{resourceType=projects}", spec.toString()); + + spec = new QuerySpec(Project.class); + Assert.assertEquals("QuerySpec{resourceClass=io.crnk.core.mock.models.Project}", spec.toString()); + + spec.addFilter(new FilterSpec(Arrays.asList("filterAttr"), FilterOperator.EQ, "test")); + Assert.assertEquals("QuerySpec{resourceClass=io.crnk.core.mock.models.Project, filters=[filterAttr EQ test]}", spec.toString()); + + spec.addSort(new SortSpec(Arrays.asList("sortAttr"), Direction.ASC)); + Assert.assertEquals("QuerySpec{resourceClass=io.crnk.core.mock.models.Project, filters=[filterAttr EQ test], sort=[sortAttr ASC]}", spec.toString()); + + spec.includeField(Arrays.asList("includedField")); + Assert.assertEquals("QuerySpec{resourceClass=io.crnk.core.mock.models.Project, filters=[filterAttr EQ test], sort=[sortAttr ASC], includedFields=[includedField]}", spec.toString()); + + spec.includeRelation(Arrays.asList("includedRelation")); + Assert.assertEquals("QuerySpec{resourceClass=io.crnk.core.mock.models.Project, filters=[filterAttr EQ test], sort=[sortAttr ASC], includedFields=[includedField], includedRelations=[includedRelation]}", spec.toString()); + + spec.setOffset(12); + spec.setLimit(13L); + Assert.assertEquals("QuerySpec{resourceClass=io.crnk.core.mock.models.Project, limit=13, offset=12, filters=[filterAttr EQ test], sort=[sortAttr ASC], includedFields=[includedField], includedRelations=[includedRelation]}", spec.toString()); + } + @Test public void testBasic() { QuerySpec spec = new QuerySpec(Project.class); @@ -75,6 +101,25 @@ public void testDuplicateWithRelations() { Assert.assertEquals(spec.getQuerySpec(Task.class), duplicate.getQuerySpec(Task.class)); } + @Test + public void setNestedSpecWithClass() { + QuerySpec spec = new QuerySpec(Project.class); + QuerySpec relatedSpec = new QuerySpec(Task.class); + spec.setNestedSpecs(Arrays.asList(relatedSpec)); + Assert.assertSame(relatedSpec, spec.getQuerySpec(Task.class)); + } + + @Test + public void setNestedSpecWithResourceType() { + QuerySpec spec = new QuerySpec("projects"); + QuerySpec relatedSpec = new QuerySpec("tasks"); + spec.setNestedSpecs(Arrays.asList(relatedSpec)); + Assert.assertSame(spec, spec.getQuerySpec("projects")); + Assert.assertSame(relatedSpec, spec.getQuerySpec("tasks")); + Assert.assertSame(relatedSpec, spec.getOrCreateQuerySpec("tasks")); + Assert.assertNotSame(relatedSpec, spec.getOrCreateQuerySpec("schedules")); + } + @Test(expected = IllegalArgumentException.class) public void putRelatedSpecShouldFailIfClassMatchesRoot() { QuerySpec spec = new QuerySpec(Project.class); diff --git a/crnk-core/src/test/java/io/crnk/core/queryspec/repository/DefaultQuerySpecDeserializerTestBase.java b/crnk-core/src/test/java/io/crnk/core/queryspec/repository/DefaultQuerySpecDeserializerTestBase.java index 11e7baa6b..6248ad52d 100644 --- a/crnk-core/src/test/java/io/crnk/core/queryspec/repository/DefaultQuerySpecDeserializerTestBase.java +++ b/crnk-core/src/test/java/io/crnk/core/queryspec/repository/DefaultQuerySpecDeserializerTestBase.java @@ -10,6 +10,7 @@ import io.crnk.core.mock.models.Task; import io.crnk.core.mock.models.TaskWithLookup; import io.crnk.core.queryspec.*; +import io.crnk.core.resource.RestrictedQueryParamsMembers; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -24,12 +25,13 @@ public abstract class DefaultQuerySpecDeserializerTestBase extends AbstractQuery public ExpectedException expectedException = ExpectedException.none(); protected DefaultQuerySpecDeserializer deserializer; protected ResourceInformation taskInformation; + private QuerySpecDeserializerContext deserializerContext; @Before public void setup() { super.setup(); - deserializer = new DefaultQuerySpecDeserializer(); - deserializer.init(new QuerySpecDeserializerContext() { + + deserializerContext = new QuerySpecDeserializerContext() { @Override public ResourceRegistry getResourceRegistry() { @@ -40,7 +42,10 @@ public ResourceRegistry getResourceRegistry() { public TypeParser getTypeParser() { return moduleRegistry.getTypeParser(); } - }); + }; + + deserializer = new DefaultQuerySpecDeserializer(); + deserializer.init(deserializerContext); taskInformation = resourceRegistry.getEntryForClass(Task.class).getResourceInformation(); } @@ -60,7 +65,44 @@ public void operations() { } @Test - public void testFindAll() throws InstantiationException, IllegalAccessException { + public void checkIgnoreParseExceptions() { + Assert.assertFalse(deserializer.isIgnoreParseExceptions()); + deserializer.setIgnoreParseExceptions(true); + Assert.assertTrue(deserializer.isIgnoreParseExceptions()); + + Map> params = new HashMap<>(); + add(params, "filter[id]", "notAnInteger"); + QuerySpec actualSpec = deserializer.deserialize(taskInformation, params); + QuerySpec expectedSpec = new QuerySpec(Task.class); + expectedSpec.addFilter(new FilterSpec(Arrays.asList("id"), FilterOperator.EQ, "notAnInteger")); + Assert.assertEquals(expectedSpec, actualSpec); + } + + @Test(expected = ParametersDeserializationException.class) + public void throwParseExceptionsByDefault() { + Assert.assertFalse(deserializer.isIgnoreParseExceptions()); + + Map> params = new HashMap<>(); + add(params, "filter[id]", "notAnInteger"); + deserializer.deserialize(taskInformation, params); + } + + @Test(expected = ParametersDeserializationException.class) + public void throwParseExceptionsForMultiValuedOffset() { + Map> params = new HashMap<>(); + params.put("page[offset]", new HashSet<>(Arrays.asList("1", "2"))); + deserializer.deserialize(taskInformation, params); + } + + @Test(expected = ParametersDeserializationException.class) + public void throwParseExceptionsWhenBracketsNotClosed() { + Map> params = new HashMap<>(); + add(params, "filter[", "someValue"); + deserializer.deserialize(taskInformation, params); + } + + @Test + public void testFindAll() { Map> params = new HashMap<>(); QuerySpec actualSpec = deserializer.deserialize(taskInformation, params); @@ -94,7 +136,7 @@ public void defaultPaginationOnRelation() { } @Test - public void testFindAllOrderByAsc() throws InstantiationException, IllegalAccessException { + public void testFindAllOrderByAsc() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addSort(new SortSpec(Arrays.asList("name"), Direction.ASC)); @@ -105,7 +147,19 @@ public void testFindAllOrderByAsc() throws InstantiationException, IllegalAccess } @Test - public void testOrderByMultipleAttributes() throws InstantiationException, IllegalAccessException { + public void testFollowNestedObjectWithinResource() { + // follow ProjectData.data + QuerySpec expectedSpec = new QuerySpec(Project.class); + expectedSpec.addSort(new SortSpec(Arrays.asList("data", "data"), Direction.ASC)); + + Map> params = new HashMap<>(); + add(params, "sort", "data.data"); + QuerySpec actualSpec = deserializer.deserialize(taskInformation, params); + Assert.assertEquals(expectedSpec, actualSpec); + } + + @Test + public void testOrderByMultipleAttributes() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addSort(new SortSpec(Arrays.asList("name"), Direction.ASC)); expectedSpec.addSort(new SortSpec(Arrays.asList("id"), Direction.ASC)); @@ -117,7 +171,7 @@ public void testOrderByMultipleAttributes() throws InstantiationException, Illeg } @Test - public void testFindAllOrderByDesc() throws InstantiationException, IllegalAccessException { + public void testFindAllOrderByDesc() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addSort(new SortSpec(Arrays.asList("name"), Direction.DESC)); @@ -129,7 +183,7 @@ public void testFindAllOrderByDesc() throws InstantiationException, IllegalAcces } @Test - public void testFilterWithDefaultOp() throws InstantiationException, IllegalAccessException { + public void testFilterWithDefaultOp() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("name"), FilterOperator.EQ, "value")); @@ -141,7 +195,7 @@ public void testFilterWithDefaultOp() throws InstantiationException, IllegalAcce } @Test - public void testFilterWithComputedAttribute() throws InstantiationException, IllegalAccessException { + public void testFilterWithComputedAttribute() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("computedAttribute"), FilterOperator.EQ, 13)); @@ -153,7 +207,7 @@ public void testFilterWithComputedAttribute() throws InstantiationException, Ill } @Test - public void testFilterWithDotNotation() throws InstantiationException, IllegalAccessException { + public void testFilterWithDotNotation() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("project", "name"), FilterOperator.EQ, "value")); @@ -165,7 +219,7 @@ public void testFilterWithDotNotation() throws InstantiationException, IllegalAc } @Test - public void testFilterWithDotNotationMultipleElements() throws InstantiationException, IllegalAccessException { + public void testFilterWithDotNotationMultipleElements() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("project", "task", "name"), FilterOperator.EQ, "value")); @@ -177,7 +231,7 @@ public void testFilterWithDotNotationMultipleElements() throws InstantiationExce } @Test - public void testUnknownPropertyAllowed() throws InstantiationException, IllegalAccessException { + public void testUnknownPropertyAllowed() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("doesNotExists"), FilterOperator.EQ, "value")); @@ -191,7 +245,7 @@ public void testUnknownPropertyAllowed() throws InstantiationException, IllegalA } @Test(expected = PropertyException.class) - public void testUnknownPropertyNotAllowed() throws InstantiationException, IllegalAccessException { + public void testUnknownPropertyNotAllowed() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("doesNotExists"), FilterOperator.EQ, "value")); @@ -204,7 +258,7 @@ public void testUnknownPropertyNotAllowed() throws InstantiationException, Illeg } @Test - public void testFilterByOne() throws InstantiationException, IllegalAccessException { + public void testFilterByOne() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("name"), FilterOperator.EQ, "value")); @@ -216,7 +270,7 @@ public void testFilterByOne() throws InstantiationException, IllegalAccessExcept } @Test - public void testFilterByMany() throws InstantiationException, IllegalAccessException { + public void testFilterByMany() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("name"), FilterOperator.EQ, new HashSet<>(Arrays.asList("value1", "value2")))); @@ -228,7 +282,7 @@ public void testFilterByMany() throws InstantiationException, IllegalAccessExcep } @Test - public void testFilterEquals() throws InstantiationException, IllegalAccessException { + public void testFilterEquals() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("id"), FilterOperator.EQ, 1L)); @@ -240,7 +294,7 @@ public void testFilterEquals() throws InstantiationException, IllegalAccessExcep } @Test - public void testFilterGreater() throws InstantiationException, IllegalAccessException { + public void testFilterGreater() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("id"), FilterOperator.LE, 1L)); @@ -252,7 +306,7 @@ public void testFilterGreater() throws InstantiationException, IllegalAccessExce } @Test - public void testFilterGreaterOnRoot() throws InstantiationException, IllegalAccessException { + public void testFilterGreaterOnRoot() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.addFilter(new FilterSpec(Arrays.asList("id"), FilterOperator.LE, 1L)); @@ -264,7 +318,7 @@ public void testFilterGreaterOnRoot() throws InstantiationException, IllegalAcce } @Test - public void testPaging() throws InstantiationException, IllegalAccessException { + public void testPaging() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.setLimit(2L); expectedSpec.setOffset(1L); @@ -277,8 +331,40 @@ public void testPaging() throws InstantiationException, IllegalAccessException { Assert.assertEquals(expectedSpec, actualSpec); } + @Test + public void deserializeUnknownParameter() { + Map> params = new HashMap<>(); + add(params, "doesNotExist[something]", "someValue"); + + final boolean[] deserialized = new boolean[1]; + deserializer = new DefaultQuerySpecDeserializer() { + @Override + protected void deserializeUnknown(QuerySpec querySpec, Parameter parameter) { + Assert.assertEquals(RestrictedQueryParamsMembers.unknown, parameter.getParamType()); + Assert.assertEquals("doesNotExist", parameter.getStrParamType()); + Assert.assertEquals("doesNotExist[something]", parameter.getName()); + Assert.assertNull(parameter.getResourceInformation()); + Assert.assertNull(parameter.getOperator()); + Assert.assertNull(parameter.getAttributePath()); + Assert.assertEquals(1, parameter.getValues().size()); + deserialized[0] = true; + } + }; + deserializer.init(deserializerContext); + deserializer.deserialize(taskInformation, params); + Assert.assertTrue(deserialized[0]); + } + + @Test(expected = ParametersDeserializationException.class) - public void testPagingError() throws InstantiationException, IllegalAccessException { + public void testInvalidPagingType() { + Map> params = new HashMap<>(); + add(params, "page[doesNotExist]", "1"); + deserializer.deserialize(taskInformation, params); + } + + @Test(expected = ParametersDeserializationException.class) + public void testPagingError() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.setLimit(2L); expectedSpec.setOffset(1L); @@ -291,7 +377,7 @@ public void testPagingError() throws InstantiationException, IllegalAccessExcept } @Test - public void testPagingMaxLimitNotAllowed() throws InstantiationException, IllegalAccessException { + public void testPagingMaxLimitNotAllowed() { Map> params = new HashMap<>(); add(params, "page[offset]", "1"); add(params, "page[limit]", "5"); @@ -303,7 +389,7 @@ public void testPagingMaxLimitNotAllowed() throws InstantiationException, Illega } @Test - public void testPagingMaxLimitAllowed() throws InstantiationException, IllegalAccessException { + public void testPagingMaxLimitAllowed() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.setOffset(1L); expectedSpec.setLimit(5L); @@ -318,7 +404,7 @@ public void testPagingMaxLimitAllowed() throws InstantiationException, IllegalAc } @Test - public void testIncludeRelations() throws InstantiationException, IllegalAccessException { + public void testIncludeRelations() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.includeRelation(Arrays.asList("project")); @@ -330,7 +416,7 @@ public void testIncludeRelations() throws InstantiationException, IllegalAccessE } @Test - public void testIncludeRelationsOnRoot() throws InstantiationException, IllegalAccessException { + public void testIncludeRelationsOnRoot() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.includeRelation(Arrays.asList("project")); @@ -342,7 +428,7 @@ public void testIncludeRelationsOnRoot() throws InstantiationException, IllegalA } @Test - public void testIncludeAttributes() throws InstantiationException, IllegalAccessException { + public void testIncludeAttributes() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.includeField(Arrays.asList("name")); @@ -354,7 +440,7 @@ public void testIncludeAttributes() throws InstantiationException, IllegalAccess } @Test - public void testIncludeAttributesOnRoot() throws InstantiationException, IllegalAccessException { + public void testIncludeAttributesOnRoot() { QuerySpec expectedSpec = new QuerySpec(Task.class); expectedSpec.includeField(Arrays.asList("name")); @@ -380,7 +466,7 @@ public void testHyphenIsAllowedInResourceName() { } @Test - public void testIngoreParseException() throws InstantiationException, IllegalAccessException { + public void testIngoreParseException() { Map> params = new HashMap<>(); add(params, "filter[id]", "NotAnId"); deserializer.setIgnoreParseExceptions(true); @@ -392,7 +478,7 @@ public void testIngoreParseException() throws InstantiationException, IllegalAcc } @Test - public void testGenericCast() throws InstantiationException, IllegalAccessException { + public void testGenericCast() { Map> params = new HashMap<>(); add(params, "filter[id]", "12"); add(params, "filter[name]", "test"); @@ -411,15 +497,15 @@ public void testGenericCast() throws InstantiationException, IllegalAccessExcept } @Test(expected = ParametersDeserializationException.class) - public void testFailOnParseException() throws InstantiationException, IllegalAccessException { + public void testFailOnParseException() { Map> params = new HashMap<>(); add(params, "filter[id]", "NotAnId"); deserializer.setIgnoreParseExceptions(false); deserializer.deserialize(taskInformation, params); } - @Test(expected = IllegalStateException.class) - public void testUnknownProperty() throws InstantiationException, IllegalAccessException { + @Test(expected = ParametersDeserializationException.class) + public void testUnknownProperty() { Map> params = new HashMap<>(); add(params, "group", "test"); deserializer.setIgnoreParseExceptions(false); diff --git a/crnk-gen-typescript/build.gradle b/crnk-gen-typescript/build.gradle index 229b456be..c2742c2d7 100644 --- a/crnk-gen-typescript/build.gradle +++ b/crnk-gen-typescript/build.gradle @@ -1,7 +1,19 @@ apply plugin: 'java' +apply plugin: 'java-gradle-plugin' + +gradlePlugin { + plugins { + 'crnk-gen-typescript' { + id = "crnk-gen-typescript" + implementationClass = "io.crnk.gen.typescript.TSGeneratorPlugin" + } + } +} + dependencies { compile gradleApi() + testCompile gradleTestKit() compileOnly project(':crnk-core') compileOnly project(':crnk-meta') @@ -18,7 +30,7 @@ dependencies { testCompile project(':crnk-meta') testCompile project(':crnk-test') testCompile project(':crnk-cdi') - testCompile 'junit:junit:4.12' + testCompile 'junit:junit:4.12' testCompile 'commons-io:commons-io:2.5' testCompile 'org.apache.deltaspike.modules:deltaspike-test-control-module-api:1.7.1' testCompile 'org.apache.deltaspike.core:deltaspike-core-impl:1.7.1' @@ -27,6 +39,8 @@ dependencies { testCompile 'org.apache.deltaspike.cdictrl:deltaspike-cdictrl-weld:1.7.1' testCompile 'org.jboss.weld.se:weld-se-core:2.4.0.Final' testCompile 'javax:javaee-api:7.0' + + } // e.g. CDI expects those directories to be equal @@ -45,3 +59,4 @@ jar { + diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/GeneratorTrigger.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/GeneratorTrigger.java index 6dc104b29..0c8b7776c 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/GeneratorTrigger.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/GeneratorTrigger.java @@ -1,5 +1,7 @@ package io.crnk.gen.runtime; +import java.io.IOException; + public interface GeneratorTrigger { /** @@ -7,5 +9,5 @@ public interface GeneratorTrigger { * * @param lookup of type io.katharsis.meta.MetaLookup. Not that the MetaLookup class is not available here. */ - void generate(Object lookup); + void generate(Object lookup) throws IOException; } \ No newline at end of file diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/RuntimeClassLoaderFactory.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/RuntimeClassLoaderFactory.java index 144465c4f..a39e0b7c8 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/RuntimeClassLoaderFactory.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/RuntimeClassLoaderFactory.java @@ -1,23 +1,25 @@ package io.crnk.gen.runtime; -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.SortedSet; - -import io.crnk.gen.typescript.GenerateTypescriptTask; +import io.crnk.gen.typescript.RuntimeMetaResolver; +import io.crnk.gen.typescript.TSGeneratorConfiguration; +import io.crnk.gen.typescript.TSNpmConfiguration; +import io.crnk.gen.typescript.TSRuntimeConfiguration; +import io.crnk.gen.typescript.internal.TSGeneratorRuntimeContext; import io.crnk.gen.typescript.model.TSElement; +import io.crnk.gen.typescript.processor.TSSourceProcessor; +import io.crnk.gen.typescript.writer.TSCodeStyle; import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSetContainer; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; + /** * Code generation runs within the application classpath, not in the gradle classpath. * This factory constructs such a classloader. Currently it makes use of the integrationTest @@ -32,7 +34,7 @@ public RuntimeClassLoaderFactory(Project project) { this.project = project; } - public URLClassLoader createClassLoader(ClassLoader parentClassLoader, Map> sharedClasses) { + public URLClassLoader createClassLoader(ClassLoader parentClassLoader) { Set classURLs = new HashSet<>(); // NOSONAR URL needed by URLClassLoader classURLs.addAll(getProjectClassUrls()); classURLs.add(getPluginUrl()); @@ -41,39 +43,43 @@ public URLClassLoader createClassLoader(ClassLoader parentClassLoader, Map> sharedClasses; - public SharedClassLoader(ClassLoader bootstrapClassLoader, ClassLoader parentClassLoader, - Map> sharedClasses) { + public SharedClassLoader(ClassLoader bootstrapClassLoader, ClassLoader parentClassLoader) { super(bootstrapClassLoader); this.parentClassLoader = parentClassLoader; - this.sharedClasses = sharedClasses; + + sharedClasses = new HashMap<>(); + sharedClasses.put(GeneratorTrigger.class.getName(), GeneratorTrigger.class); + sharedClasses.put(TSGeneratorConfiguration.class.getName(), TSGeneratorConfiguration.class); + sharedClasses.put(TSNpmConfiguration.class.getName(), TSNpmConfiguration.class); + sharedClasses.put(TSRuntimeConfiguration.class.getName(), TSRuntimeConfiguration.class); + sharedClasses.put(TSCodeStyle.class.getName(), TSCodeStyle.class); + sharedClasses.put(io.crnk.gen.typescript.RuntimeMetaResolver.class.getName(), RuntimeMetaResolver.class); + sharedClasses.put(TSSourceProcessor.class.getName(), TSSourceProcessor.class); + sharedClasses.put(TSGeneratorRuntimeContext.class.getName(), TSGeneratorRuntimeContext.class); } @Override protected synchronized URL findResource(String name) { - URL sharedResourceUrl = GenerateTypescriptTask.class.getClassLoader().getResource(name); - if (sharedResourceUrl != null) { - return sharedResourceUrl; - } - URL resource = super.findResource(name); - if (resource == null && "logback-test.xml".equals(name)) { - URL logbackUrl = RuntimeClassLoaderFactory.class.getClassLoader().getResource("logback-test.xml"); + if ("logback-test.xml".equals(name)) { + URL logbackUrl = parentClassLoader.getResource("logback-test.xml"); if (logbackUrl == null) { throw new IllegalStateException("logback-test.xml could not be found"); } return logbackUrl; } - return resource; + return super.findResource(name); } @Override @@ -89,6 +95,9 @@ protected synchronized Class loadClass(String name, boolean resolve) throws C return super.loadClass(name, resolve); } + public void putSharedClass(String name, Class clazz) { + sharedClasses.put(name, clazz); + } } private URL getPluginUrl() { @@ -115,9 +124,12 @@ private Set getProjectClassFiles() { } } - // add gradle integrationTest dependencies to url - org.gradle.api.artifacts.Configuration runtimeConfiguration = project.getConfigurations() - .getByName("integrationTestRuntime"); + // add dependencies from configured gradle configuration to url (usually test or integrationTest) + TSGeneratorConfiguration generatorConfiguration = project.getExtensions().getByType(TSGeneratorConfiguration.class); + String configurationName = generatorConfiguration.getRuntime().getConfiguration(); + + ConfigurationContainer configurations = project.getConfigurations(); + Configuration runtimeConfiguration = configurations.getByName(configurationName + "Runtime"); classpath.addAll(runtimeConfiguration.getFiles()); return classpath; @@ -129,8 +141,7 @@ private Collection getProjectClassUrls() { for (File file : projectClassFiles) { try { urls.add(file.toURI().toURL()); - } - catch (MalformedURLException e) { + } catch (MalformedURLException e) { throw new IllegalStateException(); } } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/deltaspike/DeltaspikeTypescriptGenerator.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/deltaspike/DeltaspikeTypescriptGenerator.java index c2c369fdd..57b7c8aa8 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/deltaspike/DeltaspikeTypescriptGenerator.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/runtime/deltaspike/DeltaspikeTypescriptGenerator.java @@ -13,6 +13,8 @@ import org.junit.Test; import org.junit.runner.RunWith; +import java.io.IOException; + /** * Resolution of MetaLookup implemented as test case. There are nicer things to look at, but it is simple and serves its * purpose. @@ -24,7 +26,7 @@ public class DeltaspikeTypescriptGenerator { private static GeneratorTrigger context; @Test - public void test() { + public void test() throws IOException { CrnkBoot boot = new CrnkBoot(); boot.setServiceUrlProvider(new ConstantServiceUrlProvider("http://")); boot.boot(); @@ -33,7 +35,7 @@ public void test() { Optional optionalModule = moduleRegistry.getModule(MetaModule.class); if (!optionalModule.isPresent()) { - throw new IllegalStateException("add MetaModule to CDI setup, got: " + moduleRegistry.getModules()); + throw new IllegalStateException("add MetaModule to CDI setup, got: " + moduleRegistry.getModules() + " with " + boot.getServiceDiscovery()); } MetaModule metaModule = optionalModule.get(); diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/GenerateTypescriptTask.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/GenerateTypescriptTask.java index b1cb6e32b..7dc40939d 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/GenerateTypescriptTask.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/GenerateTypescriptTask.java @@ -1,26 +1,17 @@ package io.crnk.gen.typescript; -import java.io.File; -import java.io.IOException; -import java.net.URLClassLoader; -import java.util.HashMap; -import java.util.Map; - import io.crnk.gen.runtime.GeneratorTrigger; import io.crnk.gen.runtime.RuntimeClassLoaderFactory; import io.crnk.gen.typescript.internal.TSGeneratorRuntimeContext; import io.crnk.gen.typescript.internal.TSGeneratorRuntimeContextImpl; -import io.crnk.gen.typescript.processor.TSSourceProcessor; -import io.crnk.gen.typescript.writer.TSCodeStyle; import org.gradle.api.DefaultTask; import org.gradle.api.Project; -import org.gradle.api.file.FileTree; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.SkipWhenEmpty; -import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskAction; +import java.io.File; +import java.io.IOException; +import java.net.URLClassLoader; + public class GenerateTypescriptTask extends DefaultTask { public static final String NAME = "generateTypescript"; @@ -30,29 +21,6 @@ public GenerateTypescriptTask() { setDescription("generate Typescript stubs from a Crnk setup"); } - /** - * Register resources directory as input to have incremental builds. - */ - @InputFiles - public FileTree getResourcesInput() { - return getMainSourceSet().getResources().getAsFileTree(); - } - - /** - * Register java sources as input to have incremental builds. - */ - @InputFiles - @SkipWhenEmpty - public FileTree getJavaInput() { - return getMainSourceSet().getJava().getAsFileTree(); - } - - private SourceSet getMainSourceSet() { - Project project = getProject(); - SourceSetContainer sourceSets = (SourceSetContainer) project.getProperties().get("sourceSets"); - return sourceSets.getByName("main"); - } - public File getOutputDirectory() { Project project = getProject(); String srcDirectoryPath = "generated/source/typescript/"; @@ -64,27 +32,13 @@ public void generate() throws IOException { Thread thread = Thread.currentThread(); ClassLoader contextClassLoader = thread.getContextClassLoader(); - Map> sharedClasses = new HashMap<>(); - sharedClasses.put(GeneratorTrigger.class.getName(), GeneratorTrigger.class); - sharedClasses.put(TSGeneratorConfiguration.class.getName(), TSGeneratorConfiguration.class); - sharedClasses.put(TSNpmConfiguration.class.getName(), TSNpmConfiguration.class); - sharedClasses.put(TSCodeStyle.class.getName(), TSCodeStyle.class); - sharedClasses.put(RuntimeMetaResolver.class.getName(), RuntimeMetaResolver.class); - sharedClasses.put(TSSourceProcessor.class.getName(), TSSourceProcessor.class); - sharedClasses.put(TSGeneratorRuntimeContext.class.getName(), TSGeneratorRuntimeContext.class); - RuntimeClassLoaderFactory classLoaderFactory = new RuntimeClassLoaderFactory(getProject()); - URLClassLoader classloader = classLoaderFactory.createClassLoader(contextClassLoader, sharedClasses); - - TSGeneratorConfiguration config = getConfig(); - setupDefaultConfig(config); - + URLClassLoader classloader = classLoaderFactory.createClassLoader(contextClassLoader); try { thread.setContextClassLoader(classloader); runGeneration(); - } - finally { + } finally { // make sure to restore the classloader when leaving this task thread.setContextClassLoader(contextClassLoader); @@ -96,37 +50,39 @@ public void generate() throws IOException { protected void setupDefaultConfig(TSGeneratorConfiguration config) { String defaultVersion = getProject().getVersion().toString(); - if (config.getNpmPackageVersion() == null) { - config.setNpmPackageVersion(defaultVersion); + if (config.getNpm().getPackageVersion() == null) { + config.getNpm().setPackageVersion(defaultVersion); } } protected RuntimeMetaResolver getRuntime() { TSGeneratorConfiguration config = getConfig(); String runtimeClass = config.getMetaResolverClassName(); - try { - Class clazz = getClass().getClassLoader().loadClass(runtimeClass); - return (RuntimeMetaResolver) clazz.newInstance(); - } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - throw new IllegalStateException("failed to load runtime " + runtimeClass, e); - } + return (RuntimeMetaResolver) loadClass(getClass().getClassLoader(), runtimeClass); } protected void runGeneration() { - File outputDir = getOutputDirectory(); TSGeneratorConfiguration config = getConfig(); + setupDefaultConfig(config); + + File outputDir = getOutputDirectory(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + GeneratorTrigger context = (GeneratorTrigger) loadClass(classLoader, TSGeneratorRuntimeContextImpl.class.getName()); + TSGeneratorRuntimeContext genContext = (TSGeneratorRuntimeContext) context; + genContext.setOutputDir(outputDir); + genContext.setConfig(config); + RuntimeMetaResolver runtime = getRuntime(); + runtime.run(context, classLoader); + + + } + + private Object loadClass(ClassLoader classLoader, String name) { try { - Class contextClass = classLoader.loadClass(TSGeneratorRuntimeContextImpl.class.getName()); - GeneratorTrigger context = (GeneratorTrigger) contextClass.newInstance(); - TSGeneratorRuntimeContext genContext = (TSGeneratorRuntimeContext) context; - genContext.setOutputDir(outputDir); - genContext.setConfig(config); - RuntimeMetaResolver runtime = getRuntime(); - runtime.run(context, classLoader); - } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + Class clazz = classLoader.loadClass(name); + return clazz.newInstance(); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new IllegalStateException("failed to load class", e); } } @@ -135,4 +91,5 @@ private TSGeneratorConfiguration getConfig() { Project project = getProject(); return project.getExtensions().getByType(TSGeneratorConfiguration.class); } + } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSGeneratorConfiguration.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSGeneratorConfiguration.java index dd35d98de..e5fd49b81 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSGeneratorConfiguration.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSGeneratorConfiguration.java @@ -1,18 +1,8 @@ package io.crnk.gen.typescript; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - import groovy.lang.Closure; import io.crnk.gen.typescript.model.libraries.ExpressionLibrary; -import io.crnk.gen.typescript.processor.TSEmptyObjectFactoryProcessor; -import io.crnk.gen.typescript.processor.TSExpressionObjectProcessor; -import io.crnk.gen.typescript.processor.TSImportProcessor; -import io.crnk.gen.typescript.processor.TSIndexFileProcessor; -import io.crnk.gen.typescript.processor.TSSourceProcessor; +import io.crnk.gen.typescript.processor.*; import io.crnk.gen.typescript.transform.TSMetaDataObjectTransformation; import io.crnk.gen.typescript.transform.TSMetaEnumTypeTransformation; import io.crnk.gen.typescript.transform.TSMetaPrimitiveTypeTransformation; @@ -20,6 +10,11 @@ import io.crnk.gen.typescript.writer.TSCodeStyle; import org.gradle.api.Project; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + public class TSGeneratorConfiguration { @@ -46,6 +41,8 @@ public class TSGeneratorConfiguration { private TSNpmConfiguration npm = new TSNpmConfiguration(); + private TSRuntimeConfiguration runtime = new TSRuntimeConfiguration(); + public TSGeneratorConfiguration(Project project) { this.project = project; @@ -59,8 +56,10 @@ public TSGeneratorConfiguration(Project project) { metaTransformationClassNames.add(TSMetaEnumTypeTransformation.class.getName()); metaTransformationClassNames.add(TSMetaPrimitiveTypeTransformation.class.getName()); metaTransformationClassNames.add(TSMetaResourceRepositoryTransformation.class.getName()); + } - + public TSRuntimeConfiguration getRuntime() { + return runtime; } public TSNpmConfiguration getNpm() { @@ -116,38 +115,6 @@ public void setGenerateExpressions(boolean generateExpressions) { this.generateExpressions = generateExpressions; } - /** - * @Deprecated use npm object - */ - @Deprecated - public Map getNpmPackageMapping() { - return npm.getPackageMapping(); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public void setNpmPackageMapping(Map packageMapping) { - this.npm.setPackageMapping(packageMapping); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public String getNpmPackageName() { - return npm.getPackageName(); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public void setNpmPackageName(String npmPackageName) { - this.npm.setPackageName(npmPackageName); - } - /** * @return code style to use for generated TypeScript sources. */ @@ -184,22 +151,6 @@ public void setMetaResolverClassName(String metaResolverClassName) { this.metaResolverClassName = metaResolverClassName; } - /** - * @Deprecated use npm object - */ - @Deprecated - public String getNpmPackageVersion() { - return npm.getPackageVersion(); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public void setNpmPackageVersion(String npmPackageVersion) { - this.npm.setPackageVersion(npmPackageVersion); - } - public String getSourceDirectoryName() { return sourceDirectoryName; } @@ -207,85 +158,4 @@ public String getSourceDirectoryName() { public void setSourceDirectoryName(String sourceDirectoryName) { this.sourceDirectoryName = sourceDirectoryName; } - - /** - * @Deprecated use npm object - */ - @Deprecated - public String getNpmDescription() { - return npm.getDescription(); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public String getNpmLicense() { - return npm.getLicense(); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public void setNpmLicense(String npmLicense) { - this.npm.setLicense(npmLicense); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public void setNpmDescription(String npmDescription) { - this.npm.setDescription(npmDescription); - } - - - /** - * @Deprecated use npmPeerDependencies - */ - @Deprecated - public Map getNpmDependencies() { - return npm.getPeerDependencies(); - } - - /** - * @Deprecated use npmPeerDependencies - */ - @Deprecated - public void setNpmDependencies(Map npmDependencies) { - this.setPeerNpmDependencies(npmDependencies); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public Map getPeerNpmDependencies() { - return npm.getPeerDependencies(); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public void setPeerNpmDependencies(Map npmPeerDependencies) { - this.npm.setPeerDependencies(npmPeerDependencies); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public Map getNpmDevDependencies() { - return npm.getDevDependencies(); - } - - /** - * @Deprecated use npm object - */ - @Deprecated - public void setNpmDevDependencies(Map npmDevDependencies) { - this.npm.setDevDependencies(npmDevDependencies); - } } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSGeneratorPlugin.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSGeneratorPlugin.java index 65d288ec1..6b7db53bf 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSGeneratorPlugin.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSGeneratorPlugin.java @@ -1,20 +1,16 @@ package io.crnk.gen.typescript; -import java.io.File; - import com.moowork.gradle.node.npm.NpmInstallTask; import io.crnk.gen.typescript.internal.TypescriptUtils; -import org.gradle.api.Action; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.UnknownTaskException; +import org.gradle.api.*; import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileTree; import org.gradle.api.tasks.Copy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; + public class TSGeneratorPlugin implements Plugin { private static final Logger LOGGER = LoggerFactory.getLogger(TSGeneratorPlugin.class); @@ -26,7 +22,10 @@ public void apply(final Project project) { final File distDir = new File(project.getBuildDir(), "npm"); Configuration compileConfiguration = project.getConfigurations().getByName("compile"); - GenerateTypescriptTask generateTask = project.getTasks().create(GenerateTypescriptTask.NAME, + + project.getTasks().create(PublishTypescriptStubsTask.NAME, PublishTypescriptStubsTask.class); + + final GenerateTypescriptTask generateTask = project.getTasks().create(GenerateTypescriptTask.NAME, GenerateTypescriptTask.class); generateTask.getInputs().file(compileConfiguration.getFiles()); generateTask.getOutputs().dir(sourcesDir); @@ -44,6 +43,7 @@ public void apply(final Project project) { @Override public void execute(Task task) { File targetFile = new File(buildDir, ".npmrc"); + buildDir.mkdirs(); TypescriptUtils.copyFile(npmrcFile, targetFile); } }); @@ -58,8 +58,7 @@ public void execute(Task task) { npmInstall.getInputs().file(new File(buildDir, "package.json")); npmInstall.getOutputs().dir(new File(buildDir, "node_modules")); compileTypescriptTask.dependsOn(npmInstall); - } - catch (UnknownTaskException e) { + } catch (UnknownTaskException e) { LOGGER.warn("task not found, ok in testing", e); } @@ -71,15 +70,6 @@ public void execute(Task task) { compileTypescriptTask.getInputs().files(fileTree); compileTypescriptTask.setWorkingDir(buildDir); compileTypescriptTask.getOutputs().dir(buildDir); - try { - Task processIntegrationTestResourcesTask = project.getTasks().getByName("processIntegrationTestResources"); - Task integrationCompileJavaTask = project.getTasks().getByName("compileIntegrationTestJava"); - Task assembleTask = project.getTasks().getByName("assemble"); - generateTask.dependsOn(assembleTask, integrationCompileJavaTask, processIntegrationTestResourcesTask); - } - catch (Exception e) { - LOGGER.error("failed to setup dependencies, is integrationTest and testSet plugin properly setup", e); - } ConfigurableFileTree assembleFileTree = project.fileTree(new File(buildDir, "src")); assembleFileTree.include("**/*.ts"); @@ -92,7 +82,24 @@ public void execute(Task task) { assembleSources.into(distDir); assembleSources.dependsOn(compileTypescriptTask); - TSGeneratorConfiguration config = new TSGeneratorConfiguration(project); + final TSGeneratorConfiguration config = new TSGeneratorConfiguration(project); project.getExtensions().add("typescriptGen", config); + + // setup dependency of generate task (configurable by extension) + final Task assembleTask = project.getTasks().getByName("assemble"); + generateTask.dependsOn(assembleTask); + project.afterEvaluate(new Action() { + @Override + public void execute(Project project) { + String runtimeConfiguration = config.getRuntime().getConfiguration(); + String runtimeConfigurationFirstUpper = Character.toUpperCase(runtimeConfiguration.charAt(0)) + runtimeConfiguration.substring(1); + + Task processIntegrationTestResourcesTask = project.getTasks().getByName("process" + runtimeConfigurationFirstUpper + "Resources"); + Task integrationCompileJavaTask = project.getTasks().getByName("compile" + runtimeConfigurationFirstUpper + "Java"); + generateTask.dependsOn(integrationCompileJavaTask, processIntegrationTestResourcesTask); + } + }); + + } } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSRuntimeConfiguration.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSRuntimeConfiguration.java new file mode 100644 index 000000000..a1752632e --- /dev/null +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/TSRuntimeConfiguration.java @@ -0,0 +1,14 @@ +package io.crnk.gen.typescript; + +public class TSRuntimeConfiguration { + + private String configuration = "integrationTest"; + + public String getConfiguration() { + return configuration; + } + + public void setConfiguration(String configuration) { + this.configuration = configuration; + } +} diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/internal/TSGenerator.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/internal/TSGenerator.java index d00a1f453..31b93a189 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/internal/TSGenerator.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/internal/TSGenerator.java @@ -1,16 +1,5 @@ package io.crnk.gen.typescript.internal; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; - import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -29,6 +18,12 @@ import io.crnk.meta.MetaLookup; import io.crnk.meta.model.MetaElement; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.Callable; + public class TSGenerator { private File outputDir; @@ -60,7 +55,7 @@ public Object call() throws Exception { } } - public void run() { + public void run() throws IOException { writePackaging(); writeTypescriptConfig(); transformMetaToTypescript(); @@ -68,96 +63,83 @@ public void run() { writeSources(); } - private void writeTypescriptConfig() { - try { - ObjectMapper mapper = new ObjectMapper(); - mapper.enable(SerializationFeature.INDENT_OUTPUT); - ObjectNode root = mapper.createObjectNode(); - ObjectNode compilerOptions = root.putObject("compilerOptions"); - compilerOptions.put("baseUrl", ""); - compilerOptions.put("declaration", false); - compilerOptions.put("emitDecoratorMetadata", true); - compilerOptions.put("experimentalDecorators", true); - compilerOptions.put("module", "es6"); - compilerOptions.put("moduleResolution", "node"); - compilerOptions.put("sourceMap", true); - compilerOptions.put("target", "es5"); - ArrayNode typeArrays = compilerOptions.putArray("typeRoots"); - typeArrays.add("node_modules/@types"); - ArrayNode libs = compilerOptions.putArray("lib"); - libs.add("es6"); - libs.add("dom"); - - File outputSourceDir = new File(outputDir, config.getSourceDirectoryName()); - File file = new File(outputSourceDir, "tsconfig.json"); - file.getParentFile().mkdirs(); - mapper.writer().writeValue(file, root); - } - catch (IOException e) { - throw new IllegalStateException(e); - } + private void writeTypescriptConfig() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + ObjectNode root = mapper.createObjectNode(); + ObjectNode compilerOptions = root.putObject("compilerOptions"); + compilerOptions.put("baseUrl", ""); + compilerOptions.put("declaration", false); + compilerOptions.put("emitDecoratorMetadata", true); + compilerOptions.put("experimentalDecorators", true); + compilerOptions.put("module", "es6"); + compilerOptions.put("moduleResolution", "node"); + compilerOptions.put("sourceMap", true); + compilerOptions.put("target", "es5"); + ArrayNode typeArrays = compilerOptions.putArray("typeRoots"); + typeArrays.add("node_modules/@types"); + ArrayNode libs = compilerOptions.putArray("lib"); + libs.add("es6"); + libs.add("dom"); + + File outputSourceDir = new File(outputDir, config.getSourceDirectoryName()); + File file = new File(outputSourceDir, "tsconfig.json"); + file.getParentFile().mkdirs(); + mapper.writer().writeValue(file, root); } - protected void writePackaging() { - try { - ObjectMapper mapper = new ObjectMapper(); - mapper.enable(SerializationFeature.INDENT_OUTPUT); + protected void writePackaging() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); - ObjectNode packageJson = mapper.createObjectNode(); + ObjectNode packageJson = mapper.createObjectNode(); - TSNpmConfiguration npm = config.getNpm(); + TSNpmConfiguration npm = config.getNpm(); - packageJson.put("name", npm.getPackageName()); - packageJson.put("version", npm.getPackageVersion()); - packageJson.put("description", npm.getDescription()); - packageJson.put("license", npm.getLicense()); - if (npm.getGitRepository() != null) { - ObjectNode repository = packageJson.putObject("repository"); - repository.put("type", "git"); - repository.put("url", npm.getGitRepository()); - } + packageJson.put("name", npm.getPackageName()); + packageJson.put("version", npm.getPackageVersion()); + packageJson.put("description", npm.getDescription()); + packageJson.put("license", npm.getLicense()); + if (npm.getGitRepository() != null) { + ObjectNode repository = packageJson.putObject("repository"); + repository.put("type", "git"); + repository.put("url", npm.getGitRepository()); + } - ObjectNode peerDependencies = packageJson.putObject("peerDependencies"); - ObjectNode devDependencies = packageJson.putObject("devDependencies"); - for (Map.Entry entry : npm.getPeerDependencies().entrySet()) { - peerDependencies.put(entry.getKey(), entry.getValue()); - devDependencies.put(entry.getKey(), entry.getValue()); - } - for (Map.Entry entry : npm.getDevDependencies().entrySet()) { - devDependencies.put(entry.getKey(), entry.getValue()); - } + ObjectNode peerDependencies = packageJson.putObject("peerDependencies"); + ObjectNode devDependencies = packageJson.putObject("devDependencies"); + for (Map.Entry entry : npm.getPeerDependencies().entrySet()) { + peerDependencies.put(entry.getKey(), entry.getValue()); + devDependencies.put(entry.getKey(), entry.getValue()); + } + for (Map.Entry entry : npm.getDevDependencies().entrySet()) { + devDependencies.put(entry.getKey(), entry.getValue()); + } - File packageJsonFile = new File(outputDir, "package.json"); - packageJsonFile.getParentFile().mkdirs(); + File packageJsonFile = new File(outputDir, "package.json"); + packageJsonFile.getParentFile().mkdirs(); - ObjectNode scripts = packageJson.putObject("scripts"); - scripts.put("build", "npm run build:js"); - scripts.put("build:js", "tsc -p ./src --declaration"); - packageJson.put("name", npm.getPackageName()); - packageJson.put("version", npm.getPackageVersion()); + ObjectNode scripts = packageJson.putObject("scripts"); + scripts.put("build", "npm run build:js"); + scripts.put("build:js", "tsc -p ./src --declaration"); + packageJson.put("name", npm.getPackageName()); + packageJson.put("version", npm.getPackageVersion()); - // match what is expected by NPM. Otherwise NPM will reformat it and up-to-date checking will fail - // the first time (also platform specific) - String text = mapper.writer().writeValueAsString(packageJson); - text = text.replace(" : ", ": "); - text = text + "\n"; - text = text.replace(System.lineSeparator(), "\n"); + // match what is expected by NPM. Otherwise NPM will reformat it and up-to-date checking will fail + // the first time (also platform specific) + String text = mapper.writer().writeValueAsString(packageJson); + text = text.replace(" : ", ": "); + text = text + "\n"; + text = text.replace(System.lineSeparator(), "\n"); - try (FileWriter writer = new FileWriter(packageJsonFile)) { - writer.write(text); - } + try (FileWriter writer = new FileWriter(packageJsonFile)) { + writer.write(text); } - catch (IOException e) { - throw new IllegalStateException(e); - } - } - protected void writeSources() { - PreconditionUtil.assertNotNull("no typescriptGen.npmPackageName configured", config.getNpmPackageName()); - + protected void writeSources() throws IOException { for (TSSource fileSource : sources) { TSWriter writer = new TSWriter(config.getCodeStyle()); fileSource.accept(writer); @@ -168,14 +150,11 @@ protected void writeSources() { } } - protected static void write(File file, String source) { + protected static void write(File file, String source) throws IOException { file.getParentFile().mkdirs(); try (FileWriter writer = new FileWriter(file)) { writer.write(source); } - catch (IOException e) { - throw new IllegalStateException("failed to write " + file.getAbsolutePath(), e); - } } protected File getFile(TSSource sourceFile) { @@ -219,18 +198,22 @@ public void runProcessors() { } } - private TSElement transform(MetaElement element, TSMetaTransformationOptions options) { + protected TSElement transform(MetaElement element, TSMetaTransformationOptions options) { if (elementSourceMap.containsKey(element)) { return elementSourceMap.get(element); } for (TSMetaTransformation transformation : transformations) { if (transformation.accepts(element)) { - return transformation.transform(element, new TSMetaTransformationContextImpl(), options); + return transformation.transform(element, createMetaTransformationContext(), options); } } throw new IllegalStateException("unexpected element: " + element); } + protected TSMetaTransformationContext createMetaTransformationContext() { + return new TSMetaTransformationContextImpl(); + } + private boolean isRoot(MetaElement element) { for (TSMetaTransformation transformation : transformations) { if (transformation.accepts(element)) { @@ -243,7 +226,7 @@ private boolean isRoot(MetaElement element) { protected class TSMetaTransformationContextImpl implements TSMetaTransformationContext { private boolean isGenerated(TSSource source) { - return source.getNpmPackage().equals(config.getNpmPackageName()); + return source.getNpmPackage().equals(config.getNpm().getPackageName()); } @Override @@ -251,7 +234,7 @@ public String getDirectory(MetaElement meta) { String idPath = meta.getId().substring(0, meta.getId().lastIndexOf('.')); String prefix = idPath; while (true) { - String npmName = config.getNpmPackageMapping().get(prefix); + String npmName = config.getNpm().getPackageMapping().get(prefix); if (npmName != null) { return idPath.substring(prefix.length()).replace('.', '/'); } @@ -269,7 +252,7 @@ public String getNpmPackage(MetaElement meta) { String idPath = meta.getId().substring(0, meta.getId().lastIndexOf('.')); String prefix = idPath; while (true) { - String npmName = config.getNpmPackageMapping().get(prefix); + String npmName = config.getNpm().getPackageMapping().get(prefix); if (npmName != null) { return npmName; } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/internal/TSGeneratorRuntimeContextImpl.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/internal/TSGeneratorRuntimeContextImpl.java index 72c6bb11b..6b7831689 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/internal/TSGeneratorRuntimeContextImpl.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/internal/TSGeneratorRuntimeContextImpl.java @@ -1,6 +1,7 @@ package io.crnk.gen.typescript.internal; import java.io.File; +import java.io.IOException; import io.crnk.gen.runtime.GeneratorTrigger; import io.crnk.gen.typescript.TSGeneratorConfiguration; @@ -14,7 +15,7 @@ public class TSGeneratorRuntimeContextImpl implements GeneratorTrigger, TSGenera private TSGeneratorConfiguration config; @Override - public void generate(Object meta) { + public void generate(Object meta) throws IOException { MetaLookup metaLookup = (MetaLookup) meta; TSGenerator gen = new TSGenerator(outputDir, metaLookup, config); diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSContainerElement.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSContainerElement.java index ebe824872..0b84080f3 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSContainerElement.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSContainerElement.java @@ -4,9 +4,7 @@ public interface TSContainerElement extends TSElement { - public List getElements(); + List getElements(); - public TSNamedElement getElement(String name); - - public void addElement(TSElement element); + void addElement(TSElement element); } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSContainerElementBase.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSContainerElementBase.java index 3e33049e1..eb0d2f888 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSContainerElementBase.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSContainerElementBase.java @@ -12,19 +12,6 @@ public List getElements() { return elements; } - @Override - public TSNamedElement getElement(String name) { - for (TSElement element : elements) { - if (element instanceof TSNamedElement) { - TSNamedElement named = (TSNamedElement) element; - if (name.equals(named.getName())) { - return named; - } - } - } - return null; - } - @Override public void addElement(TSElement element) { elements.add(element); diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSEnumType.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSEnumType.java index e3bd43946..f86616240 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSEnumType.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSEnumType.java @@ -22,10 +22,6 @@ public List getLiterals() { return literals; } - public void setLiterals(List literal) { - this.literals = literal; - } - @Override public void accept(TSVisitor visitor) { visitor.visit(this); diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSField.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSField.java index a0df633e8..a7fd8e89d 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSField.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSField.java @@ -17,7 +17,6 @@ public void setInitializer(String initializer) { this.initializer = initializer; } - @Override public boolean isField() { return true; diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSFunction.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSFunction.java index fd9ede563..4a36fb333 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSFunction.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSFunction.java @@ -20,18 +20,10 @@ public List getStatements() { return statements; } - public void setStatements(List statements) { - this.statements = statements; - } - public List getParameters() { return parameters; } - public void setParameters(List parameters) { - this.parameters = parameters; - } - public void setExported(boolean exported) { this.exported = exported; } @@ -44,4 +36,14 @@ public boolean isExported() { public void addParameter(TSParameter parameter) { parameters.add(parameter); } + + @Override + public boolean isField() { + return false; + } + + @Override + public TSField asField() { + throw new UnsupportedOperationException(); + } } \ No newline at end of file diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSMember.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSMember.java index e883a253c..7d24b0c8a 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSMember.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSMember.java @@ -32,18 +32,7 @@ public void setNullable(boolean nullable) { this.nullable = nullable; } - public TSType getElementType() { - if (type instanceof TSArrayType) { - return ((TSArrayType) type).getElementType(); - } - return type; - } + public abstract boolean isField(); - public boolean isField() { - return false; - } - - public TSField asField() { - throw new UnsupportedOperationException(); - } + public abstract TSField asField(); } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSObjectType.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSObjectType.java index b80ee1e1c..1a5ef1f55 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSObjectType.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSObjectType.java @@ -1,11 +1,6 @@ package io.crnk.gen.typescript.model; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; public abstract class TSObjectType extends TSTypeBase implements TSExportedElement { @@ -59,18 +54,10 @@ public List getMembers() { return members; } - public void setDeclaredMembers(List members) { - this.declaredMembers = members; - } - public Set getImplementedInterfaces() { return implementedInterfaces; } - public void setImplementedInterfaces(Set implementedInterfaces) { - this.implementedInterfaces = implementedInterfaces; - } - @Override public boolean isExported() { return exported; diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSParameter.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSParameter.java index 0dd7920db..001a382a8 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSParameter.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/TSParameter.java @@ -14,9 +14,6 @@ public TSParameter(String name, TSType type, boolean nullable) { this.nullable = nullable; } - public void setName(String name) { - this.name = name; - } public String getName() { return name; @@ -26,18 +23,10 @@ public TSType getType() { return type; } - public void setType(TSType type) { - this.type = type; - } - public boolean isNullable() { return nullable; } - public void setNullable(boolean nullable) { - this.nullable = nullable; - } - public TSType getElementType() { if (type instanceof TSArrayType) { return ((TSArrayType) type).getElementType(); diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/libraries/ExpressionLibrary.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/libraries/ExpressionLibrary.java index 79b326d9f..72e372789 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/libraries/ExpressionLibrary.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/model/libraries/ExpressionLibrary.java @@ -55,7 +55,7 @@ public class ExpressionLibrary { private ExpressionLibrary() { } - public static TSType getExpression(String primitiveName) { + public static TSType getPrimitiveExpression(String primitiveName) { if (TSPrimitiveType.STRING.getName().equalsIgnoreCase(primitiveName)) { return STRING_EXPRESSION; } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSExpressionObjectProcessor.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSExpressionObjectProcessor.java index 7aff4559a..b89e6f1b4 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSExpressionObjectProcessor.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSExpressionObjectProcessor.java @@ -5,7 +5,6 @@ import io.crnk.gen.typescript.model.libraries.ExpressionLibrary; import io.crnk.gen.typescript.model.libraries.NgrxJsonApiLibrary; import io.crnk.gen.typescript.transform.TSMetaDataObjectTransformation; -import io.crnk.meta.model.MetaElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -95,7 +94,7 @@ private void setupField(TSInterfaceType interfaceType, TSField qField, TSField f if (fieldType instanceof TSPrimitiveType) { TSPrimitiveType primitiveFieldType = (TSPrimitiveType) fieldType; String primitiveName = TypescriptUtils.firstToUpper(primitiveFieldType.getName()); - qField.setType(ExpressionLibrary.getExpression(primitiveName)); + qField.setType(ExpressionLibrary.getPrimitiveExpression(primitiveName)); qField.setInitializer(setupPrimitiveField(primitiveName, field)); } else if (fieldType instanceof TSEnumType) { qField.setType(ExpressionLibrary.STRING_EXPRESSION); diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSImportProcessor.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSImportProcessor.java index 333db2e9d..11b80b647 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSImportProcessor.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSImportProcessor.java @@ -1,18 +1,14 @@ package io.crnk.gen.typescript.processor; +import io.crnk.core.engine.internal.utils.PreconditionUtil; +import io.crnk.gen.typescript.model.*; +import io.crnk.gen.typescript.writer.TSTypeReferenceResolver; + import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; -import io.crnk.gen.typescript.model.TSElement; -import io.crnk.gen.typescript.model.TSImport; -import io.crnk.gen.typescript.model.TSParameterizedType; -import io.crnk.gen.typescript.model.TSPrimitiveType; -import io.crnk.gen.typescript.model.TSSource; -import io.crnk.gen.typescript.model.TSType; -import io.crnk.gen.typescript.writer.TSTypeReferenceResolver; - /** * Computes Typescript import statements for the given source model. */ @@ -53,14 +49,7 @@ private static void processType(TSSource source, TSType type) { TSSource refSource = getSource(type); // no need for inclusions of primitive types and within same file - if (type instanceof TSParameterizedType) { - TSParameterizedType paramType = (TSParameterizedType) type; - processType(source, paramType.getBaseType()); - for (TSType param : paramType.getParameters()) { - processType(source, param); - } - } - else if (!(type instanceof TSPrimitiveType || source == refSource)) { + if (!(type instanceof TSPrimitiveType || source == refSource)) { addImport(refSource, source, type); } } @@ -77,14 +66,10 @@ private static void addImport(TSSource refSource, TSSource source, TSType type) } private static String computeImportPath(TSSource refSource, TSSource source) { - if (refSource == null) { - throw new NullPointerException(); - } StringBuilder pathBuilder = new StringBuilder(); if (!source.getNpmPackage().equals(refSource.getNpmPackage())) { appendThirdPartyImport(pathBuilder, refSource); - } - else { + } else { appendRelativeImport(pathBuilder, refSource, source); } return pathBuilder.toString(); @@ -110,8 +95,7 @@ private static void appendChildPath(StringBuilder pathBuilder, String[] refDirs, private static void appendParentPath(StringBuilder pathBuilder, String[] srcDirs, int shared) { if (shared == srcDirs.length) { pathBuilder.append("./"); - } - else { + } else { for (int i = shared; i < srcDirs.length; i++) { pathBuilder.append("../"); } @@ -131,8 +115,7 @@ private static int computeSharedPrefix(String[] srcDirs, String[] refDirs) { for (int i = 0; i < Math.min(srcDirs.length, refDirs.length); i++) { if (srcDirs[i].equals(refDirs[i])) { n++; - } - else { + } else { break; } } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSPackageJsonProcessor.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSPackageJsonProcessor.java deleted file mode 100644 index 6530e8348..000000000 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/processor/TSPackageJsonProcessor.java +++ /dev/null @@ -1,49 +0,0 @@ -package io.crnk.gen.typescript.processor; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import io.crnk.gen.typescript.model.TSExport; -import io.crnk.gen.typescript.model.TSSource; - - -/** - * Adds a package.json file to the generated project - */ -public class TSPackageJsonProcessor implements TSSourceProcessor { - - @Override - public Set process(Set sources) { - Set newSources = new HashSet<>(sources); - - Map> directoryIndex = new HashMap<>(); - for (TSSource fileSource : sources) { - String dir = fileSource.getDirectory(); - - if (!directoryIndex.containsKey(dir)) { - directoryIndex.put(dir, new ArrayList()); - } - directoryIndex.get(dir).add(fileSource); - } - - for (Map.Entry> entry : directoryIndex.entrySet()) { - TSSource indexSource = new TSSource(); - indexSource.setName("index"); - indexSource.setNpmPackage(entry.getValue().get(0).getNpmPackage()); - indexSource.setDirectory(entry.getKey()); - - for (TSSource sourceFile : entry.getValue()) { - TSExport exportElement = new TSExport(); - exportElement.setAny(true); - exportElement.setPath("./" + sourceFile.getName()); - indexSource.getExports().add(exportElement); - } - newSources.add(indexSource); - } - return newSources; - } -} \ No newline at end of file diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/transform/TSMetaResourceRepositoryTransformation.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/transform/TSMetaResourceRepositoryTransformation.java index 9970a2925..2e5716e67 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/transform/TSMetaResourceRepositoryTransformation.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/transform/TSMetaResourceRepositoryTransformation.java @@ -12,8 +12,8 @@ import io.crnk.gen.typescript.model.libraries.NgrxJsonApiLibrary; import io.crnk.meta.model.MetaDataObject; import io.crnk.meta.model.MetaElement; -import io.crnk.meta.model.resource.MetaResource; import io.crnk.meta.model.resource.MetaResourceRepository; +import io.crnk.meta.model.resource.MetaResource; /** * Transforms MetaResourceRepository elements to (One/Many)QueryResult interfaces to gain type-safe access diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/writer/TSTypeReferenceResolver.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/writer/TSTypeReferenceResolver.java index d63ea4052..103501dcd 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/writer/TSTypeReferenceResolver.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/writer/TSTypeReferenceResolver.java @@ -1,22 +1,10 @@ package io.crnk.gen.typescript.writer; +import io.crnk.gen.typescript.model.*; + import java.util.HashSet; import java.util.Set; -import io.crnk.gen.typescript.model.TSArrayType; -import io.crnk.gen.typescript.model.TSClassType; -import io.crnk.gen.typescript.model.TSElement; -import io.crnk.gen.typescript.model.TSField; -import io.crnk.gen.typescript.model.TSIndexSignature; -import io.crnk.gen.typescript.model.TSInterfaceType; -import io.crnk.gen.typescript.model.TSMember; -import io.crnk.gen.typescript.model.TSModule; -import io.crnk.gen.typescript.model.TSObjectType; -import io.crnk.gen.typescript.model.TSParameterizedType; -import io.crnk.gen.typescript.model.TSSource; -import io.crnk.gen.typescript.model.TSType; -import io.crnk.gen.typescript.model.TSVisitorBase; - public class TSTypeReferenceResolver extends TSVisitorBase { private Set types = new HashSet<>(); @@ -74,20 +62,15 @@ public void visit(TSParameterizedType parameterizedType) { } private void addReference(TSType type) { - if (type == null) { - throw new NullPointerException(); - } if (type.getParent() instanceof TSModule) { return; } if (type instanceof TSParameterizedType) { type.accept(this); - } - else if (type instanceof TSArrayType) { + } else if (type instanceof TSArrayType) { addReference(((TSArrayType) type).getElementType()); - } - else { + } else { types.add(type); } } diff --git a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/writer/TSWriter.java b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/writer/TSWriter.java index 005cf4f50..f1dacaac2 100644 --- a/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/writer/TSWriter.java +++ b/crnk-gen-typescript/src/main/java/io/crnk/gen/typescript/writer/TSWriter.java @@ -1,33 +1,8 @@ package io.crnk.gen.typescript.writer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Set; - -import io.crnk.gen.typescript.model.TSAny; -import io.crnk.gen.typescript.model.TSArrayType; -import io.crnk.gen.typescript.model.TSClassType; -import io.crnk.gen.typescript.model.TSElement; -import io.crnk.gen.typescript.model.TSEnumLiteral; -import io.crnk.gen.typescript.model.TSEnumType; -import io.crnk.gen.typescript.model.TSExport; -import io.crnk.gen.typescript.model.TSExportedElement; -import io.crnk.gen.typescript.model.TSField; -import io.crnk.gen.typescript.model.TSFunction; -import io.crnk.gen.typescript.model.TSImport; -import io.crnk.gen.typescript.model.TSIndexSignature; -import io.crnk.gen.typescript.model.TSInterfaceType; -import io.crnk.gen.typescript.model.TSMember; -import io.crnk.gen.typescript.model.TSModule; -import io.crnk.gen.typescript.model.TSParameter; -import io.crnk.gen.typescript.model.TSParameterizedType; -import io.crnk.gen.typescript.model.TSPrimitiveType; -import io.crnk.gen.typescript.model.TSSource; -import io.crnk.gen.typescript.model.TSType; -import io.crnk.gen.typescript.model.TSVisitor; +import io.crnk.gen.typescript.model.*; + +import java.util.*; public class TSWriter implements TSVisitor { @@ -123,7 +98,7 @@ private void appendExported(TSExportedElement element) { } } - public void visitReference(TSModule module) { + private void visitReference(TSModule module) { if (module.getParent() instanceof TSModule) { visitReference((TSModule) module.getParent()); builder.append("."); @@ -144,8 +119,7 @@ public void visitReference(TSType type) { visitReference(parameters.get(i)); } builder.append(">"); - } - else { + } else { if (type.getParent() instanceof TSModule) { visitReference((TSModule) type.getParent()); builder.append("."); @@ -306,8 +280,7 @@ public void visit(TSExport exportElement) { builder.append("export "); if (exportElement.getAny()) { builder.append("*"); - } - else { + } else { builder.append("{"); Iterator iterator = exportElement.getTypeNames().iterator(); while (iterator.hasNext()) { diff --git a/crnk-gen-typescript/src/main/resources/META-INF/gradle-plugins/crnk-gen-typescript.properties b/crnk-gen-typescript/src/main/resources/META-INF/gradle-plugins/crnk-gen-typescript.properties deleted file mode 100644 index e6e103bb7..000000000 --- a/crnk-gen-typescript/src/main/resources/META-INF/gradle-plugins/crnk-gen-typescript.properties +++ /dev/null @@ -1,2 +0,0 @@ -implementation-class=io.crnk.gen.typescript.TSGeneratorPlugin - diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/GenerateTypescriptTaskTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/GenerateTypescriptTaskTest.java index 155640c94..05969d570 100644 --- a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/GenerateTypescriptTaskTest.java +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/GenerateTypescriptTaskTest.java @@ -1,20 +1,23 @@ package io.crnk.gen.typescript; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.charset.Charset; -import javax.naming.Context; - +import io.crnk.gen.typescript.runtime.DummyInitialContextFactory; import org.apache.commons.io.IOUtils; import org.gradle.api.Project; import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.Copy; import org.gradle.internal.impldep.org.junit.Assert; import org.gradle.testfixtures.ProjectBuilder; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import javax.naming.Context; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.Charset; + public class GenerateTypescriptTaskTest { @Rule @@ -34,28 +37,42 @@ public void testWithoutExpressions() throws IOException { private void test(boolean expressions) throws IOException { // Deltaspike sometimes really wants to have a retarded JNDI context - System.setProperty(Context.INITIAL_CONTEXT_FACTORY, io.crnk.gen.typescript.DummyInitialContextFactory.class.getName()); + System.setProperty(Context.INITIAL_CONTEXT_FACTORY, DummyInitialContextFactory.class.getName()); testProjectDir.newFolder("src", "main", "java"); outputDir = testProjectDir.getRoot(); + outputDir.mkdirs(); + + File npmrcFile = new File(outputDir, ".npmrc"); + FileWriter npmrcWriter = new FileWriter(npmrcFile); + npmrcWriter.write(""); + npmrcWriter.close(); Project project = ProjectBuilder.builder().withName("crnk-gen-typescript-test").withProjectDir(outputDir).build(); project.setVersion("0.0.1"); + project.getPluginManager().apply("com.moowork.node"); project.getPluginManager().apply(JavaPlugin.class); project.getPluginManager().apply(TSGeneratorPlugin.class); TSGeneratorConfiguration config = project.getExtensions().getByType(TSGeneratorConfiguration.class); + config.setExpressionLibrary("@crnk/ngrx"); config.setGenerateExpressions(expressions); - config.setNpmPackageName("@crnk/gen-typescript-test"); - config.getNpmPackageMapping().put("io.crnk.test.mock.models", "@crnk/gen-typescript-test"); - config.getNpmPackageMapping().put("io.crnk.meta", "@crnk/meta"); - config.setNpmPackageVersion("0.0.1"); + String testPackage = "@crnk/gen-typescript-test"; + config.getRuntime().setConfiguration("test"); + config.getNpm().setPackageName(testPackage); + config.getNpm().setGitRepository("someThing"); + config.getNpm().getPackageMapping().put("io.crnk.test.mock.models", testPackage); + config.getNpm().getPackageMapping().put("io.crnk.meta", testPackage); + config.getNpm().setPackageVersion("0.0.1"); GenerateTypescriptTask task = (GenerateTypescriptTask) project.getTasks().getByName("generateTypescript"); task.runGeneration(); + Copy processTask = (Copy) project.getTasks().getByName("processTypescript"); + processTask.execute(); + assertExists("build/generated/source/typescript/package.json"); assertExists("build/generated/source/typescript/src/index.ts"); assertExists("build/generated/source/typescript/src/project.ts"); @@ -65,6 +82,16 @@ private void test(boolean expressions) throws IOException { assertNotExists("build/generated/source/typescript/src/task.links.ts"); assertNotExists("build/generated/source/typescript/src/task.meta.ts"); + assertExists("build/generated/source/typescript/src/meta.key.ts"); + assertExists("build/generated/source/typescript/src/meta.element.ts"); + assertExists("build/generated/source/typescript/src/meta.data.object.ts"); + + // check whether source copied to compile directory for proper source bundling + assertExists("build/npm_compile/.npmrc"); + assertExists("build/npm_compile/package.json"); + assertExists("build/npm_compile/src/index.ts"); + assertExists("build/npm_compile/src/meta.element.ts"); + Charset utf8 = Charset.forName("UTF8"); String expectedSourceFileName = expressions ? "expected_schedule_with_expressions.ts" : "expected_schedule_without_expressions.ts"; diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/PublishTypescriptTaskTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/PublishTypescriptTaskTest.java new file mode 100644 index 000000000..1ba34b49b --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/PublishTypescriptTaskTest.java @@ -0,0 +1,41 @@ +package io.crnk.gen.typescript; + +import io.crnk.gen.typescript.runtime.DummyInitialContextFactory; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import javax.naming.Context; +import java.io.File; +import java.io.IOException; + +public class PublishTypescriptTaskTest { + + @Rule + public TemporaryFolder testProjectDir = new TemporaryFolder(); + + + @Test + public void checkTaskProperties() throws IOException { + System.setProperty(Context.INITIAL_CONTEXT_FACTORY, DummyInitialContextFactory.class.getName()); + + testProjectDir.newFolder("src", "main", "java"); + + File outputDir = testProjectDir.getRoot(); + + Project project = ProjectBuilder.builder().withName("crnk-gen-typescript-test").withProjectDir(outputDir).build(); + project.setVersion("0.0.1"); + project.getPluginManager().apply(JavaPlugin.class); + project.getPluginManager().apply(TSGeneratorPlugin.class); + + PublishTypescriptStubsTask task = (PublishTypescriptStubsTask) project.getTasks().getByName("publishTypescript"); + Assert.assertEquals("publish", task.getGroup()); + Assert.assertNotNull(task.getDescription()); + Assert.assertFalse(task.getInputs().getFiles().isEmpty()); + Assert.assertFalse(task.getOutputs().getFiles().isEmpty()); + } +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/RuntimeClassoaderFactoryTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/RuntimeClassoaderFactoryTest.java new file mode 100644 index 000000000..d7c5b5303 --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/RuntimeClassoaderFactoryTest.java @@ -0,0 +1,112 @@ +package io.crnk.gen.typescript; + +import io.crnk.core.engine.document.Resource; +import io.crnk.gen.runtime.RuntimeClassLoaderFactory; +import io.crnk.gen.typescript.model.TSClassType; +import io.crnk.gen.typescript.model.TSImport; +import io.crnk.gen.typescript.model.TSMember; +import io.crnk.gen.typescript.runtime.DummyInitialContextFactory; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import javax.naming.Context; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; + +public class RuntimeClassoaderFactoryTest { + + @Rule + public TemporaryFolder testProjectDir = new TemporaryFolder(); + + private RuntimeClassLoaderFactory.SharedClassLoader sharedClassLoader; + + private URLClassLoader classLoader; + private RuntimeClassLoaderFactory factory; + + + @Before + public void setup() throws IOException { + // Deltaspike sometimes really wants to have a retarded JNDI context + System.setProperty(Context.INITIAL_CONTEXT_FACTORY, DummyInitialContextFactory.class.getName()); + + testProjectDir.newFolder("src", "main", "java"); + + File outputDir = testProjectDir.getRoot(); + + Project project = ProjectBuilder.builder().withName("crnk-gen-typescript-test").withProjectDir(outputDir).build(); + project.setVersion("0.0.1"); + + project.getPluginManager().apply("com.moowork.node"); + project.getPluginManager().apply(JavaPlugin.class); + project.getPluginManager().apply(TSGeneratorPlugin.class); + + TSGeneratorConfiguration config = project.getExtensions().getByType(TSGeneratorConfiguration.class); + config.getRuntime().setConfiguration("test"); + + factory = new RuntimeClassLoaderFactory(project); + ClassLoader parentClassLoader = getClass().getClassLoader(); + classLoader = factory.createClassLoader(parentClassLoader); + sharedClassLoader = (RuntimeClassLoaderFactory.SharedClassLoader) classLoader.getParent(); + + } + + @Test + public void checkSharedClassAccessible() throws IOException, ClassNotFoundException { + sharedClassLoader.putSharedClass("test", String.class); + + Assert.assertSame(String.class, classLoader.loadClass("test")); + Assert.assertSame(String.class, sharedClassLoader.loadClass("test")); + } + + @Test + public void checkBootstrapClassesAccessible() throws IOException, ClassNotFoundException { + Assert.assertSame(String.class, classLoader.loadClass(String.class.getName())); + Assert.assertSame(Object.class, classLoader.loadClass(Object.class.getName())); + } + + + @Test + public void checkTypescriptModelExposed() throws IOException, ClassNotFoundException { + Assert.assertSame(TSMember.class, classLoader.loadClass(TSMember.class.getName())); + Assert.assertSame(TSClassType.class, classLoader.loadClass(TSClassType.class.getName())); + Assert.assertSame(TSImport.class, classLoader.loadClass(TSImport.class.getName())); + } + + @Test + public void defaultLogbackTestProvidedFromParentClassloader() throws IOException, ClassNotFoundException { + Assert.assertNotNull(classLoader.getResource("logback-test.xml")); + } + + @Test(expected = IllegalStateException.class) + public void defaultLogbackTestNotProvidedWithInvalidParentClassLoader() throws IOException, ClassNotFoundException { + ClassLoader bootstrapClassLoader = ClassLoader.getSystemClassLoader().getParent(); + classLoader = factory.createClassLoader(bootstrapClassLoader); + Assert.assertNull(bootstrapClassLoader.getResource("logback-test.xml")); + Assert.assertNull(classLoader.getResource("logback-test.xml")); + } + + @Test + public void classLoaderExposesTestConfiguration() throws IOException, ClassNotFoundException { + Assert.assertNotEquals(0, classLoader.getURLs().length); + + // Typescript model should be accessible + for (URL url : classLoader.getURLs()) { + System.out.println(url); + } + } + + @Test(expected = ClassNotFoundException.class) + public void classLoaderIsolatesCallerEnvironment() throws IOException, ClassNotFoundException { + // methods from context classpath should not be accessible + classLoader.loadClass(Resource.class.getName()); + } + +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/TSGeneratorConfigurationTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/TSGeneratorConfigurationTest.java new file mode 100644 index 000000000..fbdaa6c6a --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/TSGeneratorConfigurationTest.java @@ -0,0 +1,69 @@ +package io.crnk.gen.typescript; + +import groovy.lang.Closure; +import org.gradle.api.Project; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class TSGeneratorConfigurationTest { + + @Test + public void test() { + Project project = Mockito.mock(Project.class); + TSGeneratorConfiguration config = new TSGeneratorConfiguration(project); + + Set includes = new HashSet<>(); + config.setIncludes(includes); + Assert.assertSame(includes, config.getIncludes()); + + Set excludes = new HashSet<>(); + config.setExcludes(excludes); + Assert.assertSame(excludes, config.getExcludes()); + + Assert.assertFalse(config.getGenerateExpressions()); + config.setGenerateExpressions(true); + Assert.assertTrue(config.getGenerateExpressions()); + + Assert.assertEquals(null, config.getExpressionLibrary()); + config.setExpressionLibrary("@crnk/test"); + Assert.assertEquals("@crnk/test", config.getExpressionLibrary()); + + Assert.assertEquals("src", config.getSourceDirectoryName()); + config.setSourceDirectoryName("testDir"); + Assert.assertEquals("testDir", config.getSourceDirectoryName()); + + Assert.assertEquals("UNLICENSED", config.getNpm().getLicense()); + config.getNpm().setLicense("someLicense"); + Assert.assertEquals("someLicense", config.getNpm().getLicense()); + + Assert.assertEquals(null, config.getNpm().getGitRepository()); + config.getNpm().setGitRepository("git"); + Assert.assertEquals("git", config.getNpm().getGitRepository()); + + Assert.assertEquals(null, config.getNpm().getDescription()); + config.getNpm().setDescription("desc"); + Assert.assertEquals("desc", config.getNpm().getDescription()); + + Map packageMapping = new HashMap(); + config.getNpm().setPackageMapping(packageMapping); + Assert.assertSame(packageMapping, config.getNpm().getPackageMapping()); + + Map peerDep = new HashMap(); + config.getNpm().setPeerDependencies(peerDep); + Assert.assertSame(peerDep, config.getNpm().getPeerDependencies()); + + Map devDep = new HashMap(); + config.getNpm().setDevDependencies(devDep); + Assert.assertSame(devDep, config.getNpm().getDevDependencies()); + + Closure closure = Mockito.mock(Closure.class); + config.npm(closure); + Mockito.verify(project, Mockito.times(1)).configure(Mockito.anyObject(), Mockito.eq(closure)); + } +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/internal/TSGeneratorTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/internal/TSGeneratorTest.java new file mode 100644 index 000000000..2de419684 --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/internal/TSGeneratorTest.java @@ -0,0 +1,71 @@ +package io.crnk.gen.typescript.internal; + +import io.crnk.core.boot.CrnkBoot; +import io.crnk.core.module.discovery.EmptyServiceDiscovery; +import io.crnk.gen.typescript.TSGeneratorConfiguration; +import io.crnk.gen.typescript.transform.TSMetaTransformationContext; +import io.crnk.gen.typescript.transform.TSMetaTransformationOptions; +import io.crnk.meta.MetaModule; +import io.crnk.meta.model.MetaElement; +import io.crnk.meta.provider.resource.ResourceMetaProvider; +import org.gradle.api.Project; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; + +import java.io.File; + +public class TSGeneratorTest { + + + @Rule + public TemporaryFolder testProjectDir = new TemporaryFolder(); + + private TSGenerator generator; + + @Before + public void setup() { + Project project = Mockito.mock(Project.class); + TSGeneratorConfiguration config = new TSGeneratorConfiguration(project); + File outputDir = testProjectDir.getRoot(); + + MetaModule metaModule = MetaModule.create(); + metaModule.addMetaProvider(new ResourceMetaProvider()); + + CrnkBoot boot = new CrnkBoot(); + boot.setServiceDiscovery(new EmptyServiceDiscovery()); + boot.addModule(metaModule); + boot.boot(); + + generator = new TSGenerator(outputDir, metaModule.getLookup(), config); + } + + @Test(expected = UnsupportedOperationException.class) + public void throwExceptionWhenMetaElementNotMappedToNpmPackage() { + TSMetaTransformationContext transformationContext = generator.createMetaTransformationContext(); + MetaElement metaElement = Mockito.mock(MetaElement.class); + metaElement.setId("does.not.exist"); + transformationContext.getNpmPackage(metaElement); + } + + @Test(expected = UnsupportedOperationException.class) + public void throwExceptionWhenMetaElementNotMappedToDirectory() { + TSMetaTransformationContext transformationContext = generator.createMetaTransformationContext(); + MetaElement metaElement = Mockito.mock(MetaElement.class); + metaElement.setId("does.not.exist"); + transformationContext.getDirectory(metaElement); + } + + @Test(expected = IllegalStateException.class) + public void throwExceptionWhenTransformingUnknownMetaElement() { + MetaElement metaElement = Mockito.mock(MetaElement.class); + metaElement.setId("does.not.exist"); + + TSMetaTransformationOptions options = Mockito.mock(TSMetaTransformationOptions.class); + generator.transform(metaElement, options); + } + + +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/ExpressionLibraryTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/ExpressionLibraryTest.java new file mode 100644 index 000000000..258f04888 --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/ExpressionLibraryTest.java @@ -0,0 +1,34 @@ +package io.crnk.gen.typescript.model; + +import io.crnk.gen.typescript.model.libraries.ExpressionLibrary; +import io.crnk.test.mock.ClassTestUtils; +import org.junit.Assert; +import org.junit.Test; + +public class ExpressionLibraryTest { + + @Test + public void checkHasPrivateConstructor() { + ClassTestUtils.assertPrivateConstructor(ExpressionLibrary.class); + } + + @Test + public void checkGetStringExpression() { + Assert.assertSame(ExpressionLibrary.STRING_EXPRESSION, ExpressionLibrary.getPrimitiveExpression("string")); + } + + @Test + public void checkGetNumberExpression() { + Assert.assertSame(ExpressionLibrary.NUMBER_EXPRESSION, ExpressionLibrary.getPrimitiveExpression("number")); + } + + @Test + public void checkGetBooleanExpression() { + Assert.assertSame(ExpressionLibrary.BOOLEAN_EXPRESSION, ExpressionLibrary.getPrimitiveExpression("boolean")); + } + + @Test(expected = IllegalStateException.class) + public void throwExceptionOnUnknownPrimitiveException() { + ExpressionLibrary.getPrimitiveExpression("doesNotExist"); + } +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/NgrxJsonApiLibraryTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/NgrxJsonApiLibraryTest.java new file mode 100644 index 000000000..9866d7562 --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/NgrxJsonApiLibraryTest.java @@ -0,0 +1,13 @@ +package io.crnk.gen.typescript.model; + +import io.crnk.gen.typescript.model.libraries.NgrxJsonApiLibrary; +import io.crnk.test.mock.ClassTestUtils; +import org.junit.Test; + +public class NgrxJsonApiLibraryTest { + + @Test + public void hasPrivateConstructor() { + ClassTestUtils.assertPrivateConstructor(NgrxJsonApiLibrary.class); + } +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSFunctionTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSFunctionTest.java new file mode 100644 index 000000000..0b736d4f5 --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSFunctionTest.java @@ -0,0 +1,19 @@ +package io.crnk.gen.typescript.model; + +import org.junit.Assert; +import org.junit.Test; + +public class TSFunctionTest { + + @Test + public void notAField() { + TSFunction function = new TSFunction(); + Assert.assertFalse(function.isField()); + } + + @Test(expected = UnsupportedOperationException.class) + public void cannotCastToField() { + TSFunction function = new TSFunction(); + function.asField(); + } +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSVisiterBaseTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSVisiterBaseTest.java index 05f12ebc4..913b74fd1 100644 --- a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSVisiterBaseTest.java +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSVisiterBaseTest.java @@ -1,6 +1,7 @@ package io.crnk.gen.typescript.model; import org.junit.Test; +import org.mockito.Mockito; public class TSVisiterBaseTest { @@ -20,4 +21,27 @@ public void checkDoesNothing() { base.visit((TSExport) null); base.visit((TSParameterizedType) null); } + + @Test + public void visitInterfaceShouldVisitMembers() { + TSMember member = Mockito.mock(TSMember.class); + TSInterfaceType interfaceType = new TSInterfaceType(); + interfaceType.addDeclaredMember(member); + + TSVisitorBase base = new TSVisitorBase(); + base.visit(interfaceType); + Mockito.verify(member, Mockito.times(1)).accept(Mockito.eq(base)); + } + + + @Test + public void visitClassShouldVisitMembers() { + TSMember member = Mockito.mock(TSMember.class); + TSClassType classType = new TSClassType(); + classType.addDeclaredMember(member); + + TSVisitorBase base = new TSVisitorBase(); + base.visit(classType); + Mockito.verify(member, Mockito.times(1)).accept(Mockito.eq(base)); + } } diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSWriterTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSWriterTest.java index 326655924..3ea115bb8 100644 --- a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSWriterTest.java +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/model/TSWriterTest.java @@ -5,6 +5,7 @@ import org.gradle.internal.impldep.org.testng.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; public class TSWriterTest { @@ -49,6 +50,26 @@ public void writeAnyType() { Assert.assertEquals("any", writer.toString()); } + @Test + public void writeClassWithImplements() { + TSInterfaceType interfaceType = new TSInterfaceType(); + interfaceType.setName("SomeInterface"); + + TSClassType classType = new TSClassType(); + classType.setName("SomeClass"); + classType.getImplementedInterfaces().add(interfaceType); + + classType.accept(writer); + Assert.assertEquals("\nclass SomeClass implements SomeInterface {\n}", writer.toString()); + } + + @Test(expected = UnsupportedOperationException.class) + public void writeMemberNotSupported() { + // must visit subtypes directly + TSMember member = Mockito.mock(TSMember.class); + writer.visit(member); + } + @Test public void writeClassWithIndex() { TSIndexSignature indexSignature = new TSIndexSignature(); diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/processor/TSImportProcessorTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/processor/TSImportProcessorTest.java new file mode 100644 index 000000000..ce609d7f5 --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/processor/TSImportProcessorTest.java @@ -0,0 +1,124 @@ +package io.crnk.gen.typescript.processor; + +import io.crnk.gen.typescript.model.*; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +public class TSImportProcessorTest { + + private HashSet sources; + + private TSSource interfaceSource; + + private TSClassType classType; + + private TSSource classSource; + + private TSInterfaceType interfaceType; + + private TSImportProcessor processor; + + @Before + public void setup() { + processor = new TSImportProcessor(); + + interfaceType = new TSInterfaceType(); + interfaceType.setName("SomeInterface"); + interfaceSource = new TSSource(); + interfaceSource.addElement(interfaceType); + interfaceSource.setNpmPackage("@crnk/test"); + interfaceSource.setDirectory("someDir"); + interfaceSource.setName("some-interface"); + + classType = new TSClassType(); + classType.setName("SomeClass"); + classType.getImplementedInterfaces().add(interfaceType); + classSource = new TSSource(); + classSource.setNpmPackage("@crnk/test"); + classSource.setDirectory("someDir"); + classSource.setName("some-class"); + classSource.addElement(classType); + + sources = new HashSet<>(); + sources.add(classSource); + sources.add(interfaceSource); + } + + @Test + public void sameDirectoryImport() { + Set updatedSources = processor.process(sources); + Assert.assertEquals(sources.size(), updatedSources.size()); + + Assert.assertEquals(1, classSource.getImports().size()); + TSImport tsImport = classSource.getImports().get(0); + Assert.assertEquals("./some-interface", tsImport.getPath()); + } + + @Test + public void childDirectoryImport() { + interfaceSource.setDirectory("someDir/child-dir"); + + processor.process(sources); + TSImport tsImport = classSource.getImports().get(0); + Assert.assertEquals("./child-dir/some-interface", tsImport.getPath()); + } + + @Test + public void parentDirectoryImport() { + interfaceSource.setDirectory(null); + + processor.process(sources); + TSImport tsImport = classSource.getImports().get(0); + Assert.assertEquals("../some-interface", tsImport.getPath()); + } + + @Test + public void siblingDirectoryImport() { + interfaceSource.setDirectory("other-dir"); + + processor.process(sources); + TSImport tsImport = classSource.getImports().get(0); + Assert.assertEquals("../other-dir/some-interface", tsImport.getPath()); + } + + + @Test + public void checkArrayElementTypeImported() { + TSField field = new TSField(); + field.setName("someField"); + field.setType(new TSArrayType(interfaceType)); + classType.getImplementedInterfaces().clear(); + classType.addDeclaredMember(field); + + processor.process(sources); + TSImport tsImport = classSource.getImports().get(0); + Assert.assertEquals("./some-interface", tsImport.getPath()); + } + + @Test + public void checkParameterizedTypeImported() { + TSInterfaceType parameterType = new TSInterfaceType(); + parameterType.setName("ParamInterface"); + TSSource paramSource = new TSSource(); + paramSource.addElement(parameterType); + paramSource.setNpmPackage("@crnk/test"); + paramSource.setDirectory("someDir"); + paramSource.setName("some-param"); + sources.add(paramSource); + + TSField field = new TSField(); + field.setName("someField"); + field.setType(new TSParameterizedType(interfaceType, parameterType)); + classType.getImplementedInterfaces().clear(); + classType.addDeclaredMember(field); + + processor.process(sources); + Assert.assertEquals(2, classSource.getImports().size()); + Assert.assertEquals("./some-interface", classSource.getImports().get(0).getPath()); + Assert.assertEquals("./some-param", classSource.getImports().get(1).getPath()); + } +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/DummyInitialContextFactory.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/runtime/DummyInitialContextFactory.java similarity index 98% rename from crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/DummyInitialContextFactory.java rename to crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/runtime/DummyInitialContextFactory.java index 2f4791020..105a07101 100644 --- a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/DummyInitialContextFactory.java +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/runtime/DummyInitialContextFactory.java @@ -1,4 +1,4 @@ -package io.crnk.gen.typescript; +package io.crnk.gen.typescript.runtime; import java.util.Hashtable; diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/runtime/GradleDeltaspikeTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/runtime/GradleDeltaspikeTest.java new file mode 100644 index 000000000..1d0db0f8a --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/runtime/GradleDeltaspikeTest.java @@ -0,0 +1,59 @@ +package io.crnk.gen.typescript.runtime; + +import org.apache.commons.io.IOUtils; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +@Ignore // classpath issues somewhere +public class GradleDeltaspikeTest { + + public TemporaryFolder testFolder = new TemporaryFolder(); + private File root; + + @Test + public void test() throws IOException { + testFolder.create(); + root = testFolder.getRoot(); + root = new File("temp"); + + saveFile("test_build.gradle", "build.gradle"); + saveFile("test_settings.gradle", "settings.gradle"); + saveFile("META-INF/beans.xml", "src/main/resources/META-INF/beans.xml"); + saveFile("TestModuleProducer.java", "src/main/java/io/crnk/gen/typescript/TestModuleProducer.java"); + + GradleRunner runner = GradleRunner.create(); + runner = runner.withPluginClasspath(); + + // List files = Arrays.asList(new File("C:\\projects\\oss\\crnk-framework\\crnk-gen-typescript\\build\\classes\\main")); + + + runner = runner.withProjectDir(root); + + // TODO move to assembleTypescript once ngrx-json-api released + runner = runner.withArguments("generateTypescript"); + + + BuildResult build = runner.build(); + System.out.println(build.getOutput()); + } + + private void saveFile(String resourceName, String targetPath) { + File file = new File(root, targetPath); + file.getParentFile().mkdirs(); + file.delete(); + + try (FileOutputStream out = new FileOutputStream(file); InputStream in = getClass().getClassLoader().getResourceAsStream(resourceName)) { + IOUtils.copy(in, out); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/writer/TSCodeStyleTest.java b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/writer/TSCodeStyleTest.java new file mode 100644 index 000000000..427c040e2 --- /dev/null +++ b/crnk-gen-typescript/src/test/java/io/crnk/gen/typescript/writer/TSCodeStyleTest.java @@ -0,0 +1,18 @@ +package io.crnk.gen.typescript.writer; + +import org.junit.Assert; +import org.junit.Test; + +public class TSCodeStyleTest { + + @Test + public void test() { + TSCodeStyle style = new TSCodeStyle(); + Assert.assertEquals("\t", style.getIndentation()); + Assert.assertEquals("\n", style.getLineSeparator()); + style.setIndentation("a"); + style.setLineSeparator("b"); + Assert.assertEquals("a", style.getIndentation()); + Assert.assertEquals("b", style.getLineSeparator()); + } +} diff --git a/crnk-gen-typescript/src/test/resources/TestModuleProducer.java b/crnk-gen-typescript/src/test/resources/TestModuleProducer.java new file mode 100644 index 000000000..1d0fabccd --- /dev/null +++ b/crnk-gen-typescript/src/test/resources/TestModuleProducer.java @@ -0,0 +1,16 @@ +package io.crnk.gen.typescript; + +import io.crnk.meta.MetaModule; +import io.crnk.meta.provider.resource.ResourceMetaProvider; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class TestModuleProducer { + @ApplicationScoped + public MetaModule createMetaModule() { + MetaModule metaModule = MetaModule.create(); + metaModule.addMetaProvider(new ResourceMetaProvider()); + return metaModule; + } +} diff --git a/crnk-gen-typescript/src/test/resources/expected_schedule_with_expressions.ts b/crnk-gen-typescript/src/test/resources/expected_schedule_with_expressions.ts index b9abaf163..3ef6d00f0 100644 --- a/crnk-gen-typescript/src/test/resources/expected_schedule_with_expressions.ts +++ b/crnk-gen-typescript/src/test/resources/expected_schedule_with_expressions.ts @@ -1,7 +1,7 @@ import {QTask, Task} from './task' import {DefaultPagedLinksInformation} from '@crnk/core/' import {BeanPath, BooleanExpression, StringExpression} from '@crnk/ngrx/binding/expression' -import {QTypedManyResourceRelationship, QTypedOneResourceRelationship} from '@crnk/ngrx/binding/stub' +import {QTypedManyResourceRelationship, QTypedOneResourceRelationship} from '@crnk/ngrx/binding/jsonapi' import {ManyQueryResult, OneQueryResult, ResourceRelationship, StoreResource, TypedManyResourceRelationship, TypedOneResourceRelationship} from 'ngrx-json-api/src/interfaces' export module Schedule { diff --git a/crnk-gen-typescript/src/test/resources/test_build.gradle b/crnk-gen-typescript/src/test/resources/test_build.gradle new file mode 100644 index 000000000..5be474a42 --- /dev/null +++ b/crnk-gen-typescript/src/test/resources/test_build.gradle @@ -0,0 +1,77 @@ +buildscript { + repositories { + maven { + url('https://plugins.gradle.org/m2') + } + } + + dependencies { + classpath "com.moowork.gradle:gradle-node-plugin:1.1.1" + classpath 'org.unbroken-dome.gradle-plugins:gradle-testsets-plugin:1.4.2' + classpath files(new File('../build/classes/main')) + } +} + +repositories { + mavenCentral() + maven { + url('https://plugins.gradle.org/m2') + } +} + +apply plugin: 'java' +apply plugin: 'com.moowork.node' +apply plugin: 'org.unbroken-dome.test-sets' + +testSets { + integrationTest +} + +node { + version = '6.10.3' + npmVersion = '5.1.0' + download = true +} + + +dependencies { + compile files(('../../crnk-core/build/classes/main')) + compile files(('../../crnk-meta/build/classes/main')) + compile files(('../../crnk-cdi/build/classes/main')) + compile 'javax:javaee-api:7.0' + + compile 'org.slf4j:slf4j-api:1.7.13' + compile 'com.fasterxml.jackson.core:jackson-databind:2.8.7' + + integrationTestCompile 'junit:junit:4.12' + integrationTestCompile 'commons-io:commons-io:2.5' + integrationTestCompile 'org.apache.deltaspike.modules:deltaspike-test-control-module-api:1.7.1' + integrationTestCompile 'org.apache.deltaspike.core:deltaspike-core-impl:1.7.1' + integrationTestCompile 'org.apache.deltaspike.modules:deltaspike-test-control-module-api:1.7.1' + integrationTestCompile 'org.apache.deltaspike.modules:deltaspike-test-control-module-impl:1.7.1' + integrationTestCompile 'org.apache.deltaspike.cdictrl:deltaspike-cdictrl-weld:1.7.1' + integrationTestCompile 'org.jboss.weld.se:weld-se-core:2.4.0.Final' +} + +apply plugin: io.crnk.gen.typescript.TSGeneratorPlugin + +typescriptGen{ + npmPackageVersion = '0.0.1' + npmPackageName = '@crnk/test' + npmPackageMapping['ch.crnk.gen.typescript'] = '@crnk/test' + npmDependencies['ngrx-json-api'] = '>=1.1.0' +} + + +// e.g. CDI expects those directories to be equal +sourceSets { + main { + output.resourcesDir = output.classesDir + } + test { + output.resourcesDir = output.classesDir + } +} +jar { + duplicatesStrategy = 'exclude' +} \ No newline at end of file diff --git a/crnk-gen-typescript/src/test/resources/test_settings.gradle b/crnk-gen-typescript/src/test/resources/test_settings.gradle new file mode 100644 index 000000000..2b8309d84 --- /dev/null +++ b/crnk-gen-typescript/src/test/resources/test_settings.gradle @@ -0,0 +1,3 @@ +gradle.startParameter.showStacktrace = org.gradle.api.logging.configuration.ShowStacktrace.ALWAYS + + diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/JpaEntityRepository.java b/crnk-jpa/src/main/java/io/crnk/jpa/JpaEntityRepository.java index 6421c2928..4d99492f4 100644 --- a/crnk-jpa/src/main/java/io/crnk/jpa/JpaEntityRepository.java +++ b/crnk-jpa/src/main/java/io/crnk/jpa/JpaEntityRepository.java @@ -1,31 +1,28 @@ package io.crnk.jpa; -import java.io.Serializable; -import java.util.Arrays; -import java.util.List; -import java.util.Set; -import javax.persistence.EntityManager; - +import io.crnk.core.engine.internal.utils.PreconditionUtil; import io.crnk.core.queryspec.FilterOperator; import io.crnk.core.queryspec.FilterSpec; import io.crnk.core.queryspec.QuerySpec; import io.crnk.core.repository.ResourceRepositoryV2; import io.crnk.core.resource.list.ResourceList; -import io.crnk.core.resource.meta.MetaInformation; import io.crnk.core.resource.meta.HasMoreResourcesMetaInformation; +import io.crnk.core.resource.meta.MetaInformation; import io.crnk.core.resource.meta.PagedMetaInformation; import io.crnk.jpa.internal.JpaRepositoryBase; import io.crnk.jpa.internal.JpaRepositoryUtils; import io.crnk.jpa.internal.JpaRequestContext; import io.crnk.jpa.mapping.JpaMapper; import io.crnk.jpa.meta.MetaEntity; -import io.crnk.jpa.query.ComputedAttributeRegistry; -import io.crnk.jpa.query.JpaQuery; -import io.crnk.jpa.query.JpaQueryExecutor; -import io.crnk.jpa.query.JpaQueryFactory; -import io.crnk.jpa.query.Tuple; +import io.crnk.jpa.query.*; import io.crnk.meta.model.MetaAttribute; +import javax.persistence.EntityManager; +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + /** * Exposes a JPA entity as ResourceRepository. */ @@ -133,9 +130,7 @@ private S saveInternal(S resource) { // fetch again since we may have to fetch tuple data and do DTO mapping QuerySpec querySpec = new QuerySpec(repositoryConfig.getResourceClass()); - if (id == null) { - throw new IllegalStateException("id not available for entity " + id); - } + PreconditionUtil.verify(id != null, "id not available for entity %s", resource); return (S) findOne(id, querySpec); } diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/JpaModule.java b/crnk-jpa/src/main/java/io/crnk/jpa/JpaModule.java index ace7f9334..184c523a5 100644 --- a/crnk-jpa/src/main/java/io/crnk/jpa/JpaModule.java +++ b/crnk-jpa/src/main/java/io/crnk/jpa/JpaModule.java @@ -6,6 +6,8 @@ import io.crnk.core.engine.filter.DocumentFilterChain; import io.crnk.core.engine.filter.DocumentFilterContext; import io.crnk.core.engine.information.resource.ResourceInformationBuilder; +import io.crnk.core.engine.internal.utils.ClassUtils; +import io.crnk.core.engine.internal.utils.ExceptionUtil; import io.crnk.core.engine.internal.utils.PreconditionUtil; import io.crnk.core.engine.transaction.TransactionRunner; import io.crnk.core.module.Module; @@ -226,13 +228,13 @@ public Set> getResourceClasses() { /** * Adds the resource to this module. * - * @param configuration to use + * @param config to use */ public void addRepository(JpaRepositoryConfig config) { checkNotInitialized(); Class resourceClass = config.getResourceClass(); if (repositoryConfigurationMap.containsKey(resourceClass)) { - throw new IllegalArgumentException(resourceClass.getName() + " is already registered"); + throw new IllegalStateException(resourceClass.getName() + " is already registered"); } repositoryConfigurationMap.put(resourceClass, config); } @@ -293,38 +295,36 @@ public void setupModule(ModuleContext context) { } private void addHibernateConstraintViolationExceptionMapper() { - try { - Class.forName("org.hibernate.exception.ConstraintViolationException"); - } catch (ClassNotFoundException e) { // NOSONAR - // may not be available depending on environment - return; - } - - try { - Class mapperClass = Class.forName("io.crnk.jpa.internal.HibernateConstraintViolationExceptionMapper"); - Constructor constructor = mapperClass.getConstructor(); - ExceptionMapper mapper = (ExceptionMapper) constructor.newInstance(); - context.addExceptionMapper(mapper); - } catch (Exception e) { - throw new IllegalStateException(e); + // may not be available depending on environment + if (ClassUtils.existsClass("org.hibernate.exception.ConstraintViolationException")) { + ExceptionUtil.wrapCatchedExceptions(new Callable() { + + @Override + public Object call() throws Exception { + Class mapperClass = Class.forName("io.crnk.jpa.internal.HibernateConstraintViolationExceptionMapper"); + Constructor constructor = mapperClass.getConstructor(); + ExceptionMapper mapper = (ExceptionMapper) constructor.newInstance(); + context.addExceptionMapper(mapper); + return null; + } + }); } } private void addTransactionRollbackExceptionMapper() { - try { - Class.forName("javax.transaction.RollbackException"); - } catch (ClassNotFoundException e) { // NOSONAR - // may not be available depending on environment - return; - } - - try { - Class mapperClass = Class.forName("io.crnk.jpa.internal.TransactionRollbackExceptionMapper"); - Constructor constructor = mapperClass.getConstructor(ModuleContext.class); - ExceptionMapper mapper = (ExceptionMapper) constructor.newInstance(context); - context.addExceptionMapper(mapper); - } catch (Exception e) { - throw new IllegalStateException(e); + // may not be available depending on environment + if (ClassUtils.existsClass("javax.transaction.RollbackException")) { + ExceptionUtil.wrapCatchedExceptions(new Callable() { + + @Override + public Object call() throws Exception { + Class mapperClass = Class.forName("io.crnk.jpa.internal.TransactionRollbackExceptionMapper"); + Constructor constructor = mapperClass.getConstructor(ModuleContext.class); + ExceptionMapper mapper = (ExceptionMapper) constructor.newInstance(context); + context.addExceptionMapper(mapper); + return null; + } + }); } } @@ -351,7 +351,7 @@ private void setupServerRepositories() { } private void setupRepository(JpaRepositoryConfig config) { - if(config.getListMetaClass() == DefaultPagedMetaInformation.class && !isTotalResourceCountUsed()){ + if (config.getListMetaClass() == DefaultPagedMetaInformation.class && !isTotalResourceCountUsed()) { // TODO not that nice... config.setListMetaClass(DefaultHasMoreResourcesMetaInformation.class); } @@ -406,10 +406,9 @@ private void setupRelationshipRepositories(Class resourceClass, boolean mappe if (attrType instanceof MetaEntity) { setupRelationshipRepositoryForEntity(resourceClass, attrType); - } else if (attrType instanceof MetaResource) { - setupRelationshipRepositoryForResource(resourceClass, attr, attrType); } else { - throw new IllegalStateException("unable to process relation: " + attr.getId() + ", neither a entity nor a mapped entity is referenced"); + PreconditionUtil.verify(attrType instanceof MetaResource, "unable to process relation: %s, neither a entity nor a mapped entity is referenced", attr.getId()); + setupRelationshipRepositoryForResource(resourceClass, attr, attrType); } } } @@ -429,9 +428,10 @@ private void setupRelationshipRepositoryForEntity(Class resourceClass, MetaTy private void setupRelationshipRepositoryForResource(Class resourceClass, MetaAttribute attr, MetaType attrType) { Class attrImplClass = attrType.getImplementationClass(); JpaRepositoryConfig attrConfig = getRepositoryConfig(attrImplClass); - if (attrConfig == null || attrConfig.getMapper() == null) { - throw new IllegalStateException("no mapped entity for " + attrType.getName() + " reference by " + attr.getId() + " registered"); - } + + PreconditionUtil.verify(attrConfig != null && attrConfig.getMapper() != null, + "no mapped entity for %s reference by %s registered", attrType.getName(), attr.getId()); + JpaRepositoryConfig targetConfig = getRepositoryConfig(attrImplClass); Class targetResourceClass = targetConfig.getResourceClass(); @@ -469,9 +469,7 @@ public ResourceInformationBuilder getResourceInformationBuilder() { * @param resourceInformationBuilder */ public void setResourceInformationBuilder(ResourceInformationBuilder resourceInformationBuilder) { - if (this.resourceInformationBuilder != null) { - throw new IllegalStateException("already set"); - } + PreconditionUtil.verify(this.resourceInformationBuilder == null, "already set"); this.resourceInformationBuilder = resourceInformationBuilder; } @@ -532,10 +530,6 @@ public MetaLookup getJpaMetaLookup() { return jpaMetaLookup; } - public MetaLookup getResourceMetaLookup() { - return resourceMetaLookup; - } - public boolean isTotalResourceCountUsed() { return totalResourceCountUsed; } diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/JpaRelationshipRepository.java b/crnk-jpa/src/main/java/io/crnk/jpa/JpaRelationshipRepository.java index 31cb30a73..270528c1a 100644 --- a/crnk-jpa/src/main/java/io/crnk/jpa/JpaRelationshipRepository.java +++ b/crnk-jpa/src/main/java/io/crnk/jpa/JpaRelationshipRepository.java @@ -1,37 +1,27 @@ package io.crnk.jpa; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import javax.persistence.EntityManager; - import io.crnk.core.engine.internal.utils.MultivaluedMap; import io.crnk.core.queryspec.QuerySpec; import io.crnk.core.repository.BulkRelationshipRepositoryV2; import io.crnk.core.repository.RelationshipRepositoryV2; import io.crnk.core.resource.list.DefaultResourceList; import io.crnk.core.resource.list.ResourceList; -import io.crnk.core.resource.meta.MetaInformation; import io.crnk.core.resource.meta.HasMoreResourcesMetaInformation; +import io.crnk.core.resource.meta.MetaInformation; import io.crnk.core.resource.meta.PagedMetaInformation; import io.crnk.jpa.internal.JpaRepositoryBase; import io.crnk.jpa.internal.JpaRepositoryUtils; import io.crnk.jpa.internal.JpaRequestContext; -import io.crnk.jpa.mapping.IdentityMapper; import io.crnk.jpa.mapping.JpaMapper; import io.crnk.jpa.meta.MetaEntity; -import io.crnk.jpa.query.ComputedAttributeRegistry; -import io.crnk.jpa.query.JpaQuery; -import io.crnk.jpa.query.JpaQueryExecutor; -import io.crnk.jpa.query.JpaQueryFactory; -import io.crnk.jpa.query.Tuple; +import io.crnk.jpa.query.*; import io.crnk.meta.model.MetaAttribute; import io.crnk.meta.model.MetaType; +import javax.persistence.EntityManager; +import java.io.Serializable; +import java.util.*; + public class JpaRelationshipRepository extends JpaRepositoryBase implements RelationshipRepositoryV2, BulkRelationshipRepositoryV2 { @@ -46,7 +36,7 @@ public class JpaRelationshipRepository sourceResourceClass, this.sourceResourceClass = sourceResourceClass; JpaRepositoryConfig sourceMapping = module.getRepositoryConfig(sourceResourceClass); - if (sourceMapping != null) { - this.sourceEntityClass = sourceMapping.getEntityClass(); - this.sourceMapper = sourceMapping.getMapper(); - } - else { - this.sourceEntityClass = sourceResourceClass; - this.sourceMapper = IdentityMapper.newInstance(); - } + this.sourceEntityClass = sourceMapping.getEntityClass(); + this.sourceMapper = sourceMapping.getMapper(); this.entityMeta = module.getJpaMetaLookup().getMeta(sourceEntityClass, MetaEntity.class); } @@ -82,8 +66,7 @@ public void setRelation(S source, J targetId, String fieldName) { if (target != null && oppositeAttrMeta != null) { if (oppositeAttrMeta.getType().isCollection()) { oppositeAttrMeta.addValue(target, sourceEntity); - } - else { + } else { oppositeAttrMeta.setValue(target, sourceEntity); } em.persist(target); @@ -115,8 +98,7 @@ public void setRelations(S source, Iterable targetIds, String fieldName) { iterator.remove(); if (oppositeAttrMeta.getType().isCollection()) { oppositeAttrMeta.removeValue(prevTarget, sourceEntity); - } - else { + } else { oppositeAttrMeta.setValue(prevTarget, null); } } @@ -127,8 +109,7 @@ public void setRelations(S source, Iterable targetIds, String fieldName) { if (oppositeAttrMeta != null) { if (oppositeAttrMeta.getType().isCollection()) { oppositeAttrMeta.addValue(target, sourceEntity); - } - else { + } else { oppositeAttrMeta.setValue(target, sourceEntity); } em.persist(target); @@ -141,8 +122,7 @@ private Class getElementType(MetaAttribute attrMeta) { MetaType type = attrMeta.getType(); if (type.isCollection()) { return type.asCollection().getElementType().getImplementationClass(); - } - else { + } else { return type.getImplementationClass(); } } @@ -163,8 +143,7 @@ public void addRelations(S source, Iterable targetIds, String fieldName) { if (oppositeAttrMeta != null) { if (oppositeAttrMeta.getType().isCollection()) { oppositeAttrMeta.addValue(target, sourceEntity); - } - else { + } else { oppositeAttrMeta.setValue(target, sourceEntity); } em.persist(target); @@ -189,8 +168,7 @@ public void removeRelations(S source, Iterable targetIds, String fieldName) { if (target != null && oppositeAttrMeta != null) { if (oppositeAttrMeta.getType().isCollection()) { oppositeAttrMeta.removeValue(target, sourceEntity); - } - else { + } else { oppositeAttrMeta.setValue(target, null); } } @@ -289,8 +267,7 @@ public T findOneTarget(I sourceId, String fieldName, QuerySpec querySpec) { MultivaluedMap map = findTargets(Arrays.asList(sourceId), fieldName, querySpec); if (map.isEmpty()) { return null; - } - else { + } else { return map.getUnique(sourceId); } } @@ -300,8 +277,7 @@ public ResourceList findManyTargets(I sourceId, String fieldName, QuerySpec q MultivaluedMap map = findTargets(Arrays.asList(sourceId), fieldName, querySpec); if (map.isEmpty()) { return new DefaultResourceList<>(); - } - else { + } else { return (ResourceList) map.getList(sourceId); } } diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/annotations/JpaMergeRelations.java b/crnk-jpa/src/main/java/io/crnk/jpa/annotations/JpaMergeRelations.java deleted file mode 100644 index f8104ef56..000000000 --- a/crnk-jpa/src/main/java/io/crnk/jpa/annotations/JpaMergeRelations.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.crnk.jpa.annotations; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import static java.lang.annotation.ElementType.TYPE; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -/** - * Allows a JPA entity to include related entities in the exposed JSON API resource. - * For a consumer it looks like a single (large) resource that can be inserted, updated - * and deleted with a single request and, as such, in a single transaction. - */ -@Retention(RUNTIME) -@Target(TYPE) -public @interface JpaMergeRelations { - - /** - * Defines set of relationship attributes that should be merged - * into this document and treated as regular attribute of this document. - */ - String[] attributes(); -} diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/internal/JpaRepositoryUtils.java b/crnk-jpa/src/main/java/io/crnk/jpa/internal/JpaRepositoryUtils.java index a73c63c79..dcd1ed764 100644 --- a/crnk-jpa/src/main/java/io/crnk/jpa/internal/JpaRepositoryUtils.java +++ b/crnk-jpa/src/main/java/io/crnk/jpa/internal/JpaRepositoryUtils.java @@ -6,7 +6,6 @@ import io.crnk.core.queryspec.IncludeSpec; import io.crnk.core.queryspec.QuerySpec; import io.crnk.core.queryspec.SortSpec; -import io.crnk.jpa.annotations.JpaMergeRelations; import io.crnk.jpa.query.JpaQuery; import io.crnk.jpa.query.JpaQueryExecutor; import io.crnk.meta.model.MetaAttribute; @@ -67,37 +66,5 @@ public static void prepareExecutor(JpaQueryExecutor executor, QuerySpec query } executor.setLimit((int) querySpec.getLimit().longValue()); } - - addMergeInclusions(executor, querySpec); - - } - - /** - * related attribute that are merged into a resource should be loaded by - * graph control to avoid lazy-loading or potential lack of session in - * serialization. - */ - private static void addMergeInclusions(JpaQueryExecutor executor, QuerySpec querySpec) { - ArrayDeque attributePath = new ArrayDeque<>(); - Class resourceClass = querySpec.getResourceClass(); - - addMergeInclusions(attributePath, executor, resourceClass); } - - private static void addMergeInclusions(Deque attributePath, JpaQueryExecutor executor, Class resourceClass) { - JpaMergeRelations annotation = resourceClass.getAnnotation(JpaMergeRelations.class); - if (annotation != null) { - for (String attrName : annotation.attributes()) { - attributePath.push(attrName); - executor.fetch(new ArrayList<>(attributePath)); - - // recurse - Class attrType = PropertyUtils.getPropertyClass(resourceClass, attrName); - addMergeInclusions(attributePath, executor, attrType); - - attributePath.pop(); - } - } - } - } diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/internal/JpaResourceInformationBuilder.java b/crnk-jpa/src/main/java/io/crnk/jpa/internal/JpaResourceInformationBuilder.java index 8ab2a8f3a..1e08d05c1 100644 --- a/crnk-jpa/src/main/java/io/crnk/jpa/internal/JpaResourceInformationBuilder.java +++ b/crnk-jpa/src/main/java/io/crnk/jpa/internal/JpaResourceInformationBuilder.java @@ -1,34 +1,10 @@ package io.crnk.jpa.internal; -import java.io.Serializable; -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.persistence.ElementCollection; -import javax.persistence.FetchType; -import javax.persistence.ManyToMany; -import javax.persistence.ManyToOne; -import javax.persistence.MappedSuperclass; -import javax.persistence.OneToMany; -import javax.persistence.OptimisticLockException; - import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; import io.crnk.core.engine.document.Document; import io.crnk.core.engine.document.Resource; -import io.crnk.core.engine.information.resource.ResourceField; -import io.crnk.core.engine.information.resource.ResourceFieldAccess; -import io.crnk.core.engine.information.resource.ResourceFieldType; -import io.crnk.core.engine.information.resource.ResourceInformation; -import io.crnk.core.engine.information.resource.ResourceInformationBuilder; -import io.crnk.core.engine.information.resource.ResourceInformationBuilderContext; -import io.crnk.core.engine.information.resource.ResourceInstanceBuilder; +import io.crnk.core.engine.information.resource.*; import io.crnk.core.engine.internal.information.resource.AnnotationResourceInformationBuilder; import io.crnk.core.engine.internal.information.resource.AnnotationResourceInformationBuilder.AnnotatedResourceField; import io.crnk.core.engine.internal.information.resource.DefaultResourceInstanceBuilder; @@ -40,17 +16,20 @@ import io.crnk.core.resource.annotations.JsonApiMetaInformation; import io.crnk.core.resource.annotations.LookupIncludeBehavior; import io.crnk.core.utils.Optional; -import io.crnk.jpa.annotations.JpaMergeRelations; import io.crnk.jpa.annotations.JpaResource; import io.crnk.jpa.meta.MetaEntity; import io.crnk.jpa.meta.MetaJpaDataObject; import io.crnk.meta.MetaLookup; import io.crnk.meta.information.MetaAwareInformation; -import io.crnk.meta.model.MetaAttribute; -import io.crnk.meta.model.MetaDataObject; -import io.crnk.meta.model.MetaElement; -import io.crnk.meta.model.MetaKey; -import io.crnk.meta.model.MetaType; +import io.crnk.meta.model.*; + +import javax.persistence.*; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.*; /** * Extracts resource information from JPA and Crnk annotations. Crnk @@ -72,14 +51,11 @@ public static boolean isJpaLazy(Collection annotations) { for (Annotation annotation : annotations) { if (annotation instanceof ElementCollection) { return ((ElementCollection) annotation).fetch() == FetchType.LAZY; - } - else if (annotation instanceof ManyToOne) { + } else if (annotation instanceof ManyToOne) { return ((ManyToOne) annotation).fetch() == FetchType.LAZY; - } - else if (annotation instanceof OneToMany) { + } else if (annotation instanceof OneToMany) { return ((OneToMany) annotation).fetch() == FetchType.LAZY; - } - else if (annotation instanceof ManyToMany) { + } else if (annotation instanceof ManyToMany) { return ((ManyToMany) annotation).fetch() == FetchType.LAZY; } } @@ -90,8 +66,7 @@ public static String getJpaOppositeName(Collection annotations) { for (Annotation annotation : annotations) { if (annotation instanceof OneToMany) { return StringUtils.emptyToNull(((OneToMany) annotation).mappedBy()); - } - else if (annotation instanceof ManyToMany) { + } else if (annotation instanceof ManyToMany) { return StringUtils.emptyToNull(((ManyToMany) annotation).mappedBy()); } } @@ -111,8 +86,7 @@ public boolean accept(Class resourceClass) { MetaEntity metaEntity = (MetaEntity) meta; MetaKey primaryKey = metaEntity.getPrimaryKey(); return primaryKey != null && primaryKey.getElements().size() == 1; - } - else { + } else { // note that DTOs cannot be handled here return meta instanceof MetaJpaDataObject; } @@ -127,7 +101,7 @@ public ResourceInformation build(final Class resourceClass) { DefaultResourceInstanceBuilder instanceBuilder; meta = jpaMetaLookup.getMeta(resourceClass, MetaJpaDataObject.class).asDataObject(); - instanceBuilder = new JpaResourceInstanceBuilder((MetaJpaDataObject) meta, resourceClass); + instanceBuilder = new DefaultResourceInstanceBuilder(resourceClass); List fields = buildFields(meta); Set ignoredFields = getIgnoredFields(meta); @@ -172,17 +146,6 @@ protected List buildFields(MetaDataObject meta) { } protected boolean isAssociation(MetaDataObject meta, MetaAttribute attr) { - // merged attribute are handled as normal data attributes - JpaMergeRelations mergeAnnotation = meta.getImplementationClass().getAnnotation(JpaMergeRelations.class); - if (mergeAnnotation != null) { - String[] mergedAttrs = mergeAnnotation.attributes(); - for (String mergedAttr : mergedAttrs) { - if (mergedAttr.equals(attr.getName())) { - return false; - } - } - } - return attr.isAssociation(); } @@ -212,8 +175,7 @@ protected ResourceField toField(MetaDataObject meta, MetaAttribute attr) { if (getter != null) { type = getter.getReturnType(); genericType = getter.getGenericReturnType(); - } - else if (field != null) { + } else if (field != null) { type = field.getType(); genericType = field.getGenericType(); } @@ -260,26 +222,6 @@ public void init(ResourceInformationBuilderContext context) { this.context = context; } - class JpaResourceInstanceBuilder extends DefaultResourceInstanceBuilder { - - private MetaJpaDataObject meta; - - public JpaResourceInstanceBuilder(MetaJpaDataObject meta, Class resourceClass) { - super(resourceClass); - this.meta = meta; - } - - @Override - public int hashCode() { - return super.hashCode() | meta.getName().hashCode(); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj) && obj instanceof JpaResourceInstanceBuilder; - } - } - class JpaResourceInformation extends ResourceInformation implements MetaAwareInformation { private MetaDataObject jpaMeta; @@ -287,8 +229,8 @@ class JpaResourceInformation extends ResourceInformation implements MetaAwareInf private Set ignoredFields; public JpaResourceInformation(TypeParser typeParser, MetaDataObject meta, Class resourceClass, - String resourceType, String superResourceType, // NOSONAR - ResourceInstanceBuilder instanceBuilder, List fields, Set ignoredFields) { + String resourceType, String superResourceType, // NOSONAR + ResourceInstanceBuilder instanceBuilder, List fields, Set ignoredFields) { super(typeParser, resourceClass, resourceType, superResourceType, instanceBuilder, fields); this.jpaMeta = meta; this.ignoredFields = ignoredFields; @@ -336,8 +278,7 @@ private Object fromKeyString(MetaType type, String idString) { // => support compound keys with unique ids if (type instanceof MetaDataObject) { return parseEmbeddableString((MetaDataObject) type, idString); - } - else { + } else { return context.getTypeParser().parse(idString, (Class) type.getImplementationClass()); } } diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/AbstractQueryExecutorImpl.java b/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/AbstractQueryExecutorImpl.java index 486949303..834e0c587 100644 --- a/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/AbstractQueryExecutorImpl.java +++ b/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/AbstractQueryExecutorImpl.java @@ -1,18 +1,14 @@ package io.crnk.jpa.internal.query; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.persistence.EntityManager; -import javax.persistence.Query; - +import io.crnk.core.engine.internal.utils.ClassUtils; import io.crnk.jpa.query.JpaQueryExecutor; import io.crnk.meta.model.MetaAttributePath; import io.crnk.meta.model.MetaDataObject; +import javax.persistence.EntityManager; +import javax.persistence.Query; +import java.util.*; + public abstract class AbstractQueryExecutorImpl implements JpaQueryExecutor { private static final String ENTITY_GRAPH_BUILDER_IMPL = "io.crnk.jpa.internal.query.EntityGraphBuilderImpl"; @@ -34,7 +30,7 @@ public abstract class AbstractQueryExecutorImpl implements JpaQueryExecutor selectionBindings; public AbstractQueryExecutorImpl(EntityManager em, MetaDataObject meta, int numAutoSelections, - Map selectionBindings) { + Map selectionBindings) { this.em = em; this.meta = meta; this.numAutoSelections = numAutoSelections; @@ -56,10 +52,6 @@ protected static List enforceDistinct(List list) { return distinctResult; } - public MetaDataObject getMeta() { - return meta; - } - @Override public JpaQueryExecutor fetch(List attrPath) { // include path an all prefix paths @@ -114,8 +106,7 @@ public List getResultList() { entityList.add((T) values[0]); } resultList = entityList; - } - else { + } else { resultList = (List) list; } return resultList; @@ -131,11 +122,9 @@ public T getUniqueResult(boolean nullable) { } if (!list.isEmpty()) { return list.get(0); - } - else if (nullable) { + } else if (nullable) { return null; - } - else { + } else { throw new IllegalStateException("no result found"); } } @@ -160,13 +149,13 @@ public Class getEntityClass() { protected void applyFetchPaths(Query criteriaQuery) { if (!fetchPaths.isEmpty()) { EntityGraphBuilder builder; - try { - // avoid compile-time dependency - builder = (EntityGraphBuilder) Class.forName(ENTITY_GRAPH_BUILDER_IMPL).newInstance(); - } - catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { - throw new IllegalStateException(e); - } + + // avoid compile-time dependency to support old JPA implementation as long + // as no fetch paths are used + ClassLoader classLoader = getClass().getClassLoader(); + Class builderClass = ClassUtils.loadClass(classLoader, ENTITY_GRAPH_BUILDER_IMPL); + builder = (EntityGraphBuilder) ClassUtils.newInstance(builderClass); + Class entityClass = getEntityClass(); builder.build(em, criteriaQuery, entityClass, fetchPaths); } diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/backend/criteria/JpaCriteriaQueryBackend.java b/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/backend/criteria/JpaCriteriaQueryBackend.java index 021682e0d..d21cbdca3 100644 --- a/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/backend/criteria/JpaCriteriaQueryBackend.java +++ b/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/backend/criteria/JpaCriteriaQueryBackend.java @@ -193,12 +193,10 @@ private Predicate handle(Expression expression, FilterOperator operator, Object return cb.lessThan(expression, (Comparable) value); } else if (operator == FilterOperator.GE) { return cb.greaterThanOrEqualTo(expression, (Comparable) value); - } else if (operator == FilterOperator.LE) { - return cb.lessThanOrEqualTo(expression, (Comparable) value); } else { - throw new IllegalStateException("unexpected operator " + operator); + PreconditionUtil.verify(operator == FilterOperator.LE, "unexpected operator %s", operator); + return cb.lessThanOrEqualTo(expression, (Comparable) value); } - } @SuppressWarnings({"rawtypes", "unchecked"}) diff --git a/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/backend/querydsl/QuerydslQueryBackend.java b/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/backend/querydsl/QuerydslQueryBackend.java index e125ee13c..0be4682e9 100644 --- a/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/backend/querydsl/QuerydslQueryBackend.java +++ b/crnk-jpa/src/main/java/io/crnk/jpa/internal/query/backend/querydsl/QuerydslQueryBackend.java @@ -83,9 +83,7 @@ public QuerydslQueryBackend(QuerydslQueryImpl queryImpl, Class clazz, Meta private Expression getParentIdExpression(MetaDataObject parentMeta, MetaAttribute parentAttr) { MetaKey primaryKey = parentMeta.getPrimaryKey(); - if (primaryKey == null) { - throw new IllegalStateException("no primary key specified for parentAttribute " + parentAttr.getId()); - } + PreconditionUtil.assertNotNull("no primary key specified for parentAttribute " + parentAttr.getId(), parentMeta); List elements = primaryKey.getElements(); PreconditionUtil.assertEquals("composite primary keys not supported yet", 1, elements.size()); MetaAttribute primaryKeyAttr = elements.get(0); @@ -237,14 +235,16 @@ private Predicate handle(Expression expression, FilterOperator operator, Object } - private Predicate handleEquals(Expression expression, FilterOperator operator, Object value) { + private Predicate handleEquals(Expression leftExpression, FilterOperator operator, Object value) { + Expression expression = leftExpression; + if (Collection.class.isAssignableFrom(expression.getType())) { + CollectionPathBase collectionExpr = (CollectionPathBase) expression; + expression = collectionExpr.any(); + } + if (value instanceof List) { Predicate p = ((SimpleExpression) expression).in((List) value); return negateIfNeeded(p, operator); - } else if (Collection.class.isAssignableFrom(expression.getType())) { - SimpleExpression simpleExpr = (SimpleExpression) expression; - Predicate p = simpleExpr.in(value); - return negateIfNeeded(p, operator); } else if (expression instanceof MapExpressionBase) { MapExpressionBase mapExpression = (MapExpressionBase) expression; Predicate p = mapExpression.containsValue(value); diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/JpaModuleTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/JpaModuleTest.java new file mode 100644 index 000000000..73e2cb4d0 --- /dev/null +++ b/crnk-jpa/src/test/java/io/crnk/jpa/JpaModuleTest.java @@ -0,0 +1,22 @@ +package io.crnk.jpa; + +import io.crnk.core.engine.transaction.TransactionRunner; +import io.crnk.jpa.model.TestEntity; +import org.junit.Test; +import org.mockito.Mockito; + +import javax.persistence.EntityManager; + +public class JpaModuleTest { + + + @Test(expected = IllegalStateException.class) + public void cannotPerformDuplicateRegistration() { + TransactionRunner transactionRunner = Mockito.mock(TransactionRunner.class); + EntityManager em = Mockito.mock(EntityManager.class); + JpaModule module = JpaModule.newServerModule(em, transactionRunner); + module.addRepository(JpaRepositoryConfig.create(TestEntity.class)); + module.addRepository(JpaRepositoryConfig.create(TestEntity.class)); + } + +} diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/JpaQuerySpecEndToEndTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/JpaQuerySpecEndToEndTest.java index b8daac5b1..a2bba4864 100644 --- a/crnk-jpa/src/test/java/io/crnk/jpa/JpaQuerySpecEndToEndTest.java +++ b/crnk-jpa/src/test/java/io/crnk/jpa/JpaQuerySpecEndToEndTest.java @@ -1,7 +1,7 @@ package io.crnk.jpa; -import io.crnk.client.legacy.ResourceRepositoryStub; import io.crnk.client.internal.proxy.ObjectProxy; +import io.crnk.client.legacy.ResourceRepositoryStub; import io.crnk.client.response.JsonLinksInformation; import io.crnk.client.response.JsonMetaInformation; import io.crnk.core.exception.ResourceNotFoundException; diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/JpaResourceInformationBuilderTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/JpaResourceInformationBuilderTest.java index c2f06e5d4..60da2a88c 100644 --- a/crnk-jpa/src/test/java/io/crnk/jpa/JpaResourceInformationBuilderTest.java +++ b/crnk-jpa/src/test/java/io/crnk/jpa/JpaResourceInformationBuilderTest.java @@ -4,7 +4,6 @@ import io.crnk.core.engine.information.resource.ResourceInformation; import io.crnk.core.engine.parser.TypeParser; import io.crnk.jpa.internal.JpaResourceInformationBuilder; -import io.crnk.jpa.merge.MergedResource; import io.crnk.jpa.meta.JpaMetaProvider; import io.crnk.jpa.model.*; import io.crnk.jpa.util.ResourceFieldComparator; @@ -15,7 +14,6 @@ import io.crnk.meta.provider.resource.ResourceMetaProvider; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; @@ -174,20 +172,6 @@ public void testReadOnlyField() throws NoSuchFieldException, SecurityException, Assert.assertFalse(attribute.isUpdatable()); } - @Test - @Ignore - public void mergeRelationsAnnotation() { - Assert.assertTrue(builder.accept(MergedResource.class)); - - ResourceInformation info = builder.build(MergedResource.class); - Assert.assertEquals("merged", info.getResourceType()); - Assert.assertEquals(MergedResource.class, info.getResourceClass()); - Assert.assertNull(info.findRelationshipFieldByName("oneRelatedValue")); - Assert.assertNull(info.findRelationshipFieldByName("manyRelatedValues")); - Assert.assertNotNull(info.findAttributeFieldByName("oneRelatedValue")); - Assert.assertNotNull(info.findAttributeFieldByName("manyRelatedValues")); - } - @Test public void testMappedSuperclass() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { ResourceInformation info = builder.build(AnnotationMappedSuperclassEntity.class); diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/internal/JpaRepositoryUtilsTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/internal/JpaRepositoryUtilsTest.java new file mode 100644 index 000000000..93efbfab0 --- /dev/null +++ b/crnk-jpa/src/test/java/io/crnk/jpa/internal/JpaRepositoryUtilsTest.java @@ -0,0 +1,12 @@ +package io.crnk.jpa.internal; + +import io.crnk.test.mock.ClassTestUtils; +import org.junit.Test; + +public class JpaRepositoryUtilsTest { + + @Test + public void hasPrivateConstructor() { + ClassTestUtils.assertPrivateConstructor(JpaRepositoryUtils.class); + } +} diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/internal/QueryUtilTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/internal/QueryUtilTest.java new file mode 100644 index 000000000..46541b95b --- /dev/null +++ b/crnk-jpa/src/test/java/io/crnk/jpa/internal/QueryUtilTest.java @@ -0,0 +1,13 @@ +package io.crnk.jpa.internal; + +import io.crnk.jpa.internal.query.QueryUtil; +import io.crnk.test.mock.ClassTestUtils; +import org.junit.Test; + +public class QueryUtilTest { + + @Test + public void hasPrivateConstructor() { + ClassTestUtils.assertPrivateConstructor(QueryUtil.class); + } +} diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/internal/query/backend/querydsl/QuerydslUtilsTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/internal/query/backend/querydsl/QuerydslUtilsTest.java new file mode 100644 index 000000000..591aac004 --- /dev/null +++ b/crnk-jpa/src/test/java/io/crnk/jpa/internal/query/backend/querydsl/QuerydslUtilsTest.java @@ -0,0 +1,35 @@ +package io.crnk.jpa.internal.query.backend.querydsl; + +import com.querydsl.core.types.Expression; +import io.crnk.test.mock.ClassTestUtils; +import org.junit.Test; +import org.mockito.Mockito; + +public class QuerydslUtilsTest { + + @Test + public void checkHasPrivateConstructor() { + ClassTestUtils.assertPrivateConstructor(QuerydslUtils.class); + } + + @Test(expected = IllegalStateException.class) + public void throwExceptionWhenAccessingInvalidEntityPath() { + QuerydslUtils.getEntityPath(InvalidEntity.class); + } + + @Test(expected = IllegalStateException.class) + public void throwExceptionWhenGettingInvalidQueryClass() { + QuerydslUtils.getQueryClass(InvalidEntity.class); + } + + @Test(expected = IllegalStateException.class) + public void throwExceptionWhenFollowingInvalidPath() { + Expression expression = Mockito.mock(Expression.class); + Mockito.when(expression.getType()).thenReturn((Class) InvalidEntity.class); + QuerydslUtils.get(expression, "doesNotExist"); + } + + class InvalidEntity { + + } +} \ No newline at end of file diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergeTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergeTest.java deleted file mode 100644 index 14e1ece78..000000000 --- a/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergeTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package io.crnk.jpa.merge; - -import io.crnk.core.repository.ResourceRepositoryV2; -import io.crnk.jpa.AbstractJpaJerseyTest; -import io.crnk.jpa.JpaModule; -import io.crnk.jpa.JpaRepositoryConfig; -import io.crnk.jpa.model.RelatedEntity; -import io.crnk.jpa.model.TestEntity; -import io.crnk.jpa.util.SpringTransactionRunner; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -import javax.persistence.EntityManager; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; - -/** - * Example of how to merge an entity with several related entities into a single - * resource to achieve atomic updates. - */ -@Ignore // not supported, use dtp maping, to be replaced by configuration -public class MergeTest extends AbstractJpaJerseyTest { - - private ResourceRepositoryV2 repo; - - private EntityManager entityManager; - - @Override - @Before - public void setup() { - super.setup(); - repo = client.getQuerySpecRepository(MergedResource.class); - } - - @Override - protected void setupModule(JpaModule module, boolean server) { - super.setupModule(module, server); - - if (server) { - entityManager = module.getEntityManager(); - - MergedResourceMapper mapper = new MergedResourceMapper(entityManager); - module.addRepository(JpaRepositoryConfig.builder(TestEntity.class, MergedResource.class, mapper).build()); - } - } - - private MergedResource newMergedResource() { - RelatedEntity oneRelated = new RelatedEntity(); - oneRelated.setId(3L); - oneRelated.setStringValue("oneRelated"); - - RelatedEntity manyRelated = new RelatedEntity(); - manyRelated.setId(4L); - manyRelated.setStringValue("manyRelated"); - - List manyRelatedValues = new ArrayList<>(); - manyRelatedValues.add(manyRelated); - - MergedResource test = new MergedResource(); - test.setId(2L); - test.setStringValue("test"); - test.setOneRelatedValue(oneRelated); - test.setManyRelatedValues(manyRelatedValues); - return test; - } - - @Test - public void testInsert() throws InstantiationException, IllegalAccessException { - final MergedResource test = newMergedResource(); - repo.create(test); - - SpringTransactionRunner transactionRunner = context.getBean(SpringTransactionRunner.class); - transactionRunner.doInTransaction(new Callable() { - - @Override - public Object call() throws Exception { - TestEntity testEntity = entityManager.find(TestEntity.class, test.getId()); - Assert.assertEquals("test", testEntity.getStringValue()); - RelatedEntity oneRelatedEntity = testEntity.getOneRelatedValue(); - List manyRelatedEntities = testEntity.getManyRelatedValues(); - Assert.assertNotNull(oneRelatedEntity); - Assert.assertEquals(1, manyRelatedEntities.size()); - return null; - } - }); - } - -} \ No newline at end of file diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergedResource.java b/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergedResource.java deleted file mode 100644 index cc4e88b80..000000000 --- a/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergedResource.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.crnk.jpa.merge; - -import io.crnk.jpa.annotations.JpaMergeRelations; -import io.crnk.jpa.annotations.JpaResource; -import io.crnk.jpa.model.TestEntity; - -@JpaResource(type = "merged") -@JpaMergeRelations(attributes = {"oneRelatedValue", "manyRelatedValues"}) -public class MergedResource extends TestEntity { - -} diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergedResourceMapper.java b/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergedResourceMapper.java deleted file mode 100644 index 82da5562a..000000000 --- a/crnk-jpa/src/test/java/io/crnk/jpa/merge/MergedResourceMapper.java +++ /dev/null @@ -1,89 +0,0 @@ -package io.crnk.jpa.merge; - -import io.crnk.jpa.mapping.JpaMapper; -import io.crnk.jpa.model.RelatedEntity; -import io.crnk.jpa.model.TestEntity; -import io.crnk.jpa.query.Tuple; - -import javax.persistence.EntityManager; -import java.util.ArrayList; -import java.util.List; - -public class MergedResourceMapper implements JpaMapper { - - private EntityManager em; - - public MergedResourceMapper(EntityManager em) { - this.em = em; - } - - @Override - public MergedResource map(Tuple tuple) { - TestEntity test = tuple.get(0, TestEntity.class); - - MergedResource merged = new MergedResource(); - merged.setId(test.getId()); - merged.setStringValue(test.getStringValue()); - merged.setOneRelatedValue(map(test.getOneRelatedValue())); - merged.setManyRelatedValues(map(test.getManyRelatedValues())); - return merged; - } - - private RelatedEntity map(RelatedEntity related) { - RelatedEntity merged = new RelatedEntity(); - merged.setId(related.getId()); - merged.setStringValue(related.getStringValue()); - return merged; - } - - private List map(List values) { - List list = new ArrayList<>(); - for (RelatedEntity value : values) { - list.add(map(value)); - } - return list; - } - - @Override - public TestEntity unmap(MergedResource merged) { - TestEntity entity = em.find(TestEntity.class, merged.getId()); - if (entity == null) { - entity = new TestEntity(); - entity.setId(merged.getId()); - } - entity.setStringValue(merged.getStringValue()); - entity.setOneRelatedValue(unmap(merged.getOneRelatedValue())); - entity.setManyRelatedValues(unmap(merged.getManyRelatedValues())); - - // in the chosen setup the owning side is RelatedEntity and - // needs to be updated as well. Might be moved into the setters. - for (RelatedEntity related : entity.getManyRelatedValues()) { - related.setTestEntity(entity); - } - - return entity; - } - - private List unmap(List values) { - List list = new ArrayList<>(); - for (RelatedEntity value : values) { - list.add(unmap(value)); - } - return list; - } - - private RelatedEntity unmap(RelatedEntity merged) { - if (merged != null) { - RelatedEntity related = em.find(RelatedEntity.class, merged.getId()); - if (related == null) { - related = new RelatedEntity(); - related.setId(merged.getId()); - } - related.setStringValue(merged.getStringValue()); - return related; - } else { - return null; - } - } - -} diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/meta/MetaComputedAttributeTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/meta/MetaComputedAttributeTest.java new file mode 100644 index 000000000..f1bf237ba --- /dev/null +++ b/crnk-jpa/src/test/java/io/crnk/jpa/meta/MetaComputedAttributeTest.java @@ -0,0 +1,20 @@ +package io.crnk.jpa.meta; + +import io.crnk.jpa.internal.query.MetaComputedAttribute; +import org.junit.Test; + +public class MetaComputedAttributeTest { + + + @Test(expected = UnsupportedOperationException.class) + public void getValueNotSupported() { + MetaComputedAttribute attr = new MetaComputedAttribute(); + attr.getValue(null); + } + + @Test(expected = UnsupportedOperationException.class) + public void setValueNotSupported() { + MetaComputedAttribute attr = new MetaComputedAttribute(); + attr.setValue(null, null); + } +} diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/model/CollectionAttributesTestEntity.java b/crnk-jpa/src/test/java/io/crnk/jpa/model/CollectionAttributesTestEntity.java new file mode 100644 index 000000000..00a40a2bd --- /dev/null +++ b/crnk-jpa/src/test/java/io/crnk/jpa/model/CollectionAttributesTestEntity.java @@ -0,0 +1,51 @@ +package io.crnk.jpa.model; + +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.Id; +import java.util.List; + +@Entity +public class CollectionAttributesTestEntity { + + public static final String ATTR_id = "id"; + + + public static final String ATTR_stringValues = "stringValues"; + + public static final String ATTR_longValues = "longValues"; + + @Id + private Long id; + + @ElementCollection + private List longValues; + + @ElementCollection + private List stringValues; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public List getLongValues() { + return longValues; + } + + public void setLongValues(List longValues) { + this.longValues = longValues; + } + + public List getStringValues() { + return stringValues; + } + + public void setStringValues(List stringValues) { + this.stringValues = stringValues; + } + +} diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/model/FieldOnlyEntity.java b/crnk-jpa/src/test/java/io/crnk/jpa/model/FieldOnlyEntity.java new file mode 100644 index 000000000..54ca18e03 --- /dev/null +++ b/crnk-jpa/src/test/java/io/crnk/jpa/model/FieldOnlyEntity.java @@ -0,0 +1,19 @@ +package io.crnk.jpa.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +public class FieldOnlyEntity { + + public static final String ATTR_id = "id"; + + public static final String ATTR_longValue = "longValue"; + + @Id + public Long id; + + @Column + public long longValue; +} \ No newline at end of file diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/query/BasicQueryTestBase.java b/crnk-jpa/src/test/java/io/crnk/jpa/query/BasicQueryTestBase.java index 117457f13..809d4b78e 100644 --- a/crnk-jpa/src/test/java/io/crnk/jpa/query/BasicQueryTestBase.java +++ b/crnk-jpa/src/test/java/io/crnk/jpa/query/BasicQueryTestBase.java @@ -3,6 +3,7 @@ import io.crnk.core.queryspec.Direction; import io.crnk.core.queryspec.FilterOperator; import io.crnk.core.queryspec.FilterSpec; +import io.crnk.jpa.model.CollectionAttributesTestEntity; import io.crnk.jpa.model.RelatedEntity; import io.crnk.jpa.model.TestEntity; import io.crnk.jpa.model.UuidTestEntity; @@ -72,6 +73,26 @@ public void testEqualsFilter() { assertEquals((Long) 2L, builder().addFilter(TestEntity.ATTR_stringValue, FilterOperator.EQ, "test2").buildExecutor().getUniqueResult(false).getId()); } + @Test + public void testEqualsInCollectionFilter() { + CollectionAttributesTestEntity entity = new CollectionAttributesTestEntity(); + entity.setId(13L); + entity.setLongValues(Arrays.asList(1L, 2L)); + entity.setStringValues(Arrays.asList("John", "Doe")); + em.persist(entity); + + assertEquals((Long) 13L, queryFactory.query(CollectionAttributesTestEntity.class) + .addFilter(CollectionAttributesTestEntity.ATTR_stringValues, FilterOperator.EQ, "Doe") + .buildExecutor().getUniqueResult(false).getId()); + assertEquals((Long) 13L, queryFactory.query(CollectionAttributesTestEntity.class) + .addFilter(CollectionAttributesTestEntity.ATTR_stringValues, FilterOperator.EQ, "John") + .buildExecutor().getUniqueResult(false).getId()); + assertNull(queryFactory.query(CollectionAttributesTestEntity.class) + .addFilter(CollectionAttributesTestEntity.ATTR_stringValues, FilterOperator.EQ, "Jane") + .buildExecutor().getUniqueResult(true)); + } + + @Test public void testNotEqualsFilter() { assertEquals(4, builder().addFilter(TestEntity.ATTR_id, FilterOperator.NEQ, 0L).buildExecutor().getResultList().size()); @@ -102,6 +123,7 @@ public void testGreaterFilter() { assertEquals(4, builder().addFilter(TestEntity.ATTR_id, FilterOperator.GT, 0L).buildExecutor().getResultList().size()); assertEquals(3, builder().addFilter(TestEntity.ATTR_id, FilterOperator.GT, 1L).buildExecutor().getResultList().size()); assertEquals(0, builder().addFilter(TestEntity.ATTR_id, FilterOperator.GT, 4L).buildExecutor().getResultList().size()); + assertEquals(3, builder().addFilter(TestEntity.ATTR_stringValue, FilterOperator.GT, "test1").buildExecutor().getResultList().size()); } @Test @@ -109,6 +131,7 @@ public void testLessFilter() { assertEquals(0, builder().addFilter(TestEntity.ATTR_id, FilterOperator.LT, 0L).buildExecutor().getResultList().size()); assertEquals(1, builder().addFilter(TestEntity.ATTR_id, FilterOperator.LT, 1L).buildExecutor().getResultList().size()); assertEquals(2, builder().addFilter(TestEntity.ATTR_id, FilterOperator.LT, 2L).buildExecutor().getResultList().size()); + assertEquals(1, builder().addFilter(TestEntity.ATTR_stringValue, FilterOperator.LT, "test1").buildExecutor().getResultList().size()); } @Test @@ -116,6 +139,7 @@ public void testGreaterEqualsFilter() { assertEquals(5, builder().addFilter(TestEntity.ATTR_id, FilterOperator.GE, 0L).buildExecutor().getResultList().size()); assertEquals(4, builder().addFilter(TestEntity.ATTR_id, FilterOperator.GE, 1L).buildExecutor().getResultList().size()); assertEquals(3, builder().addFilter(TestEntity.ATTR_id, FilterOperator.GE, 2L).buildExecutor().getResultList().size()); + assertEquals(4, builder().addFilter(TestEntity.ATTR_stringValue, FilterOperator.GE, "test1").buildExecutor().getResultList().size()); } @Test @@ -123,6 +147,7 @@ public void testLessEqualsFilter() { assertEquals(1, builder().addFilter(TestEntity.ATTR_id, FilterOperator.LE, 0L).buildExecutor().getResultList().size()); assertEquals(2, builder().addFilter(TestEntity.ATTR_id, FilterOperator.LE, 1L).buildExecutor().getResultList().size()); assertEquals(3, builder().addFilter(TestEntity.ATTR_id, FilterOperator.LE, 2L).buildExecutor().getResultList().size()); + assertEquals(2, builder().addFilter(TestEntity.ATTR_stringValue, FilterOperator.LE, "test1").buildExecutor().getResultList().size()); } @Test @@ -179,6 +204,16 @@ public void testJoinFilter() { assertEquals((Long) 2L, builder().addFilter(TestEntity.ATTR_oneRelatedValue + "." + RelatedEntity.ATTR_stringValue, FilterOperator.EQ, "related2").buildExecutor().getUniqueResult(false).getId()); } + @Test(expected = IllegalStateException.class) + public void testThrowExceptionOnNonUnique() { + builder().buildExecutor().getUniqueResult(false); + } + + @Test(expected = IllegalStateException.class) + public void testThrowExceptionOnNonNullableUnique() { + builder().addFilter(TestEntity.ATTR_stringValue, FilterOperator.EQ, "doesNotExist").buildExecutor().getUniqueResult(false); + } + @Test public void testPrimitiveOrder() { assertEquals(5, builder().addSortBy(Arrays.asList(TestEntity.ATTR_id), Direction.DESC).buildExecutor().getResultList().size()); @@ -198,12 +233,18 @@ public void testEmbeddedOrder() { } @Test - public void testOneRelatedOrder() { + public void testOneRelatedEntityOrder() { assertEquals(5, builder().setDefaultJoinType(JoinType.LEFT).addSortBy(Arrays.asList(TestEntity.ATTR_oneRelatedValue), Direction.DESC).buildExecutor().getResultList().size()); assertEquals((Long) 0L, builder().addSortBy(Arrays.asList(TestEntity.ATTR_oneRelatedValue), Direction.ASC).buildExecutor().getResultList().get(0).getId()); assertEquals((Long) 3L, builder().addSortBy(Arrays.asList(TestEntity.ATTR_oneRelatedValue), Direction.DESC).buildExecutor().getResultList().get(0).getId()); } + @Test + public void testOneRelatedAttributeOrder() { + assertEquals((Long) 0L, builder().addSortBy(Arrays.asList(TestEntity.ATTR_oneRelatedValue, RelatedEntity.ATTR_stringValue), Direction.ASC).buildExecutor().getResultList().get(0).getId()); + assertEquals((Long) 3L, builder().addSortBy(Arrays.asList(TestEntity.ATTR_oneRelatedValue, RelatedEntity.ATTR_stringValue), Direction.DESC).buildExecutor().getResultList().get(0).getId()); + } + @Test public void testMapOrder() { assertEquals(5, builder().setDefaultJoinType(JoinType.LEFT).addSortBy(Arrays.asList(TestEntity.ATTR_mapValue, "a"), Direction.DESC).buildExecutor().getResultList().size()); diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/query/QueryDslTupleImplTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/query/QueryDslTupleImplTest.java index 3fe7fb75a..de06ce3e0 100644 --- a/crnk-jpa/src/test/java/io/crnk/jpa/query/QueryDslTupleImplTest.java +++ b/crnk-jpa/src/test/java/io/crnk/jpa/query/QueryDslTupleImplTest.java @@ -1,6 +1,7 @@ package io.crnk.jpa.query; import com.querydsl.core.Tuple; +import com.querydsl.core.types.Expression; import io.crnk.jpa.internal.query.backend.querydsl.QuerydslTupleImpl; import org.junit.Assert; import org.junit.Before; @@ -13,14 +14,20 @@ public class QueryDslTupleImplTest { private QuerydslTupleImpl impl; + private Expression expression; @Before public void setup() { + + expression = Mockito.mock(Expression.class); Tuple tuple = Mockito.mock(Tuple.class); Mockito.when(tuple.size()).thenReturn(2); + Mockito.when(tuple.get(expression)).thenReturn("test"); Mockito.when(tuple.toArray()).thenReturn(new Object[]{"0", "1"}); Mockito.when(tuple.get(0, String.class)).thenReturn("0"); Mockito.when(tuple.get(1, String.class)).thenReturn("1"); + + Mockito.when(tuple.size()).thenReturn(2); Map selectionBindings = new HashMap<>(); impl = new QuerydslTupleImpl(tuple, selectionBindings); } @@ -34,5 +41,8 @@ public void testReduce() { Assert.assertEquals("1", impl.get(0, String.class)); Assert.assertEquals(1, impl.size()); Assert.assertArrayEquals(new Object[]{"1"}, impl.toArray()); + + Assert.assertEquals("test", impl.get(expression)); + } } diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/query/criteria/BasicCriteriaTest.java b/crnk-jpa/src/test/java/io/crnk/jpa/query/criteria/BasicCriteriaTest.java index d0a3721d5..b3e3f021b 100644 --- a/crnk-jpa/src/test/java/io/crnk/jpa/query/criteria/BasicCriteriaTest.java +++ b/crnk-jpa/src/test/java/io/crnk/jpa/query/criteria/BasicCriteriaTest.java @@ -6,6 +6,7 @@ import io.crnk.jpa.query.JpaQuery; import io.crnk.jpa.query.JpaQueryFactory; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import javax.persistence.EntityManager; @@ -19,6 +20,12 @@ protected JpaQueryFactory createQueryFactory(EntityManager em) { return JpaCriteriaQueryFactory.newInstance(); } + @Test + @Ignore + public void testEqualsInCollectionFilter() { + // TODO invalid SQL generated, maybe due to use of list? + } + @Test public void testSetCached() { JpaQuery builder = queryFactory.query(TestEntity.class); diff --git a/crnk-jpa/src/test/java/io/crnk/jpa/repository/JpaEntityRepositoryTestBase.java b/crnk-jpa/src/test/java/io/crnk/jpa/repository/JpaEntityRepositoryTestBase.java index 1d1e677ca..a02f10cec 100644 --- a/crnk-jpa/src/test/java/io/crnk/jpa/repository/JpaEntityRepositoryTestBase.java +++ b/crnk-jpa/src/test/java/io/crnk/jpa/repository/JpaEntityRepositoryTestBase.java @@ -5,6 +5,7 @@ import io.crnk.core.resource.meta.PagedMetaInformation; import io.crnk.jpa.JpaEntityRepository; import io.crnk.jpa.JpaRepositoryConfig; +import io.crnk.jpa.model.FieldOnlyEntity; import io.crnk.jpa.model.RelatedEntity; import io.crnk.jpa.model.SequenceEntity; import io.crnk.jpa.model.TestEntity; @@ -12,6 +13,7 @@ import org.hibernate.Hibernate; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.springframework.transaction.annotation.Transactional; @@ -31,10 +33,15 @@ public void setup() { } @Test - public void testGetEntityType() throws InstantiationException, IllegalAccessException { + public void testGetResourceType() throws InstantiationException, IllegalAccessException { Assert.assertEquals(TestEntity.class, repo.getResourceClass()); } + @Test + public void testGetEntityType() throws InstantiationException, IllegalAccessException { + Assert.assertEquals(TestEntity.class, repo.getEntityClass()); + } + @Test public void testFindAll() throws InstantiationException, IllegalAccessException { QuerySpec querySpec = new QuerySpec(TestEntity.class); @@ -43,6 +50,38 @@ public void testFindAll() throws InstantiationException, IllegalAccessException Assert.assertEquals(numTestEntities, list.size()); } + @Test + public void testFindOne() throws InstantiationException, IllegalAccessException { + QuerySpec querySpec = new QuerySpec(TestEntity.class); + + TestEntity entity = repo.findOne(1L, querySpec); + Assert.assertEquals("test1", entity.getStringValue()); + } + + @Test + public void testFindAllById() throws InstantiationException, IllegalAccessException { + QuerySpec querySpec = new QuerySpec(TestEntity.class); + + ResourceList entities = repo.findAll(Arrays.asList(1L, 2L), querySpec); + Assert.assertEquals(2, entities.size()); + Assert.assertEquals("test1", entities.get(0).getStringValue()); + Assert.assertEquals("test2", entities.get(1).getStringValue()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidLimit() throws InstantiationException, IllegalAccessException { + QuerySpec querySpec = new QuerySpec(TestEntity.class); + querySpec.setLimit(Long.MAX_VALUE); + repo.findAll(querySpec); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidOffset() throws InstantiationException, IllegalAccessException { + QuerySpec querySpec = new QuerySpec(TestEntity.class); + querySpec.setOffset(Long.MAX_VALUE); + repo.findAll(querySpec); + } + @Test public void testFindAllOrderByAsc() throws InstantiationException, IllegalAccessException { testFindAllOrder(true); @@ -296,4 +335,26 @@ public void testSequencePrimaryKey() throws InstantiationException, IllegalAcces entity = sequenceRepo.save(entity); Assert.assertEquals("someUpdatedValue", entity.getStringValue()); } + + @Test + @Ignore // currently not supported + public void testFieldOnlyEntity() throws InstantiationException, IllegalAccessException { + QuerySpec querySpec = new QuerySpec(FieldOnlyEntity.class); + JpaEntityRepository fieldRepo = new JpaEntityRepository<>(module, JpaRepositoryConfig.create(FieldOnlyEntity.class)); + List list = fieldRepo.findAll(querySpec); + Assert.assertEquals(0, list.size()); + + FieldOnlyEntity entity = new FieldOnlyEntity(); + entity.id = 13L; + entity.longValue = 14L; + fieldRepo.create(entity); + + FieldOnlyEntity savedEntity = fieldRepo.findOne(13L, querySpec); + Assert.assertNotNull(savedEntity); + Assert.assertEquals(14L, savedEntity.longValue); + + fieldRepo.delete(13L); + list = fieldRepo.findAll(querySpec); + Assert.assertEquals(0, list.size()); + } } diff --git a/crnk-meta/src/main/java/io/crnk/meta/MetaLookup.java b/crnk-meta/src/main/java/io/crnk/meta/MetaLookup.java index fccecf005..adcc6728c 100644 --- a/crnk-meta/src/main/java/io/crnk/meta/MetaLookup.java +++ b/crnk-meta/src/main/java/io/crnk/meta/MetaLookup.java @@ -1,40 +1,24 @@ package io.crnk.meta; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.sql.Timestamp; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.crnk.core.engine.internal.utils.MultivaluedMap; import io.crnk.core.engine.internal.utils.PreconditionUtil; import io.crnk.core.module.Module.ModuleContext; -import io.crnk.meta.model.MetaArrayType; -import io.crnk.meta.model.MetaElement; -import io.crnk.meta.model.MetaEnumType; -import io.crnk.meta.model.MetaListType; -import io.crnk.meta.model.MetaLiteral; -import io.crnk.meta.model.MetaMapType; -import io.crnk.meta.model.MetaPrimitiveType; -import io.crnk.meta.model.MetaSetType; -import io.crnk.meta.model.MetaType; +import io.crnk.meta.model.*; import io.crnk.meta.provider.MetaProvider; import io.crnk.meta.provider.MetaProviderContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.sql.Timestamp; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + public class MetaLookup { private static final Logger LOGGER = LoggerFactory.getLogger(MetaLookup.class); @@ -206,10 +190,7 @@ private MetaElement getUniqueElementByType(Type type, Class metaElementClass) { - return this.allocateMeta(type, metaElementClass, true) != null; - } - private MetaElement allocateMetaFromCollectionType(ParameterizedType paramType, Class elementMetaClass) { PreconditionUtil.assertEquals("expected single type argument", 1, paramType.getActualTypeArguments().length); @@ -373,7 +349,7 @@ private MetaElement allocateMetaFromCollectionType(ParameterizedType paramType, metaSet.setImplementationType(paramType); metaSet.setElementType(elementType); return metaSet; - } + } if (isList) { PreconditionUtil.assertTrue("expected a list type", isList); MetaListType metaList = new MetaListType(); @@ -462,9 +438,8 @@ private String computeIdPrefixFromPackage(Class implClass, MetaElement elemen if (implPackage == null && implClass.isArray()) { implPackage = implClass.getComponentType().getPackage(); } - if (implPackage == null) { - throw new IllegalStateException(implClass.getName() + " does not belong to a package"); - } + PreconditionUtil.verify(implPackage != null, "%s does not belong to a package", implClass.getName()); + String packageName = implPackage.getName(); StringBuilder idInfix = new StringBuilder("."); while (true) { diff --git a/crnk-meta/src/main/java/io/crnk/meta/MetaModule.java b/crnk-meta/src/main/java/io/crnk/meta/MetaModule.java index c108b4eeb..69051a93b 100644 --- a/crnk-meta/src/main/java/io/crnk/meta/MetaModule.java +++ b/crnk-meta/src/main/java/io/crnk/meta/MetaModule.java @@ -15,18 +15,8 @@ import io.crnk.legacy.registry.DefaultResourceInformationBuilderContext; import io.crnk.meta.internal.MetaRelationshipRepository; import io.crnk.meta.internal.MetaResourceRepositoryImpl; -import io.crnk.meta.model.MetaAttribute; -import io.crnk.meta.model.MetaCollectionType; -import io.crnk.meta.model.MetaDataObject; -import io.crnk.meta.model.MetaElement; -import io.crnk.meta.model.MetaInterface; -import io.crnk.meta.model.MetaKey; -import io.crnk.meta.model.MetaListType; -import io.crnk.meta.model.MetaMapType; -import io.crnk.meta.model.MetaPrimaryKey; -import io.crnk.meta.model.MetaPrimitiveType; -import io.crnk.meta.model.MetaSetType; -import io.crnk.meta.model.MetaType; +import io.crnk.meta.model.*; +import io.crnk.meta.model.resource.MetaResource; import io.crnk.meta.provider.MetaProvider; public class MetaModule implements Module, InitializingModule { @@ -69,18 +59,21 @@ public void setupModule(ModuleContext context) { lookup.setModuleContext(context); final Set> metaClasses = new HashSet<>(); - metaClasses.add(MetaElement.class); + metaClasses.add(MetaArrayType.class); metaClasses.add(MetaAttribute.class); metaClasses.add(MetaCollectionType.class); metaClasses.add(MetaDataObject.class); + metaClasses.add(MetaElement.class); + metaClasses.add(MetaEnumType.class); + metaClasses.add(MetaInterface.class); metaClasses.add(MetaKey.class); metaClasses.add(MetaListType.class); + metaClasses.add(MetaLiteral.class); metaClasses.add(MetaMapType.class); + metaClasses.add(MetaPrimaryKey.class); metaClasses.add(MetaPrimitiveType.class); metaClasses.add(MetaSetType.class); metaClasses.add(MetaType.class); - metaClasses.add(MetaInterface.class); - metaClasses.add(MetaPrimaryKey.class); for (MetaProvider provider : lookup.getProviders()) { metaClasses.addAll(provider.getMetaTypes()); } diff --git a/crnk-meta/src/main/java/io/crnk/meta/internal/ResourceMetaProviderImpl.java b/crnk-meta/src/main/java/io/crnk/meta/internal/ResourceMetaProviderImpl.java index 83c91845a..4d24408c1 100644 --- a/crnk-meta/src/main/java/io/crnk/meta/internal/ResourceMetaProviderImpl.java +++ b/crnk-meta/src/main/java/io/crnk/meta/internal/ResourceMetaProviderImpl.java @@ -33,10 +33,7 @@ import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.concurrent.Callable; public class ResourceMetaProviderImpl extends MetaProviderBase { @@ -119,7 +116,8 @@ public void discoverElements() { ResourceRegistry resourceRegistry = context.getModuleContext().getResourceRegistry(); // enforce setup of meta data - for (RegistryEntry entry : resourceRegistry.getResources()) { + Collection entries = resourceRegistry.getResources(); + for (RegistryEntry entry : entries) { ResourceInformation information = entry.getResourceInformation(); MetaResource metaResource = context.getLookup().getMeta(information.getResourceClass(), MetaResource.class); ResourceRepositoryInformation repositoryInformation = entry.getRepositoryInformation(); @@ -139,8 +137,8 @@ private MetaResourceRepository discoverRepository(ResourceRepositoryInformation MetaResourceRepository meta = new MetaResourceRepository(); meta.setResourceType(metaResource); - meta.setName(metaResource.getName() + "Repository"); - meta.setId(metaResource.getId() + "Repository"); + meta.setName(metaResource.getName() + "$Repository"); + meta.setId(metaResource.getId() + "$Repository"); for (RepositoryAction action : repositoryInformation.getActions().values()) { MetaResourceAction metaAction = new MetaResourceAction(); @@ -299,8 +297,8 @@ private void addAttribute(MetaResourceBase resource, ResourceField field) { boolean isId = field.getResourceFieldType() == ResourceFieldType.ID; attr.setNullable(!isPrimitive && !isId); - PreconditionUtil.assertFalse(attr.getName(), - !attr.isAssociation() && MetaElement.class.isAssignableFrom(field.getElementType())); + //PreconditionUtil.assertFalse(resource.getName() + "." + attr.getName(), + // !attr.isAssociation() && MetaElement.class.isAssignableFrom(field.getElementType())); // enrich with information not available in the crnk information model if (field instanceof MetaAwareInformation) { diff --git a/crnk-meta/src/main/java/io/crnk/meta/model/resource/MetaResourceRepository.java b/crnk-meta/src/main/java/io/crnk/meta/model/resource/MetaResourceRepository.java index babe9dc9f..1a2a93985 100644 --- a/crnk-meta/src/main/java/io/crnk/meta/model/resource/MetaResourceRepository.java +++ b/crnk-meta/src/main/java/io/crnk/meta/model/resource/MetaResourceRepository.java @@ -1,16 +1,21 @@ package io.crnk.meta.model.resource; +import io.crnk.core.resource.annotations.JsonApiRelation; import io.crnk.core.resource.annotations.JsonApiResource; +import io.crnk.core.resource.annotations.SerializeType; import io.crnk.meta.model.MetaDataObject; import io.crnk.meta.model.MetaElement; @JsonApiResource(type = "meta/resourceRepository") public class MetaResourceRepository extends MetaElement { + @JsonApiRelation(serialize = SerializeType.LAZY) private MetaResource resourceType; + @JsonApiRelation(serialize = SerializeType.LAZY) private MetaDataObject listMetaType; + @JsonApiRelation(serialize = SerializeType.LAZY) private MetaDataObject listLinksType; public MetaResource getResourceType() { diff --git a/crnk-meta/src/main/java/io/crnk/meta/provider/resource/ResourceMetaProvider.java b/crnk-meta/src/main/java/io/crnk/meta/provider/resource/ResourceMetaProvider.java index 358fc7ec6..e44d39eda 100644 --- a/crnk-meta/src/main/java/io/crnk/meta/provider/resource/ResourceMetaProvider.java +++ b/crnk-meta/src/main/java/io/crnk/meta/provider/resource/ResourceMetaProvider.java @@ -2,11 +2,15 @@ import io.crnk.meta.internal.JsonObjectMetaProvider; import io.crnk.meta.internal.ResourceMetaProviderImpl; +import io.crnk.meta.model.MetaElement; +import io.crnk.meta.model.resource.*; import io.crnk.meta.provider.MetaProvider; import io.crnk.meta.provider.MetaProviderBase; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; public class ResourceMetaProvider extends MetaProviderBase { @@ -24,4 +28,9 @@ public ResourceMetaProvider(boolean useResourceRegistry) { public Collection getDependencies() { return Arrays.asList((MetaProvider) new ResourceMetaProviderImpl(useResourceRegistry), new JsonObjectMetaProvider()); } + + @Override + public Set> getMetaTypes() { + return new HashSet<>(Arrays.asList(MetaResource.class, MetaJsonObject.class, MetaResourceField.class, MetaResourceRepository.class, MetaResourceAction.class)); + } } diff --git a/crnk-meta/src/test/java/io/crnk/meta/ResourceMetaProviderTest.java b/crnk-meta/src/test/java/io/crnk/meta/ResourceMetaProviderTest.java index f1d002927..6b8956e19 100644 --- a/crnk-meta/src/test/java/io/crnk/meta/ResourceMetaProviderTest.java +++ b/crnk-meta/src/test/java/io/crnk/meta/ResourceMetaProviderTest.java @@ -447,7 +447,7 @@ public void testMultiValuedListRelation() { @Test public void testRepository() { MetaResource resourceMeta = lookup.getMeta(Schedule.class, MetaResource.class); - MetaResourceRepository meta = (MetaResourceRepository) lookup.getMetaById().get(resourceMeta.getId() + "Repository"); + MetaResourceRepository meta = (MetaResourceRepository) lookup.getMetaById().get(resourceMeta.getId() + "$Repository"); Assert.assertEquals(resourceMeta, meta.getResourceType()); Assert.assertNotNull(meta.getListLinksType()); Assert.assertNotNull(meta.getListMetaType()); diff --git a/crnk-spring/src/main/java/io/crnk/spring/boot/v3/CrnkConfigV3.java b/crnk-spring/src/main/java/io/crnk/spring/boot/v3/CrnkConfigV3.java index d39c4cebf..55da0360b 100644 --- a/crnk-spring/src/main/java/io/crnk/spring/boot/v3/CrnkConfigV3.java +++ b/crnk-spring/src/main/java/io/crnk/spring/boot/v3/CrnkConfigV3.java @@ -1,7 +1,5 @@ package io.crnk.spring.boot.v3; -import javax.servlet.Filter; - import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import io.crnk.core.boot.CrnkBoot; @@ -21,6 +19,8 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import javax.servlet.Filter; + /** * Current crnk configuration with JSON API compliance, QuerySpec and module support. * Note that there is no support for QueryParams is this version due to the lack of JSON API compatibility. @@ -31,12 +31,16 @@ public class CrnkConfigV3 implements ApplicationContextAware { private ApplicationContext applicationContext; - @Autowired private CrnkSpringBootProperties properties; - @Autowired private ObjectMapper objectMapper; + @Autowired + public CrnkConfigV3(CrnkSpringBootProperties properties, ObjectMapper objectMapper) { + this.properties = properties; + this.objectMapper = objectMapper; + } + @Bean public SpringServiceDiscovery discovery() { return new SpringServiceDiscovery(); diff --git a/crnk-spring/src/test/java/io/crnk/spring/boot/CrnkConfigV3Test.java b/crnk-spring/src/test/java/io/crnk/spring/boot/CrnkConfigV3Test.java new file mode 100644 index 000000000..a497b0a5b --- /dev/null +++ b/crnk-spring/src/test/java/io/crnk/spring/boot/CrnkConfigV3Test.java @@ -0,0 +1,54 @@ +package io.crnk.spring.boot; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.crnk.core.boot.CrnkBoot; +import io.crnk.core.boot.CrnkProperties; +import io.crnk.core.engine.properties.PropertiesProvider; +import io.crnk.core.engine.url.ConstantServiceUrlProvider; +import io.crnk.core.queryspec.DefaultQuerySpecDeserializer; +import io.crnk.spring.boot.v3.CrnkConfigV3; +import io.crnk.spring.internal.SpringServiceDiscovery; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; +import org.springframework.core.env.Environment; + +public class CrnkConfigV3Test { + + + @Test + public void checkProperties() { + ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); + Mockito.when(applicationContext.getEnvironment()).thenReturn(Mockito.mock(Environment.class)); + + CrnkSpringBootProperties properties = new CrnkSpringBootProperties(); + properties.setDomainName("testDomain"); + properties.setDefaultPageLimit(12L); + properties.setMaxPageLimit(20L); + properties.setPathPrefix("prefix"); + properties.setResourcePackage("ch.something"); + + ObjectMapper objectMapper = new ObjectMapper(); + + CrnkConfigV3 config = new CrnkConfigV3(properties, objectMapper); + config.setApplicationContext(applicationContext); + + SpringServiceDiscovery serviceDiscovery = Mockito.mock(SpringServiceDiscovery.class); + CrnkBoot boot = config.crnkBoot(serviceDiscovery); + + PropertiesProvider propertiesProvider = boot.getPropertiesProvider(); + Assert.assertEquals("testDomain", propertiesProvider.getProperty(CrnkProperties.RESOURCE_DEFAULT_DOMAIN)); + Assert.assertEquals("ch.something", propertiesProvider.getProperty(CrnkProperties.RESOURCE_SEARCH_PACKAGE)); + Assert.assertEquals("prefix", propertiesProvider.getProperty(CrnkProperties.WEB_PATH_PREFIX)); + + DefaultQuerySpecDeserializer deserializer = (DefaultQuerySpecDeserializer) boot.getQuerySpecDeserializer(); + Assert.assertEquals(12L, deserializer.getDefaultLimit().longValue()); + Assert.assertEquals(20L, deserializer.getMaxPageLimit().longValue()); + + ConstantServiceUrlProvider constantServiceUrlProvider = (ConstantServiceUrlProvider) boot.getServiceUrlProvider(); + Assert.assertEquals("testDomainprefix", constantServiceUrlProvider.getUrl()); + + Assert.assertSame(objectMapper, boot.getObjectMapper()); + } +} diff --git a/crnk-ui/src/main/java/io/crnk/ui/UIModule.java b/crnk-ui/src/main/java/io/crnk/ui/UIModule.java index 58914e731..fa8d57933 100644 --- a/crnk-ui/src/main/java/io/crnk/ui/UIModule.java +++ b/crnk-ui/src/main/java/io/crnk/ui/UIModule.java @@ -31,4 +31,8 @@ public String getModuleName() { public void setupModule(ModuleContext context) { context.addHttpRequestProcessor(new UIHttpRequestProcessor(config)); } + + public UIModuleConfig getConfig() { + return config; + } } diff --git a/crnk-ui/src/test/java/io/crnk/ui/UIModuleTest.java b/crnk-ui/src/test/java/io/crnk/ui/UIModuleTest.java index 6d913caf4..b7a8cecec 100644 --- a/crnk-ui/src/test/java/io/crnk/ui/UIModuleTest.java +++ b/crnk-ui/src/test/java/io/crnk/ui/UIModuleTest.java @@ -1,22 +1,35 @@ package io.crnk.ui; -import java.io.IOException; - import io.crnk.core.engine.http.HttpRequestContext; +import io.crnk.core.engine.http.HttpRequestProcessor; +import io.crnk.core.module.Module; import io.crnk.test.mock.ClassTestUtils; import io.crnk.ui.internal.UIHttpRequestProcessor; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; +import java.io.IOException; + public class UIModuleTest { @Test public void ui() { - UIModule module = UIModule.create(new UIModuleConfig()); + UIModuleConfig config = new UIModuleConfig(); + config.setPath("something"); + UIModule module = UIModule.create(config); Assert.assertEquals("ui", module.getModuleName()); + Assert.assertEquals("something", module.getConfig().getPath()); + } + + @Test + public void setup() { + UIModule module = UIModule.create(new UIModuleConfig()); + Module.ModuleContext context = Mockito.mock(Module.ModuleContext.class); + module.setupModule(context); + Mockito.verify(context, Mockito.times(1)).addHttpRequestProcessor(Mockito.any(HttpRequestProcessor.class)); } @Test diff --git a/crnk-validation/src/test/java/io/crnk/validation/ValidationEndToEndTest.java b/crnk-validation/src/test/java/io/crnk/validation/ValidationEndToEndTest.java index 319ebc59a..fe9e39478 100644 --- a/crnk-validation/src/test/java/io/crnk/validation/ValidationEndToEndTest.java +++ b/crnk-validation/src/test/java/io/crnk/validation/ValidationEndToEndTest.java @@ -1,18 +1,5 @@ package io.crnk.validation; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.Validation; -import javax.validation.ValidationException; -import javax.validation.Validator; -import javax.validation.ValidatorFactory; - import io.crnk.core.engine.internal.utils.StringUtils; import io.crnk.legacy.queryParams.QueryParams; import io.crnk.validation.internal.ConstraintViolationImpl; @@ -23,6 +10,9 @@ import org.junit.Assert; import org.junit.Test; +import javax.validation.*; +import java.util.*; + // TODO remo: root/leaf bean not yet available, Crnk extensions required public class ValidationEndToEndTest extends AbstractValidationTest { @@ -34,8 +24,7 @@ public void testPropertyNotNull() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); @@ -59,8 +48,7 @@ public void testListAttribute() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); @@ -88,8 +76,7 @@ public void testNestedPropertyNotNull() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); @@ -112,8 +99,7 @@ public void testListElementAttributeNotNull() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); @@ -141,8 +127,7 @@ public void testMapElementAttributeNotNull() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); @@ -171,8 +156,7 @@ public void testSetElementAttributeNotNull() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); @@ -199,8 +183,7 @@ public void testResourceObjectValidation() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); @@ -219,8 +202,7 @@ public void testValidationException() { try { projectRepo.create(project); Assert.fail(); - } - catch (ValidationException e) { + } catch (ValidationException e) { Assert.assertEquals("messageKey", e.getMessage()); } } @@ -240,8 +222,7 @@ public void testPropertyOnRelation() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); @@ -265,8 +246,7 @@ public void testRelationProperty() { try { projectRepo.create(project); Assert.fail(); - } - catch (ConstraintViolationException e) { + } catch (ConstraintViolationException e) { Set> violations = e.getConstraintViolations(); Assert.assertEquals(1, violations.size()); ConstraintViolationImpl violation = (ConstraintViolationImpl) violations.iterator().next(); diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 3baa851b2..6a575630b 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0e7bb48ea..b66845023 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon May 15 15:24:17 CEST 2017 +#Fri Apr 21 08:15:51 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip