From e55a10fb4360aba7bf23e89fdd54113d688f4dbe Mon Sep 17 00:00:00 2001 From: Adrian Palanques Garcia Date: Wed, 2 Dec 2020 14:00:56 +0100 Subject: [PATCH 1/7] Init --- .gitignore | 32 + .mvn/wrapper/MavenWrapperDownloader.java | 115 ++ .mvn/wrapper/maven-wrapper.jar | Bin 0 -> 50710 bytes .mvn/wrapper/maven-wrapper.properties | 2 + README.md | 294 +++++ .../api-test-composed-reduced.yaml | 871 +++++++++++++ .../composed-id-models/api-test-composed.yaml | 901 +++++++++++++ apigen-examples/composed-id-models/data.sql | 16 + apigen-examples/multifile-definition/api.yaml | 118 ++ .../multifile-definition/owner_api.yaml | 138 ++ .../multifile-definition/pet_api.yaml | 206 +++ .../standard_schemas.yaml | 132 ++ .../multifile-definition/tag_api.yaml | 58 + apigen-examples/other/ApigenDemoAPI.yml | 1159 +++++++++++++++++ apigen-examples/other/date-api.yaml | 195 +++ .../simple-id-models/api-test-reduced.yaml | 843 ++++++++++++ .../simple-id-models/api-test.yaml | 858 ++++++++++++ apigen-examples/simple-id-models/data.sql | 16 + archetype-core/README.md | 340 +++++ archetype-core/pom.xml | 75 ++ .../autoconfigure/ApigenApplication.java | 17 + .../autoconfigure/ApigenConfiguration.java | 62 + .../ApigenDocumentationConfiguration.java | 66 + .../autoconfigure/ApigenProperties.java | 45 + .../core/AbstractCrudService.java | 236 ++++ .../core/AbstractReadService.java | 81 ++ .../core/AbstractRelationsManager.java | 19 + .../archetypecore/core/ApigenMapper.java | 5 + .../core/advice/ApigenControllerAdvice.java | 405 ++++++ .../core/errors/ApigenError.java | 17 + .../core/errors/ApigenErrorManager.java | 13 + .../core/errors/DefaultApigenError.java | 59 + .../errors/DefaultApigenErrorManager.java | 57 + .../ApigenAbstractPersistable.java | 55 + .../core/persistence/ApigenRepository.java | 15 + .../persistence/ApigenRepositoryImpl.java | 35 + .../core/persistence/ApigenSearch.java | 34 + .../core/persistence/ApigenSearchResult.java | 21 + .../executor/ApigenSearchExecutor.java | 369 ++++++ .../persistence/executor/AttributeInfo.java | 55 + .../core/persistence/executor/EntityInfo.java | 151 +++ .../persistence/executor/EntityLookup.java | 26 + .../persistence/executor/TupleMapper.java | 186 +++ .../core/persistence/filter/Filter.java | 18 + .../persistence/filter/FilterOperation.java | 36 + .../core/persistence/filter/PropType.java | 73 ++ .../core/persistence/filter/Value.java | 39 + ...enFunctionsMetadataBuilderInitializer.java | 44 + .../persistence/pagination/Pagination.java | 15 + .../resource/ApigenEntityOutResource.java | 9 + .../core/resource/FilterResource.java | 14 + .../resource/ResourceNamingTranslator.java | 12 + .../ResourceNamingTranslatorByReflection.java | 206 +++ .../core/responses/ApiResponse.java | 99 ++ .../content/ApiListResponseContent.java | 19 + .../core/responses/metadata/Links.java | 77 ++ .../core/responses/metadata/Metadata.java | 8 + .../core/responses/metadata/Paging.java | 54 + .../core/responses/result/ApiError.java | 14 + .../core/responses/result/ApiResult.java | 28 + .../exceptions/CustomApigenException.java | 59 + .../exceptions/EntityNotFoundException.java | 16 + .../exceptions/InvalidPropertyPath.java | 25 + .../exceptions/NotImplementedException.java | 8 + .../exceptions/RelationalErrors.java | 44 + .../exceptions/RelationalErrorsException.java | 13 + .../interceptors/ApigenContext.java | 29 + .../archetypecore/interceptors/WebConfig.java | 59 + .../interceptors/expand/ApigenExpand.java | 15 + .../expand/ExpandAnnotationInterceptor.java | 46 + .../expand/ExpandInterceptor.java | 84 ++ .../expand/ExpandPathInterceptor.java | 39 + .../response/ApiResponseBodyAdvice.java | 39 + .../response/ApiResponseEnhancer.java | 69 + .../trace/TraceIdInterceptor.java | 26 + .../update/CachingRequestBodyFilter.java | 28 + .../update/UpdateRequestBodyAdvice.java | 69 + ...ernate.boot.spi.MetadataBuilderInitializer | 1 + .../apigen/archetypecore/FakeApplication.java | 14 + .../core/AbstractCrudServiceTest.java | 182 +++ .../core/AbstractReadServiceTest.java | 162 +++ .../advice/ApigenControllerAdviceTests.java | 759 +++++++++++ .../FakeApiResponseErrorsController.java | 440 +++++++ .../persistence/ApigenRepositoryTest.java | 120 ++ .../persistence/stubs/FakeEntityDates.java | 40 + .../stubs/FakeEntityDatesRepository.java | 8 + .../persistence/stubs/FakeEntityNode.java | 47 + .../stubs/FakeEntityNodeRepository.java | 8 + ...urceNamingTranslatorByReflectionTests.java | 256 ++++ .../resource/stubs/FakeResourceColor.java | 23 + .../core/resource/stubs/FakeResourceForm.java | 15 + .../responses/ApiResponseObjectMother.java | 24 + .../ApiResponseBodyAdviceTest.java | 235 ++++ .../interceptors/ExpandInterceptorTests.java | 192 +++ .../interceptors/TraceIdInterceptorTests.java | 33 + .../controller/FakeApiResultController.java | 51 + .../controller/FakeExpandController.java | 63 + .../controller/FakePaginationController.java | 39 + .../controller/FakeTraceIdController.java | 19 + .../src/test/resources/application.properties | 25 + archetype-core/src/test/resources/data.sql | 13 + archetype-parent-spring-boot/pom.xml | 146 +++ generator-core/pom.xml | 78 ++ .../generatorcore/config/Configuration.java | 43 + .../config/controller/Attribute.java | 27 + .../config/controller/Controller.java | 16 + .../config/controller/Endpoint.java | 33 + .../config/controller/Parameter.java | 14 + .../config/controller/Request.java | 15 + .../config/controller/Response.java | 22 + .../config/entity/Attribute.java | 38 + .../generatorcore/config/entity/Column.java | 45 + .../generatorcore/config/entity/Entity.java | 26 + .../generatorcore/config/entity/Relation.java | 50 + .../config/entity/RelationType.java | 10 + .../config/extractors/AbstractExtractor.java | 11 + .../extractors/AttributesExtractor.java | 133 ++ .../extractors/ConfigurationExtractor.java | 56 + .../extractors/ControllersExtractor.java | 147 +++ .../config/extractors/EntitiesExtractor.java | 194 +++ .../extractors/ParametersExtractor.java | 44 + .../config/extractors/RequestExtractor.java | 43 + .../config/extractors/ResponseExtractor.java | 99 ++ .../extractors/ValidationsExtractor.java | 133 ++ .../extractors/context/ExtractorContext.java | 39 + .../config/validation/Validation.java | 64 + .../config/validation/ValidationType.java | 157 +++ .../exceptions/DefinitionException.java | 4 + .../exceptions/ExtractorException.java | 15 + .../exceptions/GeneratorErrors.java | 21 + .../exceptions/InvalidValuesException.java | 15 + .../generator/ApigenProjectGenerator.java | 138 ++ .../generatorcore/generator/Project.java | 12 + .../generator/base/ApplicationBuilder.java | 65 + .../generator/base/ApplicationGenerator.java | 22 + .../generator/base/PomGenerator.java | 84 ++ .../base/ProjectStructureGenerator.java | 26 + .../generator/base/PropertiesGenerator.java | 73 ++ .../base/SpringBootBaseGenerator.java | 26 + .../base/SpringBootContextTestGenerator.java | 46 + .../common/AbstractClassBuilder.java | 58 + .../generator/common/AbstractGenerator.java | 34 + .../common/ApigenExt2JavapoetType.java | 62 + .../generator/common/Formats.java | 11 + .../generator/common/Members.java | 38 + .../common/Openapi2JavapoetType.java | 98 ++ .../generator/common/Validator.java | 23 + .../generator/mapper/MapperBuilder.java | 171 +++ .../generator/mapper/MappersGenerator.java | 35 + .../persistence/ComposedIdBuilder.java | 169 +++ .../generator/persistence/EntitiesData.java | 100 ++ .../persistence/EntitiesGenerator.java | 40 + .../generator/persistence/EntityBuilder.java | 299 +++++ .../persistence/EntityRelationManager.java | 181 +++ .../persistence/relations/ColumnRelation.java | 11 + .../relations/ManyToManyBuilder.java | 25 + .../relations/ManyToManyOwnerBuilder.java | 55 + .../relations/ManyToOneBuilder.java | 26 + .../relations/OneToManyBuilder.java | 27 + .../relations/OneToOneBuilder.java | 25 + .../relations/OneToOneOwnerBuilder.java | 24 + .../relations/RelatedFieldBuilder.java | 33 + .../repository/RepositoriesGenerator.java | 33 + .../repository/RepositoryBuilder.java | 69 + .../service/RelationManagerBuilder.java | 204 +++ .../service/RelationManagersGenerator.java | 28 + .../generator/service/ServiceBuilder.java | 110 ++ .../generator/service/ServicesGenerator.java | 29 + .../web/controller/ControllerBuilder.java | 71 + .../controller/ControllerBuilderFactory.java | 19 + .../web/controller/ControllersGenerator.java | 27 + .../controller/EntityControllerBuilder.java | 75 ++ .../controller/ResourceControllerBuilder.java | 32 + .../endpoints/CustomEndpointBuilder.java | 108 ++ .../endpoints/DeleteEndpointBuilder.java | 38 + .../controller/endpoints/EndpointBuilder.java | 165 +++ .../endpoints/EndpointBuilderFactory.java | 44 + .../endpoints/GetAllEndpointBuilder.java | 59 + .../endpoints/GetByIdEndpointBuilder.java | 60 + .../endpoints/PostEndpointBuilder.java | 55 + .../endpoints/PostSearchEndpointBuilder.java | 73 ++ .../endpoints/PutEndpointBuilder.java | 74 ++ .../parameters/ParameterBuilder.java | 47 + .../parameters/PathParameterBuilder.java | 34 + .../parameters/QueryParameterBuilder.java | 55 + .../web/resource/ResourceBuilder.java | 7 + .../generator/web/resource/ResourcesData.java | 23 + .../web/resource/ResourcesGenerator.java | 59 + .../input/AllInputResourceBuilder.java | 183 +++ .../resource/input/InputResourceBuilder.java | 11 + .../input/InputResourceBuilderFactory.java | 17 + .../output/EntityOutputResourceBuilder.java | 91 ++ .../output/OutputResourceBuilder.java | 7 + .../output/OutputResourceBuilderFactory.java | 24 + .../output/ResourceOutputResourceBuilder.java | 135 ++ .../response/EntityListResponseBuilder.java | 43 + .../web/response/ListResponseBuilder.java | 108 ++ .../response/ResourceListResponseBuilder.java | 52 + .../ResourceSimpleResponseBuilder.java | 72 + .../web/response/ResponseBuilder.java | 7 + .../web/response/ResponseBuilderFactory.java | 32 + .../web/response/ResponsesGenerator.java | 33 + .../web/response/SimpleResponseBuilder.java | 67 + .../generatorcore/spec/OpenAPIExtended.java | 73 ++ .../spec/components/ApigenBinding.java | 8 + .../spec/components/ApigenModel.java | 56 + .../spec/components/ApigenProject.java | 21 + .../spec/components/Extensions.java | 15 + .../utils/CustomStringUtils.java | 52 + .../apigen/generatorcore/utils/Mapping.java | 69 + .../apigen/generatorcore/utils/ZipUtils.java | 98 ++ .../config/ConfigurationObjectMother.java | 19 + .../controller/ControllerObjectMother.java | 70 + .../EndpointBaseResponseObjectMother.java | 76 ++ .../EndpointRequestObjectMother.java | 18 + .../config/entity/EntityObjectMother.java | 256 ++++ .../ConfigurationExtractorTest.java | 265 ++++ .../mapper/MapperBuilderObjectMother.java | 18 + .../parameter/ParameterObjectMother.java | 255 ++++ .../generator/base/PomFileGeneratorTest.java | 42 + .../base/ProjectStructureGeneratorTest.java | 41 + .../base/SpringBootBaseGeneratorTest.java | 52 + .../SpringBootContextTestGeneratorTest.java | 47 + .../common/ApigenExt2JavapoetTypeTests.java | 39 + .../common/Openapi2JavapoetTypeTest.java | 87 ++ .../generator/mapper/MapperBuilderTest.java | 95 ++ .../generator/mapper/MapperGeneratorTest.java | 51 + .../persistence/EntitiesGeneratorTest.java | 417 ++++++ .../repository/RepositoriesGeneratorTest.java | 40 + .../repository/RepositoryBuilderTests.java | 58 + .../service/RelationManagerBuilderTests.java | 175 +++ .../service/ServiceBuilderTests.java | 89 ++ .../service/ServicesGeneratorTest.java | 41 + .../controller/ControllersGeneratorTest.java | 37 + .../EntityControllerBuilderTests.java | 91 ++ .../endpoints/AttributeObjectMother.java | 36 + .../endpoints/CustomEndpointBuilderTests.java | 53 + .../endpoints/DeleteEndpointBuilderTests.java | 82 ++ .../EndpointBuilderFactoryTests.java | 73 ++ .../endpoints/EndpointObjectMother.java | 93 ++ .../endpoints/GetAllEndpointBuilderTests.java | 115 ++ .../GetByIdEndpointBuilderTests.java | 104 ++ .../endpoints/PostEndpointBuilderTests.java | 82 ++ .../PostSearchEndpointBuilderTests.java | 118 ++ .../endpoints/PutEndpointBuilderTests.java | 99 ++ .../parameters/ParameterBuilderTest.java | 145 +++ .../web/resource/ResourceGeneratorTest.java | 36 + .../input/AllInputResourceBuilderTests.java | 157 +++ .../EntityOutputResourceBuilderTest.java | 79 ++ .../ResourceOutputResourceBuilderTest.java | 100 ++ .../EntityListResponseBuilderTests.java | 83 ++ .../ResourceListResponseBuilderTests.java | 94 ++ .../ResourceSimpleResponseBuilderTests.java | 76 ++ .../web/response/ResponsesGeneratorTests.java | 81 ++ .../response/SimpleResponseBuilderTests.java | 68 + .../utils/CustomStringUtilsTest.java | 89 ++ .../generatorcore/utils/ZipUtilsTest.java | 35 + .../test/resources/api_fragments/common.yaml | 0 .../api_fragments/standard_request.yaml | 92 ++ .../test/resources/api_fragments/testApi.yaml | 267 ++++ generator-rest/Dockerfile | 5 + generator-rest/documentation.yaml | 482 +++++++ generator-rest/pom.xml | 74 ++ .../cloudappi/apigen/generatorrest/App.java | 11 + .../core/config/ApigenCoreConfig.java | 18 + .../core/config/ApigenProperties.java | 20 + .../config/AutoconfigureExclusionsConfig.java | 12 + .../core/config/DocumentationConfig.java | 38 + .../core/exceptions/GeneratorRestErrors.java | 16 + .../exceptions/GeneratorRestException.java | 16 + .../core/web/ExceptionAdvice.java | 78 ++ .../generatorrest/uitls/OpenAPIZipUtils.java | 38 + .../generatorrest/web/ConfigController.java | 34 + .../web/GeneratorController.java | 59 + .../src/main/resources/application.properties | 13 + .../apigen/generatorrest/AppTests.java | 13 + lombok.config | 1 + mvnw | 310 +++++ mvnw.cmd | 182 +++ pom.xml | 162 +++ 280 files changed, 25465 insertions(+) create mode 100644 .gitignore create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java create mode 100644 .mvn/wrapper/maven-wrapper.jar create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 README.md create mode 100644 apigen-examples/composed-id-models/api-test-composed-reduced.yaml create mode 100644 apigen-examples/composed-id-models/api-test-composed.yaml create mode 100644 apigen-examples/composed-id-models/data.sql create mode 100644 apigen-examples/multifile-definition/api.yaml create mode 100644 apigen-examples/multifile-definition/owner_api.yaml create mode 100644 apigen-examples/multifile-definition/pet_api.yaml create mode 100644 apigen-examples/multifile-definition/standard_schemas.yaml create mode 100644 apigen-examples/multifile-definition/tag_api.yaml create mode 100644 apigen-examples/other/ApigenDemoAPI.yml create mode 100644 apigen-examples/other/date-api.yaml create mode 100644 apigen-examples/simple-id-models/api-test-reduced.yaml create mode 100644 apigen-examples/simple-id-models/api-test.yaml create mode 100644 apigen-examples/simple-id-models/data.sql create mode 100644 archetype-core/README.md create mode 100644 archetype-core/pom.xml create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenApplication.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenConfiguration.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenProperties.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudService.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractReadService.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractRelationsManager.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/ApigenMapper.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdvice.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenError.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenErrorManager.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenError.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepository.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearch.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearchResult.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/AttributeInfo.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityInfo.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityLookup.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/TupleMapper.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Filter.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/FilterOperation.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/PropType.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Value.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/pagination/Pagination.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ApigenEntityOutResource.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/FilterResource.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslator.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponse.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/content/ApiListResponseContent.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Links.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Metadata.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Paging.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiError.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiResult.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/CustomApigenException.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/EntityNotFoundException.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/InvalidPropertyPath.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/NotImplementedException.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrors.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrorsException.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/ApigenContext.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/WebConfig.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ApigenExpand.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java create mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java create mode 100644 archetype-core/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/FakeApplication.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudServiceTest.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractReadServiceTest.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponseObjectMother.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ExpandInterceptorTests.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeApiResultController.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeExpandController.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakePaginationController.java create mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java create mode 100644 archetype-core/src/test/resources/application.properties create mode 100644 archetype-core/src/test/resources/data.sql create mode 100644 archetype-parent-spring-boot/pom.xml create mode 100644 generator-core/pom.xml create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/Configuration.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Attribute.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Controller.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Endpoint.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Parameter.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Request.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Response.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Attribute.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Column.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Entity.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Relation.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/RelationType.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AbstractExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AttributesExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ControllersExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/EntitiesExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ParametersExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/RequestExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ResponseExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ValidationsExtractor.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/context/ExtractorContext.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/Validation.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/ValidationType.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/DefinitionException.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/ExtractorException.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/GeneratorErrors.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/InvalidValuesException.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/ApigenProjectGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/Project.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PomGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PropertiesGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractClassBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Formats.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Members.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetType.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Validator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MappersGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesData.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityRelationManager.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagersGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesData.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ListResponseBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGenerator.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/OpenAPIExtended.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenBinding.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenModel.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenProject.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/Extensions.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtils.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/Mapping.java create mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/ZipUtils.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/ConfigurationObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/ControllerObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/entity/EntityObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/parameter/ParameterObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/PomFileGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilderTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtilsTest.java create mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/ZipUtilsTest.java create mode 100644 generator-core/src/test/resources/api_fragments/common.yaml create mode 100644 generator-core/src/test/resources/api_fragments/standard_request.yaml create mode 100644 generator-core/src/test/resources/api_fragments/testApi.yaml create mode 100644 generator-rest/Dockerfile create mode 100644 generator-rest/documentation.yaml create mode 100644 generator-rest/pom.xml create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/App.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenCoreConfig.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenProperties.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/DocumentationConfig.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestException.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/web/ExceptionAdvice.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/uitls/OpenAPIZipUtils.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/ConfigController.java create mode 100644 generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/GeneratorController.java create mode 100644 generator-rest/src/main/resources/application.properties create mode 100644 generator-rest/src/test/java/net/cloudappi/apigen/generatorrest/AppTests.java create mode 100644 lombok.config create mode 100644 mvnw create mode 100644 mvnw.cmd create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..962d9b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/** +!**/src/test/** +**/.flattened-pom.xml + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ + +### VS Code ### +.vscode/ diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 0000000..4500029 --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,115 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if (mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if (mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if (!outputFile.getParentFile().exists()) { + if (!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 GIT binary patch literal 50710 zcmbTd1CVCTmM+|7+wQV$+qP}n>auOywyU~q+qUhh+uxis_~*a##hm*_WW?9E7Pb7N%LRFiwbEGCJ0XP=%-6oeT$XZcYgtzC2~q zk(K08IQL8oTl}>>+hE5YRgXTB@fZ4TH9>7=79e`%%tw*SQUa9~$xKD5rS!;ZG@ocK zQdcH}JX?W|0_Afv?y`-NgLum62B&WSD$-w;O6G0Sm;SMX65z)l%m1e-g8Q$QTI;(Q z+x$xth4KFvH@Bs6(zn!iF#nenk^Y^ce;XIItAoCsow38eq?Y-Auh!1in#Rt-_D>H^ z=EjbclGGGa6VnaMGmMLj`x3NcwA43Jb(0gzl;RUIRAUDcR1~99l2SAPkVhoRMMtN} zXvC<tOmX83grD8GSo_Lo?%lNfhD#EBgPo z*nf@ppMC#B!T)Ae0RG$mlJWmGl7CkuU~B8-==5i;rS;8i6rJ=PoQxf446XDX9g|c> zU64ePyMlsI^V5Jq5A+BPe#e73+kpc_r1tv#B)~EZ;7^67F0*QiYfrk0uVW;Qb=NsG zN>gsuCwvb?s-KQIppEaeXtEMdc9dy6Dfduz-tMTms+i01{eD9JE&h?Kht*$eOl#&L zJdM_-vXs(V#$Ed;5wyNWJdPNh+Z$+;$|%qR(t`4W@kDhd*{(7-33BOS6L$UPDeE_53j${QfKN-0v-HG z(QfyvFNbwPK%^!eIo4ac1;b>c0vyf9}Xby@YY!lkz-UvNp zwj#Gg|4B~?n?G^{;(W;|{SNoJbHTMpQJ*Wq5b{l9c8(%?Kd^1?H1om1de0Da9M;Q=n zUfn{f87iVb^>Exl*nZ0hs(Yt>&V9$Pg`zX`AI%`+0SWQ4Zc(8lUDcTluS z5a_KerZWe}a-MF9#Cd^fi!y3%@RFmg&~YnYZ6<=L`UJ0v={zr)>$A;x#MCHZy1st7 ztT+N07NR+vOwSV2pvWuN1%lO!K#Pj0Fr>Q~R40{bwdL%u9i`DSM4RdtEH#cW)6}+I-eE< z&tZs+(Ogu(H_;$a$!7w`MH0r%h&@KM+<>gJL@O~2K2?VrSYUBbhCn#yy?P)uF3qWU z0o09mIik+kvzV6w>vEZy@&Mr)SgxPzUiDA&%07m17udz9usD82afQEps3$pe!7fUf z0eiidkJ)m3qhOjVHC_M(RYCBO%CZKZXFb8}s0-+}@CIn&EF(rRWUX2g^yZCvl0bI} zbP;1S)iXnRC&}5-Tl(hASKqdSnO?ASGJ*MIhOXIblmEudj(M|W!+I3eDc}7t`^mtg z)PKlaXe(OH+q-)qcQ8a@!llRrpGI8DsjhoKvw9T;TEH&?s=LH0w$EzI>%u;oD@x83 zJL7+ncjI9nn!TlS_KYu5vn%f*@qa5F;| zEFxY&B?g=IVlaF3XNm_03PA)=3|{n-UCgJoTr;|;1AU9|kPE_if8!Zvb}0q$5okF$ zHaJdmO&gg!9oN|M{!qGE=tb|3pVQ8PbL$}e;NgXz<6ZEggI}wO@aBP**2Wo=yN#ZC z4G$m^yaM9g=|&!^ft8jOLuzc3Psca*;7`;gnHm}tS0%f4{|VGEwu45KptfNmwxlE~ z^=r30gi@?cOm8kAz!EylA4G~7kbEiRlRIzwrb~{_2(x^$-?|#e6Bi_**(vyr_~9Of z!n>Gqf+Qwiu!xhi9f53=PM3`3tNF}pCOiPU|H4;pzjcsqbwg*{{kyrTxk<;mx~(;; z1NMrpaQ`57yn34>Jo3b|HROE(UNcQash!0p2-!Cz;{IRv#Vp5!3o$P8!%SgV~k&Hnqhp`5eLjTcy93cK!3Hm-$`@yGnaE=?;*2uSpiZTs_dDd51U%i z{|Zd9ou-;laGS_x=O}a+ zB||za<795A?_~Q=r=coQ+ZK@@ zId~hWQL<%)fI_WDIX#=(WNl!Dm$a&ROfLTd&B$vatq!M-2Jcs;N2vps$b6P1(N}=oI3<3luMTmC|0*{ zm1w8bt7vgX($!0@V0A}XIK)w!AzUn7vH=pZEp0RU0p?}ch2XC-7r#LK&vyc2=-#Q2 z^L%8)JbbcZ%g0Du;|8=q8B>X=mIQirpE=&Ox{TiuNDnOPd-FLI^KfEF729!!0x#Es z@>3ursjFSpu%C-8WL^Zw!7a0O-#cnf`HjI+AjVCFitK}GXO`ME&on|^=~Zc}^LBp9 zj=-vlN;Uc;IDjtK38l7}5xxQF&sRtfn4^TNtnzXv4M{r&ek*(eNbIu!u$>Ed%` z5x7+&)2P&4>0J`N&ZP8$vcR+@FS0126s6+Jx_{{`3ZrIMwaJo6jdrRwE$>IU_JTZ} z(||hyyQ)4Z1@wSlT94(-QKqkAatMmkT7pCycEB1U8KQbFX&?%|4$yyxCtm3=W`$4fiG0WU3yI@c zx{wfmkZAYE_5M%4{J-ygbpH|(|GD$2f$3o_Vti#&zfSGZMQ5_f3xt6~+{RX=$H8at z?GFG1Tmp}}lmm-R->ve*Iv+XJ@58p|1_jRvfEgz$XozU8#iJS})UM6VNI!3RUU!{5 zXB(+Eqd-E;cHQ>)`h0(HO_zLmzR3Tu-UGp;08YntWwMY-9i^w_u#wR?JxR2bky5j9 z3Sl-dQQU$xrO0xa&>vsiK`QN<$Yd%YXXM7*WOhnRdSFt5$aJux8QceC?lA0_if|s> ze{ad*opH_kb%M&~(~&UcX0nFGq^MqjxW?HJIP462v9XG>j(5Gat_)#SiNfahq2Mz2 zU`4uV8m$S~o9(W>mu*=h%Gs(Wz+%>h;R9Sg)jZ$q8vT1HxX3iQnh6&2rJ1u|j>^Qf`A76K%_ubL`Zu?h4`b=IyL>1!=*%!_K)=XC z6d}4R5L+sI50Q4P3upXQ3Z!~1ZXLlh!^UNcK6#QpYt-YC=^H=EPg3)z*wXo*024Q4b2sBCG4I# zlTFFY=kQ>xvR+LsuDUAk)q%5pEcqr(O_|^spjhtpb1#aC& zghXzGkGDC_XDa%t(X`E+kvKQ4zrQ*uuQoj>7@@ykWvF332)RO?%AA&Fsn&MNzmFa$ zWk&&^=NNjxLjrli_8ESU)}U|N{%j&TQmvY~lk!~Jh}*=^INA~&QB9em!in_X%Rl1&Kd~Z(u z9mra#<@vZQlOY+JYUwCrgoea4C8^(xv4ceCXcejq84TQ#sF~IU2V}LKc~Xlr_P=ry zl&Hh0exdCbVd^NPCqNNlxM3vA13EI8XvZ1H9#bT7y*U8Y{H8nwGpOR!e!!}*g;mJ#}T{ekSb}5zIPmye*If(}}_=PcuAW#yidAa^9-`<8Gr0 z)Fz=NiZ{)HAvw{Pl5uu)?)&i&Us$Cx4gE}cIJ}B4Xz~-q7)R_%owbP!z_V2=Aq%Rj z{V;7#kV1dNT9-6R+H}}(ED*_!F=~uz>&nR3gb^Ce%+0s#u|vWl<~JD3MvS0T9thdF zioIG3c#Sdsv;LdtRv3ml7%o$6LTVL>(H`^@TNg`2KPIk*8-IB}X!MT0`hN9Ddf7yN z?J=GxPL!uJ7lqwowsl?iRrh@#5C$%E&h~Z>XQcvFC*5%0RN-Opq|=IwX(dq(*sjs+ zqy99+v~m|6T#zR*e1AVxZ8djd5>eIeCi(b8sUk)OGjAsKSOg^-ugwl2WSL@d#?mdl zib0v*{u-?cq}dDGyZ%$XRY=UkQwt2oGu`zQneZh$=^! zj;!pCBWQNtvAcwcWIBM2y9!*W|8LmQy$H~5BEx)78J`4Z0(FJO2P^!YyQU{*Al+fs z){!4JvT1iLrJ8aU3k0t|P}{RN)_^v%$$r;+p0DY7N8CXzmS*HB*=?qaaF9D@#_$SN zSz{moAK<*RH->%r7xX~9gVW$l7?b|_SYI)gcjf0VAUJ%FcQP(TpBs; zg$25D!Ry_`8xpS_OJdeo$qh#7U+cepZ??TII7_%AXsT$B z=e)Bx#v%J0j``00Zk5hsvv6%T^*xGNx%KN-=pocSoqE5_R)OK%-Pbu^1MNzfds)mL zxz^F4lDKV9D&lEY;I+A)ui{TznB*CE$=9(wgE{m}`^<--OzV-5V4X2w9j(_!+jpTr zJvD*y6;39&T+==$F&tsRKM_lqa1HC}aGL0o`%c9mO=fts?36@8MGm7Vi{Y z^<7m$(EtdSr#22<(rm_(l_(`j!*Pu~Y>>xc>I9M#DJYDJNHO&4=HM%YLIp?;iR&$m z#_$ZWYLfGLt5FJZhr3jpYb`*%9S!zCG6ivNHYzNHcI%khtgHBliM^Ou}ZVD7ehU9 zS+W@AV=?Ro!=%AJ>Kcy9aU3%VX3|XM_K0A+ZaknKDyIS3S-Hw1C7&BSW5)sqj5Ye_ z4OSW7Yu-;bCyYKHFUk}<*<(@TH?YZPHr~~Iy%9@GR2Yd}J2!N9K&CN7Eq{Ka!jdu; zQNB*Y;i(7)OxZK%IHGt#Rt?z`I|A{q_BmoF!f^G}XVeTbe1Wnzh%1g>j}>DqFf;Rp zz7>xIs12@Ke0gr+4-!pmFP84vCIaTjqFNg{V`5}Rdt~xE^I;Bxp4)|cs8=f)1YwHz zqI`G~s2~qqDV+h02b`PQpUE#^^Aq8l%y2|ByQeXSADg5*qMprEAE3WFg0Q39`O+i1 z!J@iV!`Y~C$wJ!5Z+j5$i<1`+@)tBG$JL=!*uk=2k;T<@{|s1$YL079FvK%mPhyHV zP8^KGZnp`(hVMZ;s=n~3r2y;LTwcJwoBW-(ndU-$03{RD zh+Qn$ja_Z^OuMf3Ub|JTY74s&Am*(n{J3~@#OJNYuEVVJd9*H%)oFoRBkySGm`hx! zT3tG|+aAkXcx-2Apy)h^BkOyFTWQVeZ%e2@;*0DtlG9I3Et=PKaPt&K zw?WI7S;P)TWED7aSH$3hL@Qde?H#tzo^<(o_sv_2ci<7M?F$|oCFWc?7@KBj-;N$P zB;q!8@bW-WJY9do&y|6~mEruZAVe$!?{)N9rZZxD-|oltkhW9~nR8bLBGXw<632!l z*TYQn^NnUy%Ds}$f^=yQ+BM-a5X4^GHF=%PDrRfm_uqC zh{sKwIu|O0&jWb27;wzg4w5uA@TO_j(1X?8E>5Zfma|Ly7Bklq|s z9)H`zoAGY3n-+&JPrT!>u^qg9Evx4y@GI4$n-Uk_5wttU1_t?6><>}cZ-U+&+~JE) zPlDbO_j;MoxdLzMd~Ew|1o^a5q_1R*JZ=#XXMzg?6Zy!^hop}qoLQlJ{(%!KYt`MK z8umEN@Z4w!2=q_oe=;QttPCQy3Nm4F@x>@v4sz_jo{4m*0r%J(w1cSo;D_hQtJs7W z><$QrmG^+<$4{d2bgGo&3-FV}avg9zI|Rr(k{wTyl3!M1q+a zD9W{pCd%il*j&Ft z5H$nENf>>k$;SONGW`qo6`&qKs*T z2^RS)pXk9b@(_Fw1bkb)-oqK|v}r$L!W&aXA>IpcdNZ_vWE#XO8X`#Yp1+?RshVcd zknG%rPd*4ECEI0wD#@d+3NbHKxl}n^Sgkx==Iu%}HvNliOqVBqG?P2va zQ;kRJ$J6j;+wP9cS za#m;#GUT!qAV%+rdWolk+)6kkz4@Yh5LXP+LSvo9_T+MmiaP-eq6_k;)i6_@WSJ zlT@wK$zqHu<83U2V*yJ|XJU4farT#pAA&@qu)(PO^8PxEmPD4;Txpio+2)#!9 z>&=i7*#tc0`?!==vk>s7V+PL#S1;PwSY?NIXN2=Gu89x(cToFm))7L;< z+bhAbVD*bD=}iU`+PU+SBobTQ%S!=VL!>q$rfWsaaV}Smz>lO9JXT#`CcH_mRCSf4%YQAw`$^yY z3Y*^Nzk_g$xn7a_NO(2Eb*I=^;4f!Ra#Oo~LLjlcjke*k*o$~U#0ZXOQ5@HQ&T46l z7504MUgZkz2gNP1QFN8Y?nSEnEai^Rgyvl}xZfMUV6QrJcXp;jKGqB=D*tj{8(_pV zqyB*DK$2lgYGejmJUW)*s_Cv65sFf&pb(Yz8oWgDtQ0~k^0-wdF|tj}MOXaN@ydF8 zNr={U?=;&Z?wr^VC+`)S2xl}QFagy;$mG=TUs7Vi2wws5zEke4hTa2)>O0U?$WYsZ z<8bN2bB_N4AWd%+kncgknZ&}bM~eDtj#C5uRkp21hWW5gxWvc6b*4+dn<{c?w9Rmf zIVZKsPl{W2vQAlYO3yh}-{Os=YBnL8?uN5(RqfQ=-1cOiUnJu>KcLA*tQK3FU`_bM zM^T28w;nAj5EdAXFi&Kk1Nnl2)D!M{@+D-}bIEe+Lc4{s;YJc-{F#``iS2uk;2!Zp zF9#myUmO!wCeJIoi^A+T^e~20c+c2C}XltaR!|U-HfDA=^xF97ev}$l6#oY z&-&T{egB)&aV$3_aVA51XGiU07$s9vubh_kQG?F$FycvS6|IO!6q zq^>9|3U^*!X_C~SxX&pqUkUjz%!j=VlXDo$!2VLH!rKj@61mDpSr~7B2yy{>X~_nc zRI+7g2V&k zd**H++P9dg!-AOs3;GM`(g<+GRV$+&DdMVpUxY9I1@uK28$az=6oaa+PutlO9?6#? zf-OsgT>^@8KK>ggkUQRPPgC7zjKFR5spqQb3ojCHzj^(UH~v+!y*`Smv)VpVoPwa6 zWG18WJaPKMi*F6Zdk*kU^`i~NNTfn3BkJniC`yN98L-Awd)Z&mY? zprBW$!qL-OL7h@O#kvYnLsfff@kDIegt~?{-*5A7JrA;#TmTe?jICJqhub-G@e??D zqiV#g{)M!kW1-4SDel7TO{;@*h2=_76g3NUD@|c*WO#>MfYq6_YVUP+&8e4|%4T`w zXzhmVNziAHazWO2qXcaOu@R1MrPP{t)`N)}-1&~mq=ZH=w=;-E$IOk=y$dOls{6sRR`I5>|X zpq~XYW4sd;J^6OwOf**J>a7u$S>WTFPRkjY;BfVgQst)u4aMLR1|6%)CB^18XCz+r ztkYQ}G43j~Q&1em(_EkMv0|WEiKu;z2zhb(L%$F&xWwzOmk;VLBYAZ8lOCziNoPw1 zv2BOyXA`A8z^WH!nXhKXM`t0;6D*-uGds3TYGrm8SPnJJOQ^fJU#}@aIy@MYWz**H zvkp?7I5PE{$$|~{-ZaFxr6ZolP^nL##mHOErB^AqJqn^hFA=)HWj!m3WDaHW$C)i^ z9@6G$SzB=>jbe>4kqr#sF7#K}W*Cg-5y6kun3u&0L7BpXF9=#7IN8FOjWrWwUBZiU zT_se3ih-GBKx+Uw0N|CwP3D@-C=5(9T#BH@M`F2!Goiqx+Js5xC92|Sy0%WWWp={$(am!#l~f^W_oz78HX<0X#7 zp)p1u~M*o9W@O8P{0Qkg@Wa# z2{Heb&oX^CQSZWSFBXKOfE|tsAm#^U-WkDnU;IowZ`Ok4!mwHwH=s|AqZ^YD4!5!@ zPxJj+Bd-q6w_YG`z_+r;S86zwXb+EO&qogOq8h-Ect5(M2+>(O7n7)^dP*ws_3U6v zVsh)sk^@*c>)3EML|0<-YROho{lz@Nd4;R9gL{9|64xVL`n!m$-Jjrx?-Bacp!=^5 z1^T^eB{_)Y<9)y{-4Rz@9_>;_7h;5D+@QcbF4Wv7hu)s0&==&6u)33 zHRj+&Woq-vDvjwJCYES@$C4{$?f$Ibi4G()UeN11rgjF+^;YE^5nYprYoJNoudNj= zm1pXSeG64dcWHObUetodRn1Fw|1nI$D9z}dVEYT0lQnsf_E1x2vBLql7NrHH!n&Sq z6lc*mvU=WS6=v9Lrl}&zRiu_6u;6g%_DU{9b+R z#YHqX7`m9eydf?KlKu6Sb%j$%_jmydig`B*TN`cZL-g!R)iE?+Q5oOqBFKhx z%MW>BC^(F_JuG(ayE(MT{S3eI{cKiwOtPwLc0XO*{*|(JOx;uQOfq@lp_^cZo=FZj z4#}@e@dJ>Bn%2`2_WPeSN7si^{U#H=7N4o%Dq3NdGybrZgEU$oSm$hC)uNDC_M9xc zGzwh5Sg?mpBIE8lT2XsqTt3j3?We8}3bzLBTQd639vyg^$0#1epq8snlDJP2(BF)K zSx30RM+{f+b$g{9usIL8H!hCO117Xgv}ttPJm9wVRjPk;ePH@zxv%j9k5`TzdXLeT zFgFX`V7cYIcBls5WN0Pf6SMBN+;CrQ(|EsFd*xtwr#$R{Z9FP`OWtyNsq#mCgZ7+P z^Yn$haBJ)r96{ZJd8vlMl?IBxrgh=fdq_NF!1{jARCVz>jNdC)H^wfy?R94#MPdUjcYX>#wEx+LB#P-#4S-%YH>t-j+w zOFTI8gX$ard6fAh&g=u&56%3^-6E2tpk*wx3HSCQ+t7+*iOs zPk5ysqE}i*cQocFvA68xHfL|iX(C4h*67@3|5Qwle(8wT&!&{8*{f%0(5gH+m>$tq zp;AqrP7?XTEooYG1Dzfxc>W%*CyL16q|fQ0_jp%%Bk^k!i#Nbi(N9&T>#M{gez_Ws zYK=l}adalV(nH}I_!hNeb;tQFk3BHX7N}}R8%pek^E`X}%ou=cx8InPU1EE0|Hen- zyw8MoJqB5=)Z%JXlrdTXAE)eqLAdVE-=>wGHrkRet}>3Yu^lt$Kzu%$3#(ioY}@Gu zjk3BZuQH&~7H+C*uX^4}F*|P89JX;Hg2U!pt>rDi(n(Qe-c}tzb0#6_ItoR0->LSt zR~UT<-|@TO%O`M+_e_J4wx7^)5_%%u+J=yF_S#2Xd?C;Ss3N7KY^#-vx+|;bJX&8r zD?|MetfhdC;^2WG`7MCgs>TKKN=^=!x&Q~BzmQio_^l~LboTNT=I zC5pme^P@ER``p$2md9>4!K#vV-Fc1an7pl>_|&>aqP}+zqR?+~Z;f2^`a+-!Te%V? z;H2SbF>jP^GE(R1@%C==XQ@J=G9lKX+Z<@5}PO(EYkJh=GCv#)Nj{DkWJM2}F&oAZ6xu8&g7pn1ps2U5srwQ7CAK zN&*~@t{`31lUf`O;2w^)M3B@o)_mbRu{-`PrfNpF!R^q>yTR&ETS7^-b2*{-tZAZz zw@q5x9B5V8Qd7dZ!Ai$9hk%Q!wqbE1F1c96&zwBBaRW}(^axoPpN^4Aw}&a5dMe+*Gomky_l^54*rzXro$ z>LL)U5Ry>~FJi=*{JDc)_**c)-&faPz`6v`YU3HQa}pLtb5K)u%K+BOqXP0)rj5Au$zB zW1?vr?mDv7Fsxtsr+S6ucp2l#(4dnr9sD*v+@*>g#M4b|U?~s93>Pg{{a5|rm2xfI z`>E}?9S@|IoUX{Q1zjm5YJT|3S>&09D}|2~BiMo=z4YEjXlWh)V&qs;*C{`UMxp$9 zX)QB?G$fPD6z5_pNs>Jeh{^&U^)Wbr?2D6-q?)`*1k@!UvwQgl8eG$r+)NnFoT)L6 zg7lEh+E6J17krfYJCSjWzm67hEth24pomhz71|Qodn#oAILN)*Vwu2qpJirG)4Wnv}9GWOFrQg%Je+gNrPl8mw7ykE8{ z=|B4+uwC&bpp%eFcRU6{mxRV32VeH8XxX>v$du<$(DfinaaWxP<+Y97Z#n#U~V zVEu-GoPD=9$}P;xv+S~Ob#mmi$JQmE;Iz4(){y*9pFyW-jjgdk#oG$fl4o9E8bo|L zWjo4l%n51@Kz-n%zeSCD`uB?T%FVk+KBI}=ve zvlcS#wt`U6wrJo}6I6Rwb=1GzZfwE=I&Ne@p7*pH84XShXYJRgvK)UjQL%R9Zbm(m zxzTQsLTON$WO7vM)*vl%Pc0JH7WhP;$z@j=y#avW4X8iqy6mEYr@-}PW?H)xfP6fQ z&tI$F{NNct4rRMSHhaelo<5kTYq+(?pY)Ieh8*sa83EQfMrFupMM@nfEV@EmdHUv9 z35uzIrIuo4#WnF^_jcpC@uNNaYTQ~uZWOE6P@LFT^1@$o&q+9Qr8YR+ObBkpP9=F+$s5+B!mX2~T zAuQ6RenX?O{IlLMl1%)OK{S7oL}X%;!XUxU~xJN8xk z`xywS*naF(J#?vOpB(K=o~lE;m$zhgPWDB@=p#dQIW>xe_p1OLoWInJRKbEuoncf; zmS1!u-ycc1qWnDg5Nk2D)BY%jmOwCLC+Ny>`f&UxFowIsHnOXfR^S;&F(KXd{ODlm z$6#1ccqt-HIH9)|@fHnrKudu!6B$_R{fbCIkSIb#aUN|3RM>zuO>dpMbROZ`^hvS@ z$FU-;e4W}!ubzKrU@R*dW*($tFZ>}dd*4_mv)#O>X{U@zSzQt*83l9mI zI$8O<5AIDx`wo0}f2fsPC_l>ONx_`E7kdXu{YIZbp1$(^oBAH({T~&oQ&1{X951QW zmhHUxd)t%GQ9#ak5fTjk-cahWC;>^Rg7(`TVlvy0W@Y!Jc%QL3Ozu# zDPIqBCy&T2PWBj+d-JA-pxZlM=9ja2ce|3B(^VCF+a*MMp`(rH>Rt6W1$;r{n1(VK zLs>UtkT43LR2G$AOYHVailiqk7naz2yZGLo*xQs!T9VN5Q>eE(w zw$4&)&6xIV$IO^>1N-jrEUg>O8G4^@y+-hQv6@OmF@gy^nL_n1P1-Rtyy$Bl;|VcV zF=p*&41-qI5gG9UhKmmnjs932!6hceXa#-qfK;3d*a{)BrwNFeKU|ge?N!;zk+kB! zMD_uHJR#%b54c2tr~uGPLTRLg$`fupo}cRJeTwK;~}A>(Acy4k-Xk&Aa1&eWYS1ULWUj@fhBiWY$pdfy+F z@G{OG{*v*mYtH3OdUjwEr6%_ZPZ3P{@rfbNPQG!BZ7lRyC^xlMpWH`@YRar`tr}d> z#wz87t?#2FsH-jM6m{U=gp6WPrZ%*w0bFm(T#7m#v^;f%Z!kCeB5oiF`W33W5Srdt zdU?YeOdPG@98H7NpI{(uN{FJdu14r(URPH^F6tOpXuhU7T9a{3G3_#Ldfx_nT(Hec zo<1dyhsVsTw;ZkVcJ_0-h-T3G1W@q)_Q30LNv)W?FbMH+XJ* zy=$@39Op|kZv`Rt>X`zg&at(?PO^I=X8d9&myFEx#S`dYTg1W+iE?vt#b47QwoHI9 zNP+|3WjtXo{u}VG(lLUaW0&@yD|O?4TS4dfJI`HC-^q;M(b3r2;7|FONXphw-%7~* z&;2!X17|05+kZOpQ3~3!Nb>O94b&ZSs%p)TK)n3m=4eiblVtSx@KNFgBY_xV6ts;NF;GcGxMP8OKV^h6LmSb2E#Qnw ze!6Mnz7>lE9u{AgQ~8u2zM8CYD5US8dMDX-5iMlgpE9m*s+Lh~A#P1er*rF}GHV3h z=`STo?kIXw8I<`W0^*@mB1$}pj60R{aJ7>C2m=oghKyxMbFNq#EVLgP0cH3q7H z%0?L93-z6|+jiN|@v>ix?tRBU(v-4RV`}cQH*fp|)vd3)8i9hJ3hkuh^8dz{F5-~_ zUUr1T3cP%cCaTooM8dj|4*M=e6flH0&8ve32Q)0dyisl))XkZ7Wg~N}6y`+Qi2l+e zUd#F!nJp{#KIjbQdI`%oZ`?h=5G^kZ_uN`<(`3;a!~EMsWV|j-o>c?x#;zR2ktiB! z);5rrHl?GPtr6-o!tYd|uK;Vbsp4P{v_4??=^a>>U4_aUXPWQ$FPLE4PK$T^3Gkf$ zHo&9$U&G`d(Os6xt1r?sg14n)G8HNyWa^q8#nf0lbr4A-Fi;q6t-`pAx1T*$eKM*$ z|CX|gDrk#&1}>5H+`EjV$9Bm)Njw&7-ZR{1!CJTaXuP!$Pcg69`{w5BRHysB$(tWUes@@6aM69kb|Lx$%BRY^-o6bjH#0!7b;5~{6J+jKxU!Kmi# zndh@+?}WKSRY2gZ?Q`{(Uj|kb1%VWmRryOH0T)f3cKtG4oIF=F7RaRnH0Rc_&372={_3lRNsr95%ZO{IX{p@YJ^EI%+gvvKes5cY+PE@unghjdY5#9A!G z70u6}?zmd?v+{`vCu-53_v5@z)X{oPC@P)iA3jK$`r zSA2a7&!^zmUiZ82R2=1cumBQwOJUPz5Ay`RLfY(EiwKkrx%@YN^^XuET;tE zmr-6~I7j!R!KrHu5CWGSChO6deaLWa*9LLJbcAJsFd%Dy>a!>J`N)Z&oiU4OEP-!Ti^_!p}O?7`}i7Lsf$-gBkuY*`Zb z7=!nTT;5z$_5$=J=Ko+Cp|Q0J=%oFr>hBgnL3!tvFoLNhf#D0O=X^h+x08iB;@8pXdRHxX}6R4k@i6%vmsQwu^5z zk1ip`#^N)^#Lg#HOW3sPI33xqFB4#bOPVnY%d6prwxf;Y-w9{ky4{O6&94Ra8VN@K zb-lY;&`HtxW@sF!doT5T$2&lIvJpbKGMuDAFM#!QPXW87>}=Q4J3JeXlwHys?!1^#37q_k?N@+u&Ns20pEoBeZC*np;i;M{2C0Z4_br2gsh6eL z#8`#sn41+$iD?^GL%5?cbRcaa-Nx0vE(D=*WY%rXy3B%gNz0l?#noGJGP728RMY#q z=2&aJf@DcR?QbMmN)ItUe+VM_U!ryqA@1VVt$^*xYt~-qvW!J4Tp<-3>jT=7Zow5M z8mSKp0v4b%a8bxFr>3MwZHSWD73D@+$5?nZAqGM#>H@`)mIeC#->B)P8T$zh-Pxnc z8)~Zx?TWF4(YfKuF3WN_ckpCe5;x4V4AA3(i$pm|78{%!q?|~*eH0f=?j6i)n~Hso zmTo>vqEtB)`%hP55INf7HM@taH)v`Fw40Ayc*R!T?O{ziUpYmP)AH`euTK!zg9*6Z z!>M=$3pd0!&TzU=hc_@@^Yd3eUQpX4-33}b{?~5t5lgW=ldJ@dUAH%`l5US1y_`40 zs(X`Qk}vvMDYYq+@Rm+~IyCX;iD~pMgq^KY)T*aBz@DYEB={PxA>)mI6tM*sx-DmGQHEaHwRrAmNjO!ZLHO4b;;5mf@zzlPhkP($JeZGE7 z?^XN}Gf_feGoG~BjUgVa*)O`>lX=$BSR2)uD<9 z>o^|nb1^oVDhQbfW>>!;8-7<}nL6L^V*4pB=>wwW+RXAeRvKED(n1;R`A6v$6gy0I(;Vf?!4;&sgn7F%LpM}6PQ?0%2Z@b{It<(G1CZ|>913E0nR2r^Pa*Bp z@tFGi*CQ~@Yc-?{cwu1 zsilf=k^+Qs>&WZG(3WDixisHpR>`+ihiRwkL(3T|=xsoNP*@XX3BU8hr57l3k;pni zI``=3Nl4xh4oDj<%>Q1zYXHr%Xg_xrK3Nq?vKX3|^Hb(Bj+lONTz>4yhU-UdXt2>j z<>S4NB&!iE+ao{0Tx^N*^|EZU;0kJkx@zh}S^P{ieQjGl468CbC`SWnwLRYYiStXm zOxt~Rb3D{dz=nHMcY)#r^kF8|q8KZHVb9FCX2m^X*(|L9FZg!5a7((!J8%MjT$#Fs)M1Pb zq6hBGp%O1A+&%2>l0mpaIzbo&jc^!oN^3zxap3V2dNj3x<=TwZ&0eKX5PIso9j1;e zwUg+C&}FJ`k(M|%%}p=6RPUq4sT3-Y;k-<68ciZ~_j|bt>&9ZLHNVrp#+pk}XvM{8 z`?k}o-!if>hVlCP9j%&WI2V`5SW)BCeR5>MQhF)po=p~AYN%cNa_BbV6EEh_kk^@a zD>4&>uCGCUmyA-c)%DIcF4R6!>?6T~Mj_m{Hpq`*(wj>foHL;;%;?(((YOxGt)Bhx zuS+K{{CUsaC++%}S6~CJ=|vr(iIs-je)e9uJEU8ZJAz)w166q)R^2XI?@E2vUQ!R% zn@dxS!JcOimXkWJBz8Y?2JKQr>`~SmE2F2SL38$SyR1^yqj8_mkBp)o$@+3BQ~Mid z9U$XVqxX3P=XCKj0*W>}L0~Em`(vG<>srF8+*kPrw z20{z(=^w+ybdGe~Oo_i|hYJ@kZl*(9sHw#Chi&OIc?w`nBODp?ia$uF%Hs(X>xm?j zqZQ`Ybf@g#wli`!-al~3GWiE$K+LCe=Ndi!#CVjzUZ z!sD2O*;d28zkl))m)YN7HDi^z5IuNo3^w(zy8 zszJG#mp#Cj)Q@E@r-=NP2FVxxEAeOI2e=|KshybNB6HgE^(r>HD{*}S}mO>LuRGJT{*tfTzw_#+er-0${}%YPe@CMJ1Ng#j#)i)SnY@ss3gL;g zg2D~#Kpdfu#G;q1qz_TwSz1VJT(b3zby$Vk&;Y#1(A)|xj`_?i5YQ;TR%jice5E;0 zYHg;`zS5{S*9xI6o^j>rE8Ua*XhIw{_-*&@(R|C(am8__>+Ws&Q^ymy*X4~hR2b5r zm^p3sw}yv=tdyncy_Ui7{BQS732et~Z_@{-IhHDXAV`(Wlay<#hb>%H%WDi+K$862nA@BDtM#UCKMu+kM`!JHyWSi?&)A7_ z3{cyNG%a~nnH_!+;g&JxEMAmh-Z}rC!o7>OVzW&PoMyTA_g{hqXG)SLraA^OP**<7 zjWbr7z!o2n3hnx7A=2O=WL;`@9N{vQIM@&|G-ljrPvIuJHYtss0Er0fT5cMXNUf1B z7FAwBDixt0X7C3S)mPe5g`YtME23wAnbU)+AtV}z+e8G;0BP=bI;?(#|Ep!vVfDbK zvx+|CKF>yt0hWQ3drchU#XBU+HiuG*V^snFAPUp-5<#R&BUAzoB!aZ+e*KIxa26V}s6?nBK(U-7REa573wg-jqCg>H8~>O{ z*C0JL-?X-k_y%hpUFL?I>0WV{oV`Nb)nZbJG01R~AG>flIJf)3O*oB2i8~;!P?Wo_ z0|QEB*fifiL6E6%>tlAYHm2cjTFE@*<);#>689Z6S#BySQ@VTMhf9vYQyLeDg1*F} zjq>i1*x>5|CGKN{l9br3kB0EHY|k4{%^t7-uhjd#NVipUZa=EUuE5kS1_~qYX?>hJ z$}!jc9$O$>J&wnu0SgfYods^z?J4X;X7c77Me0kS-dO_VUQ39T(Kv(Y#s}Qqz-0AH z^?WRL(4RzpkD+T5FG_0NyPq-a-B7A5LHOCqwObRJi&oRi(<;OuIN7SV5PeHU$<@Zh zPozEV`dYmu0Z&Tqd>t>8JVde9#Pt+l95iHe$4Xwfy1AhI zDM4XJ;bBTTvRFtW>E+GzkN)9k!hA5z;xUOL2 zq4}zn-DP{qc^i|Y%rvi|^5k-*8;JZ~9a;>-+q_EOX+p1Wz;>i7c}M6Nv`^NY&{J-> z`(mzDJDM}QPu5i44**2Qbo(XzZ-ZDu%6vm8w@DUarqXj41VqP~ zs&4Y8F^Waik3y1fQo`bVUH;b=!^QrWb)3Gl=QVKr+6sxc=ygauUG|cm?|X=;Q)kQ8 zM(xrICifa2p``I7>g2R~?a{hmw@{!NS5`VhH8+;cV(F>B94M*S;5#O`YzZH1Z%yD? zZ61w(M`#aS-*~Fj;x|J!KM|^o;MI#Xkh0ULJcA?o4u~f%Z^16ViA27FxU5GM*rKq( z7cS~MrZ=f>_OWx8j#-Q3%!aEU2hVuTu(7`TQk-Bi6*!<}0WQi;_FpO;fhpL4`DcWp zGOw9vx0N~6#}lz(r+dxIGZM3ah-8qrqMmeRh%{z@dbUD2w15*_4P?I~UZr^anP}DB zU9CCrNiy9I3~d#&!$DX9e?A});BjBtQ7oGAyoI$8YQrkLBIH@2;lt4E^)|d6Jwj}z z&2_E}Y;H#6I4<10d_&P0{4|EUacwFHauvrjAnAm6yeR#}f}Rk27CN)vhgRqEyPMMS7zvunj2?`f;%?alsJ+-K+IzjJx>h8 zu~m_y$!J5RWAh|C<6+uiCNsOKu)E72M3xKK(a9Okw3e_*O&}7llNV!=P87VM2DkAk zci!YXS2&=P0}Hx|wwSc9JP%m8dMJA*q&VFB0yMI@5vWoAGraygwn){R+Cj6B1a2Px z5)u(K5{+;z2n*_XD!+Auv#LJEM)(~Hx{$Yb^ldQmcYF2zNH1V30*)CN_|1$v2|`LnFUT$%-tO0Eg|c5$BB~yDfzS zcOXJ$wpzVK0MfTjBJ0b$r#_OvAJ3WRt+YOLlJPYMx~qp>^$$$h#bc|`g0pF-Ao43? z>*A+8lx>}L{p(Tni2Vvk)dtzg$hUKjSjXRagj)$h#8=KV>5s)J4vGtRn5kP|AXIz! zPgbbVxW{2o4s-UM;c#We8P&mPN|DW7_uLF!a|^0S=wr6Esx9Z$2|c1?GaupU6$tb| zY_KU`(_29O_%k(;>^|6*pZURH3`@%EuKS;Ns z1lujmf;r{qAN&Q0&m{wJSZ8MeE7RM5+Sq;ul_ z`+ADrd_Um+G37js6tKsArNB}n{p*zTUxQr>3@wA;{EUbjNjlNd6$Mx zg0|MyU)v`sa~tEY5$en7^PkC=S<2@!nEdG6L=h(vT__0F=S8Y&eM=hal#7eM(o^Lu z2?^;05&|CNliYrq6gUv;|i!(W{0N)LWd*@{2q*u)}u*> z7MQgk6t9OqqXMln?zoMAJcc zMKaof_Up})q#DzdF?w^%tTI7STI^@8=Wk#enR*)&%8yje>+tKvUYbW8UAPg55xb70 zEn5&Ba~NmOJlgI#iS8W3-@N%>V!#z-ZRwfPO1)dQdQkaHsiqG|~we2ALqG7Ruup(DqSOft2RFg_X%3w?6VqvV1uzX_@F(diNVp z4{I|}35=11u$;?|JFBEE*gb;T`dy+8gWJ9~pNsecrO`t#V9jW-6mnfO@ff9od}b(3s4>p0i30gbGIv~1@a^F2kl7YO;DxmF3? zWi-RoXhzRJV0&XE@ACc?+@6?)LQ2XNm4KfalMtsc%4!Fn0rl zpHTrHwR>t>7W?t!Yc{*-^xN%9P0cs0kr=`?bQ5T*oOo&VRRu+1chM!qj%2I!@+1XF z4GWJ=7ix9;Wa@xoZ0RP`NCWw0*8247Y4jIZ>GEW7zuoCFXl6xIvz$ezsWgKdVMBH> z{o!A7f;R-@eK9Vj7R40xx)T<2$?F2E<>Jy3F;;=Yt}WE59J!1WN367 zA^6pu_zLoZIf*x031CcwotS{L8bJE(<_F%j_KJ2P_IusaZXwN$&^t716W{M6X2r_~ zaiMwdISX7Y&Qi&Uh0upS3TyEIXNDICQlT5fHXC`aji-c{U(J@qh-mWl-uMN|T&435 z5)a1dvB|oe%b2mefc=Vpm0C%IUYYh7HI*;3UdgNIz}R##(#{(_>82|zB0L*1i4B5j-xi9O4x10rs_J6*gdRBX=@VJ+==sWb&_Qc6tSOowM{BX@(zawtjl zdU!F4OYw2@Tk1L^%~JCwb|e#3CC>srRHQ*(N%!7$Mu_sKh@|*XtR>)BmWw!;8-mq7 zBBnbjwx8Kyv|hd*`5}84flTHR1Y@@uqjG`UG+jN_YK&RYTt7DVwfEDXDW4U+iO{>K zw1hr{_XE*S*K9TzzUlJH2rh^hUm2v7_XjwTuYap|>zeEDY$HOq3X4Tz^X}E9z)x4F zs+T?Ed+Hj<#jY-`Va~fT2C$=qFT-5q$@p9~0{G&eeL~tiIAHXA!f6C(rAlS^)&k<- zXU|ZVs}XQ>s5iONo~t!XXZgtaP$Iau;JT%h)>}v54yut~pykaNye4axEK#5@?TSsQ zE;Jvf9I$GVb|S`7$pG)4vgo9NXsKr?u=F!GnA%VS2z$@Z(!MR9?EPcAqi5ft)Iz6sNl`%kj+_H-X`R<>BFrBW=fSlD|{`D%@Rcbu2?%>t7i34k?Ujb)2@J-`j#4 zLK<69qcUuniIan-$A1+fR=?@+thwDIXtF1Tks@Br-xY zfB+zblrR(ke`U;6U~-;p1Kg8Lh6v~LjW@9l2P6s+?$2!ZRPX`(ZkRGe7~q(4&gEi<$ch`5kQ?*1=GSqkeV z{SA1EaW_A!t{@^UY2D^YO0(H@+kFVzZaAh0_`A`f(}G~EP~?B|%gtxu&g%^x{EYSz zk+T;_c@d;+n@$<>V%P=nk36?L!}?*=vK4>nJSm+1%a}9UlmTJTrfX4{Lb7smNQn@T zw9p2%(Zjl^bWGo1;DuMHN(djsEm)P8mEC2sL@KyPjwD@d%QnZ$ zMJ3cnn!_!iP{MzWk%PI&D?m?C(y2d|2VChluN^yHya(b`h>~GkI1y;}O_E57zOs!{ zt2C@M$^PR2U#(dZmA-sNreB@z-yb0Bf7j*yONhZG=onhx>t4)RB`r6&TP$n zgmN*)eCqvgriBO-abHQ8ECN0bw?z5Bxpx z=jF@?zFdVn?@gD5egM4o$m`}lV(CWrOKKq(sv*`mNcHcvw&Xryfw<{ch{O&qc#WCTXX6=#{MV@q#iHYba!OUY+MGeNTjP%Fj!WgM&`&RlI^=AWTOqy-o zHo9YFt!gQ*p7{Fl86>#-JLZo(b^O`LdFK~OsZBRR@6P?ad^Ujbqm_j^XycM4ZHFyg ziUbIFW#2tj`65~#2V!4z7DM8Z;fG0|APaQ{a2VNYpNotB7eZ5kp+tPDz&Lqs0j%Y4tA*URpcfi z_M(FD=fRGdqf430j}1z`O0I=;tLu81bwJXdYiN7_&a-?ly|-j*+=--XGvCq#32Gh(=|qj5F?kmihk{%M&$}udW5)DHK zF_>}5R8&&API}o0osZJRL3n~>76nUZ&L&iy^s>PMnNcYZ|9*1$v-bzbT3rpWsJ+y{ zPrg>5Zlery96Um?lc6L|)}&{992{_$J&=4%nRp9BAC6!IB=A&=tF>r8S*O-=!G(_( zwXbX_rGZgeiK*&n5E;f=k{ktyA1(;x_kiMEt0*gpp_4&(twlS2e5C?NoD{n>X2AT# zY@Zp?#!b1zNq96MQqeO*M1MMBin5v#RH52&Xd~DO6-BZLnA6xO1$sou(YJ1Dlc{WF zVa%2DyYm`V#81jP@70IJ;DX@y*iUt$MLm)ByAD$eUuji|5{ptFYq(q)mE(5bOpxjM z^Q`AHWq44SG3`_LxC9fwR)XRVIp=B%<(-lOC3jI#bb@dK(*vjom!=t|#<@dZql%>O z15y^{4tQoeW9Lu%G&V$90x6F)xN6y_oIn;!Q zs)8jT$;&;u%Y>=T3hg34A-+Y*na=|glcStr5D;&5*t5*DmD~x;zQAV5{}Ya`?RRGa zT*t9@$a~!co;pD^!J5bo?lDOWFx%)Y=-fJ+PDGc0>;=q=s?P4aHForSB+)v0WY2JH z?*`O;RHum6j%#LG)Vu#ciO#+jRC3!>T(9fr+XE7T2B7Z|0nR5jw@WG)kDDzTJ=o4~ zUpeyt7}_nd`t}j9BKqryOha{34erm)RmST)_9Aw)@ zHbiyg5n&E{_CQR@h<}34d7WM{s{%5wdty1l+KX8*?+-YkNK2Be*6&jc>@{Fd;Ps|| z26LqdI3#9le?;}risDq$K5G3yoqK}C^@-8z^wj%tdgw-6@F#Ju{Sg7+y)L?)U$ez> zoOaP$UFZ?y5BiFycir*pnaAaY+|%1%8&|(@VB)zweR%?IidwJyK5J!STzw&2RFx zZV@qeaCB01Hu#U9|1#=Msc8Pgz5P*4Lrp!Q+~(G!OiNR{qa7|r^H?FC6gVhkk3y7=uW#Sh;&>78bZ}aK*C#NH$9rX@M3f{nckYI+5QG?Aj1DM)@~z_ zw!UAD@gedTlePB*%4+55naJ8ak_;))#S;4ji!LOqY5VRI){GMwHR~}6t4g>5C_#U# ztYC!tjKjrKvRy=GAsJVK++~$|+s!w9z3H4G^mACv=EErXNSmH7qN}%PKcN|8%9=i)qS5+$L zu&ya~HW%RMVJi4T^pv?>mw*Gf<)-7gf#Qj|e#w2|v4#t!%Jk{&xlf;$_?jW*n!Pyx zkG$<18kiLOAUPuFfyu-EfWX%4jYnjBYc~~*9JEz6oa)_R|8wjZA|RNrAp%}14L7fW zi7A5Wym*K+V8pkqqO-X#3ft{0qs?KVt^)?kS>AicmeO&q+~J~ zp0YJ_P~_a8j= zsAs~G=8F=M{4GZL{|B__UorX@MRNQLn?*_gym4aW(~+i13knnk1P=khoC-ViMZk+x zLW(l}oAg1H`dU+Fv**;qw|ANDSRs>cGqL!Yw^`; zv;{E&8CNJcc)GHzTYM}f&NPw<6j{C3gaeelU#y!M)w-utYEHOCCJo|Vgp7K6C_$14 zqIrLUB0bsgz^D%V%fbo2f9#yb#CntTX?55Xy|Kps&Xek*4_r=KDZ z+`TQuv|$l}MWLzA5Ay6Cvsa^7xvwXpy?`w(6vx4XJ zWuf1bVSb#U8{xlY4+wlZ$9jjPk)X_;NFMqdgq>m&W=!KtP+6NL57`AMljW+es zzqjUjgz;V*kktJI?!NOg^s_)ph45>4UDA!Vo0hn>KZ+h-3=?Y3*R=#!fOX zP$Y~+14$f66ix?UWB_6r#fMcC^~X4R-<&OD1CSDNuX~y^YwJ>sW0j`T<2+3F9>cLo z#!j57$ll2K9(%$4>eA7(>FJX5e)pR5&EZK!IMQzOfik#FU*o*LGz~7u(8}XzIQRy- z!U7AlMTIe|DgQFmc%cHy_9^{o`eD%ja_L>ckU6$O4*U**o5uR7`FzqkU8k4gxtI=o z^P^oGFPm5jwZMI{;nH}$?p@uV8FT4r=|#GziKXK07bHJLtK}X%I0TON$uj(iJ`SY^ zc$b2CoxCQ>7LH@nxcdW&_C#fMYBtTxcg46dL{vf%EFCZ~eErMvZq&Z%Lhumnkn^4A zsx$ay(FnN7kYah}tZ@0?-0Niroa~13`?hVi6`ndno`G+E8;$<6^gsE-K3)TxyoJ4M zb6pj5=I8^FD5H@`^V#Qb2^0cx7wUz&cruA5g>6>qR5)O^t1(-qqP&1g=qvY#s&{bx zq8Hc%LsbK1*%n|Y=FfojpE;w~)G0-X4i*K3{o|J7`krhIOd*c*$y{WIKz2n2*EXEH zT{oml3Th5k*vkswuFXdGDlcLj15Nec5pFfZ*0?XHaF_lVuiB%Pv&p7z)%38}%$Gup zVTa~C8=cw%6BKn_|4E?bPNW4PT7}jZQLhDJhvf4z;~L)506IE0 zX!tWXX(QOQPRj-p80QG79t8T2^az4Zp2hOHziQlvT!|H)jv{Ixodabzv6lBj)6WRB z{)Kg@$~~(7$-az?lw$4@L%I&DI0Lo)PEJJziWP33a3azb?jyXt1v0N>2kxwA6b%l> zZqRpAo)Npi&loWbjFWtEV)783BbeIAhqyuc+~>i7aQ8shIXt)bjCWT6$~ro^>99G} z2XfmT0(|l!)XJb^E!#3z4oEGIsL(xd; zYX1`1I(cG|u#4R4T&C|m*9KB1`UzKvho5R@1eYtUL9B72{i(ir&ls8g!pD ztR|25xGaF!4z5M+U@@lQf(12?xGy`!|3E}7pI$k`jOIFjiDr{tqf0va&3pOn6Pu)% z@xtG2zjYuJXrV)DUrIF*y<1O1<$#54kZ#2;=X51J^F#0nZ0(;S$OZDt_U2bx{RZ=Q zMMdd$fH|!s{ zXq#l;{`xfV`gp&C>A`WrQU?d{!Ey5(1u*VLJt>i27aZ-^&2IIk=zP5p+{$q(K?2(b z8?9h)kvj9SF!Dr zoyF}?V|9;6abHxWk2cEvGs$-}Pg}D+ZzgkaN&$Snp%;5m%zh1E#?Wac-}x?BYlGN#U#Mek*}kek#I9XaHt?mz3*fDrRTQ#&#~xyeqJk1QJ~E$7qsw6 z?sV;|?*=-{M<1+hXoj?@-$y+(^BJ1H~wQ9G8C0#^aEAyhDduNX@haoa=PuPp zYsGv8UBfQaRHgBgLjmP^eh>fLMeh{8ic)?xz?#3kX-D#Z{;W#cd_`9OMFIaJg-=t`_3*!YDgtNQ2+QUEAJB9M{~AvT$H`E)IKmCR21H532+ata8_i_MR@ z2Xj<3w<`isF~Ah$W{|9;51ub*f4#9ziKrOR&jM{x7I_7()O@`F*5o$KtZ?fxU~g`t zUovNEVKYn$U~VX8eR)qb`7;D8pn*Pp$(otYTqL)5KH$lUS-jf}PGBjy$weoceAcPp z&5ZYB$r&P$MN{0H0AxCe4Qmd3T%M*5d4i%#!nmBCN-WU-4m4Tjxn-%j3HagwTxCZ9 z)j5vO-C7%s%D!&UfO>bi2oXiCw<-w{vVTK^rVbv#W=WjdADJy8$khnU!`ZWCIU`># zyjc^1W~pcu>@lDZ{zr6gv%)2X4n27~Ve+cQqcND%0?IFSP4sH#yIaXXYAq^z3|cg` z`I3$m%jra>e2W-=DiD@84T!cb%||k)nPmEE09NC%@PS_OLhkrX*U!cgD*;;&gIaA(DyVT4QD+q_xu z>r`tg{hiGY&DvD-)B*h+YEd+Zn)WylQl}<4>(_NlsKXCRV;a)Rcw!wtelM2_rWX`j zTh5A|i6=2BA(iMCnj_fob@*eA;V?oa4Z1kRBGaU07O70fb6-qmA$Hg$ps@^ka1=RO zTbE_2#)1bndC3VuK@e!Sftxq4=Uux}fDxXE#Q5_x=E1h>T5`DPHz zbH<_OjWx$wy7=%0!mo*qH*7N4tySm+R0~(rbus`7;+wGh;C0O%x~fEMkt!eV>U$`i z5>Q(o z=t$gPjgGh0&I7KY#k50V7DJRX<%^X z>6+ebc9efB3@eE2Tr){;?_w`vhgF>`-GDY(YkR{9RH(MiCnyRtd!LxXJ75z+?2 zGi@m^+2hKJ5sB1@Xi@s_@p_Kwbc<*LQ_`mr^Y%j}(sV_$`J(?_FWP)4NW*BIL~sR>t6 zM;qTJZ~GoY36&{h-Pf}L#y2UtR}>ZaI%A6VkU>vG4~}9^i$5WP2Tj?Cc}5oQxe2=q z8BeLa$hwCg_psjZyC2+?yX4*hJ58Wu^w9}}7X*+i5Rjqu5^@GzXiw#SUir1G1`jY% zOL=GE_ENYxhcyUrEt9XlMNP6kx6h&%6^u3@zB8KUCAa18T(R2J`%JjWZ z!{7cXaEW+Qu*iJPu+m>QqW}Lo$4Z+!I)0JNzZ&_M%=|B1yejFRM04bGAvu{=lNPd+ zJRI^DRQ(?FcVUD+bgEcAi@o(msqys9RTCG#)TjI!9~3-dc`>gW;HSJuQvH~d`MQs86R$|SKXHh zqS9Qy)u;T`>>a!$LuaE2keJV%;8g)tr&Nnc;EkvA-RanHXsy)D@XN0a>h}z2j81R; zsUNJf&g&rKpuD0WD@=dDrPHdBoK42WoBU|nMo17o(5^;M|dB4?|FsAGVrSyWcI`+FVw^vTVC`y}f(BwJl zrw3Sp151^9=}B})6@H*i4-dIN_o^br+BkcLa^H56|^2XsT0dESw2 zMX>(KqNl=x2K5=zIKg}2JpGAZu{I_IO}0$EQ5P{4zol**PCt3F4`GX}2@vr8#Y)~J zKb)gJeHcFnR@4SSh%b;c%J`l=W*40UPjF#q{<}ywv-=vHRFmDjv)NtmC zQx9qm)d%0zH&qG7AFa3VAU1S^(n8VFTC~Hb+HjYMjX8r#&_0MzlNR*mnLH5hi}`@{ zK$8qiDDvS_(L9_2vHgzEQ${DYSE;DqB!g*jhJghE&=LTnbgl&Xepo<*uRtV{2wDHN z)l;Kg$TA>Y|K8Lc&LjWGj<+bp4Hiye_@BfU(y#nF{fpR&|Ltbye?e^j0}8JC4#xi% zv29ZR%8%hk=3ZDvO-@1u8KmQ@6p%E|dlHuy#H1&MiC<*$YdLkHmR#F3ae;bKd;@*i z2_VfELG=B}JMLCO-6UQy^>RDE%K4b>c%9ki`f~Z2Qu8hO7C#t%Aeg8E%+}6P7Twtg z-)dj(w}_zFK&86KR@q9MHicUAucLVshUdmz_2@32(V`y3`&Kf8Q2I)+!n0mR=rrDU zXvv^$ho;yh*kNqJ#r1}b0|i|xRUF6;lhx$M*uG3SNLUTC@|htC z-=fsw^F%$qqz4%QdjBrS+ov}Qv!z00E+JWas>p?z@=t!WWU3K*?Z(0meTuTOC7OTx zU|kFLE0bLZ+WGcL$u4E}5dB0g`h|uwv3=H6f+{5z9oLv-=Q45+n~V4WwgO=CabjM% zBAN+RjM65(-}>Q2V#i1Na@a0`08g&y;W#@sBiX6Tpy8r}*+{RnyGUT`?XeHSqo#|J z^ww~c;ou|iyzpErDtlVU=`8N7JSu>4M z_pr9=tX0edVn9B}YFO2y(88j#S{w%E8vVOpAboK*27a7e4Ekjt0)hIX99*1oE;vex z7#%jhY=bPijA=Ce@9rRO(Vl_vnd00!^TAc<+wVvRM9{;hP*rqEL_(RzfK$er_^SN; z)1a8vo8~Dr5?;0X0J62Cusw$A*c^Sx1)dom`-)Pl7hsW4i(r*^Mw`z5K>!2ixB_mu z*Ddqjh}zceRFdmuX1akM1$3>G=#~|y?eYv(e-`Qy?bRHIq=fMaN~fB zUa6I8Rt=)jnplP>yuS+P&PxeWpJ#1$F`iqRl|jF$WL_aZFZl@kLo&d$VJtu&w?Q0O zzuXK>6gmygq(yXJy0C1SL}T8AplK|AGNUOhzlGeK_oo|haD@)5PxF}rV+5`-w{Aag zus45t=FU*{LguJ11Sr-28EZkq;!mJO7AQGih1L4rEyUmp>B!%X0YemsrV3QFvlgt* z5kwlPzaiJ+kZ^PMd-RRbl(Y?F*m`4*UIhIuf#8q>H_M=fM*L_Op-<_r zBZagV=4B|EW+KTja?srADTZXCd3Yv%^Chfpi)cg{ED${SI>InNpRj5!euKv?=Xn92 zsS&FH(*w`qLIy$doc>RE&A5R?u zzkl1sxX|{*fLpXvIW>9d<$ePROttn3oc6R!sN{&Y+>Jr@yeQN$sFR z;w6A<2-0%UA?c8Qf;sX7>>uKRBv3Ni)E9pI{uVzX|6Bb0U)`lhLE3hK58ivfRs1}d zNjlGK0hdq0qjV@q1qI%ZFMLgcpWSY~mB^LK)4GZ^h_@H+3?dAe_a~k*;9P_d7%NEFP6+ zgV(oGr*?W(ql?6SQ~`lUsjLb%MbfC4V$)1E0Y_b|OIYxz4?O|!kRb?BGrgiH5+(>s zoqM}v*;OBfg-D1l`M6T6{K`LG+0dJ1)!??G5g(2*vlNkm%Q(MPABT$r13q?|+kL4- zf)Mi5r$sn;u41aK(K#!m+goyd$c!KPl~-&-({j#D4^7hQkV3W|&>l_b!}!z?4($OA z5IrkfuT#F&S1(`?modY&I40%gtroig{YMvF{K{>5u^I51k8RriGd${z)=5k2tG zM|&Bp5kDTfb#vfuTTd?)a=>bX=lokw^y9+2LS?kwHQIWI~pYgy7 zb?A-RKVm_vM5!9?C%qYdfRAw& zAU7`up~%g=p@}pg#b7E)BFYx3g%(J36Nw(Dij!b>cMl@CSNbrW!DBDbTD4OXk!G4x zi}JBKc8HBYx$J~31PXH+4^x|UxK~(<@I;^3pWN$E=sYma@JP|8YL`L(zI6Y#c%Q{6 z*APf`DU$S4pr#_!60BH$FGViP14iJmbrzSrOkR;f3YZa{#E7Wpd@^4E-zH8EgPc-# zKWFPvh%WbqU_%ZEt`=Q?odKHc7@SUmY{GK`?40VuL~o)bS|is$Hn=<=KGHOsEC5tB zFb|q}gGlL97NUf$G$>^1b^3E18PZ~Pm9kX%*ftnolljiEt@2#F2R5ah$zbXd%V_Ev zyDd{1o_uuoBga$fB@Fw!V5F3jIr=a-ykqrK?WWZ#a(bglI_-8pq74RK*KfQ z0~Dzus7_l;pMJYf>Bk`)`S8gF!To-BdMnVw5M-pyu+aCiC5dwNH|6fgRsIKZcF&)g zr}1|?VOp}I3)IR@m1&HX1~#wsS!4iYqES zK}4J{Ei>;e3>LB#Oly>EZkW14^@YmpbgxCDi#0RgdM${&wxR+LiX}B+iRioOB0(pDKpVEI;ND?wNx>%e|m{RsqR_{(nmQ z3ZS}@t!p4a(BKx_-CYwrcyJ5u1TO9bcXti$8sy>xcLKqKCc#~UOZYD{llKTSFEjJ~ zyNWt>tLU}*>^`TvPxtP%F`ZJQw@W0^>x;!^@?k_)9#bF$j0)S3;mH-IR5y82l|%=F z2lR8zhP?XNP-ucZZ6A+o$xOyF!w;RaLHGh57GZ|TCXhJqY~GCh)aXEV$1O&$c}La1 zjuJxkY9SM4av^Hb;i7efiYaMwI%jGy`3NdY)+mcJhF(3XEiSlU3c|jMBi|;m-c?~T z+x0_@;SxcoY=(6xNgO$bBt~Pj8`-<1S|;Bsjrzw3@zSjt^JC3X3*$HI79i~!$RmTz zsblZsLYs7L$|=1CB$8qS!tXrWs!F@BVuh?kN(PvE5Av-*r^iYu+L^j^m9JG^#=m>@ z=1soa)H*w6KzoR$B8mBCXoU;f5^bVuwQ3~2LKg!yxomG1#XPmn(?YH@E~_ED+W6mxs%x{%Z<$pW`~ON1~2XjP5v(0{C{+6Dm$00tsd3w=f=ZENy zOgb-=f}|Hb*LQ$YdWg<(u7x3`PKF)B7ZfZ6;1FrNM63 z?O6tE%EiU@6%rVuwIQjvGtOofZBGZT1Sh(xLIYt9c4VI8`!=UJd2BfLjdRI#SbVAX ziT(f*RI^T!IL5Ac>ql7uduF#nuCRJ1)2bdvAyMxp-5^Ww5p#X{rb5)(X|fEhDHHW{ zw(Lfc$g;+Q`B0AiPGtmK%*aWfQQ$d!*U<|-@n2HZvCWSiw^I>#vh+LyC;aaVWGbmkENr z&kl*8o^_FW$T?rDYLO1Pyi%>@&kJKQoH2E0F`HjcN}Zlnx1ddoDA>G4Xu_jyp6vuT zPvC}pT&Owx+qB`zUeR|4G;OH(<<^_bzkjln0k40t`PQxc$7h(T8Ya~X+9gDc8Z9{Z z&y0RAU}#_kQGrM;__MK9vwIwK^aoqFhk~dK!ARf1zJqHMxF2?7-8|~yoO@_~Ed;_wvT%Vs{9RK$6uUQ|&@#6vyBsFK9eZW1Ft#D2)VpQRwpR(;x^ zdoTgMqfF9iBl%{`QDv7B0~8{8`8k`C4@cbZAXBu00v#kYl!#_Wug{)2PwD5cNp?K^ z9+|d-4z|gZ!L{57>!Ogfbzchm>J1)Y%?NThxIS8frAw@z>Zb9v%3_3~F@<=LG%r*U zaTov}{{^z~SeX!qgSYow`_5)ij*QtGp4lvF`aIGQ>@3ZTkDmsl#@^5*NGjOuu82}o zzLF~Q9SW+mP=>88%eSA1W4_W7-Q>rdq^?t=m6}^tDPaBRGFLg%ak93W!kOp#EO{6& zP%}Iff5HZQ9VW$~+9r=|Quj#z*=YwcnssS~9|ub2>v|u1JXP47vZ1&L1O%Z1DsOrDfSIMHU{VT>&>H=9}G3i@2rP+rx@eU@uE8rJNec zij~#FmuEBj03F1~ct@C@$>y)zB+tVyjV3*n`mtAhIM0$58vM9jOQC}JJOem|EpwqeMuYPxu3sv}oMS?S#o6GGK@8PN59)m&K4Dc&X% z(;XL_kKeYkafzS3Wn5DD>Yiw{LACy_#jY4op(>9q>>-*9@C0M+=b#bknAWZ37^(Ij zq>H%<@>o4a#6NydoF{_M4i4zB_KG)#PSye9bk0Ou8h%1Dtl7Q_y#7*n%g)?m>xF~( zjqvOwC;*qvN_3(*a+w2|ao0D?@okOvg8JskUw(l7n`0fncglavwKd?~l_ryKJ^Ky! zKCHkIC-o7%fFvPa$)YNh022lakMar^dgL=t#@XLyNHHw!b?%WlM)R@^!)I!smZL@k zBi=6wE5)2v&!UNV(&)oOYW(6Qa!nUjDKKBf-~Da=#^HE4(@mWk)LPvhyN3i4goB$3K8iV7uh zsv+a?#c4&NWeK(3AH;ETrMOIFgu{_@%XRwCZ;L=^8Ts)hix4Pf3yJRQ<8xb^CkdmC z?c_gB)XmRsk`9ch#tx4*hO=#qS7={~Vb4*tTf<5P%*-XMfUUYkI9T1cEF;ObfxxI-yNuA=I$dCtz3ey znVkctYD*`fUuZ(57+^B*R=Q}~{1z#2!ca?)+YsRQb+lt^LmEvZt_`=j^wqig+wz@n@ z`LIMQJT3bxMzuKg8EGBU+Q-6cs5(@5W?N>JpZL{$9VF)veF`L5%DSYTNQEypW%6$u zm_~}T{HeHj1bAlKl8ii92l9~$dm=UM21kLemA&b$;^!wB7#IKWGnF$TVq!!lBlG4 z{?Rjz?P(uvid+|i$VH?`-C&Gcb3{(~Vpg`w+O);Wk1|Mrjxrht0GfRUnZqz2MhrXa zqgVC9nemD5)H$to=~hp)c=l9?#~Z_7i~=U-`FZxb-|TR9@YCxx;Zjo-WpMNOn2)z) zFPGGVl%3N$f`gp$gPnWC+f4(rmts%fidpo^BJx72zAd7|*Xi{2VXmbOm)1`w^tm9% znM=0Fg4bDxH5PxPEm{P3#A(mxqlM7SIARP?|2&+c7qmU8kP&iApzL|F>Dz)Ixp_`O zP%xrP1M6@oYhgo$ZWwrAsYLa4 z|I;DAvJxno9HkQrhLPQk-8}=De{9U3U%)dJ$955?_AOms!9gia%)0E$Mp}$+0er@< zq7J&_SzvShM?e%V?_zUu{niL@gt5UFOjFJUJ}L?$f%eU%jUSoujr{^O=?=^{19`ON zlRIy8Uo_nqcPa6@yyz`CM?pMJ^^SN^Fqtt`GQ8Q#W4kE7`V9^LT}j#pMChl!j#g#J zr-=CCaV%xyFeQ9SK+mG(cTwW*)xa(eK;_Z(jy)woZp~> zA(4}-&VH+TEeLzPTqw&FOoK(ZjD~m{KW05fiGLe@E3Z2`rLukIDahE*`u!ubU)9`o zn^-lyht#E#-dt~S>}4y$-mSbR8{T@}22cn^refuQ08NjLOv?JiEWjyOnzk<^R5%gO zhUH_B{oz~u#IYwVnUg8?3P*#DqD8#X;%q%HY**=I>>-S|!X*-!x1{^l#OnR56O>iD zc;i;KS+t$koh)E3)w0OjWJl_aW2;xF=9D9Kr>)(5}4FqUbk# zI#$N8o0w;IChL49m9CJTzoC!|u{Ljd%ECgBOf$}&jA^$(V#P#~)`&g`H8E{uv52pp zwto`xUL-L&WTAVREEm$0g_gYPL(^vHq(*t1WCH_6alhkeW&GCZ3hL)|{O-jiFOBrF z!EW=Jej|dqQitT6!B-7&io2K)WIm~Q)v@yq%U|VpV+I?{y0@Yd%n8~-NuuM*pM~KA z85YB};IS~M(c<}4Hxx>qRK0cdl&e?t253N%vefkgds>Ubn8X}j6Vpgs>a#nFq$osY z1ZRwLqFv=+BTb=i%D2Wv>_yE0z}+niZ4?rE|*a3d7^kndWGwnFqt+iZ(7+aln<}jzbAQ(#Z2SS}3S$%Bd}^ zc9ghB%O)Z_mTZMRC&H#)I#fiLuIkGa^`4e~9oM5zKPx?zjkC&Xy0~r{;S?FS%c7w< zWbMpzc(xSw?9tGxG~_l}Acq}zjt5ClaB7-!vzqnlrX;}$#+PyQ9oU)_DfePh2E1<7 ztok6g6K^k^DuHR*iJ?jw?bs_whk|bx`dxu^nC6#e{1*m~z1eq7m}Cf$*^Eua(oi_I zAL+3opNhJteu&mWQ@kQWPucmiP)4|nFG`b2tpC;h{-PI@`+h?9v=9mn|0R-n8#t=+Z*FD(c5 zjj79Jxkgck*DV=wpFgRZuwr%}KTm+dx?RT@aUHJdaX-ODh~gByS?WGx&czAkvkg;x zrf92l8$Or_zOwJVwh>5rB`Q5_5}ef6DjS*$x30nZbuO3dijS*wvNEqTY5p1_A0gWr znH<(Qvb!os14|R)n2Ost>jS2;d1zyLHu`Svm|&dZD+PpP{Bh>U&`Md;gRl64q;>{8MJJM$?UNUd`aC>BiLe>*{ zJY15->yW+<3rLgYeTruFDtk1ovU<$(_y7#HgUq>)r0{^}Xbth}V#6?%5jeFYt;SG^ z3qF)=uWRU;Jj)Q}cpY8-H+l_n$2$6{ZR?&*IGr{>ek!69ZH0ZoJ*Ji+ezzlJ^%qL3 zO5a`6gwFw(moEzqxh=yJ9M1FTn!eo&qD#y5AZXErHs%22?A+JmS&GIolml!)rZTnUDM3YgzYfT#;OXn)`PWv3Ta z!-i|-Wojv*k&bC}_JJDjiAK(Ba|YZgUI{f}TdEOFT2+}nPmttytw7j%@bQZDV1vvj z^rp{gRkCDmYJHGrE1~e~AE!-&6B6`7UxVQuvRrfdFkGX8H~SNP_X4EodVd;lXd^>eV1jN+Tt4}Rsn)R0LxBz0c=NXU|pUe!MQQFkGBWbR3&(jLm z%RSLc#p}5_dO{GD=DEFr=Fc% z85CBF>*t!6ugI?soX(*JNxBp+-DdZ4X0LldiK}+WWGvXV(C(Ht|!3$psR=&c*HIM=BmX;pRIpz@Ale{9dhGe(U2|Giv;# zOc|;?p67J=Q(kamB*aus=|XP|m{jN^6@V*Bpm?ye56Njh#vyJqE=DweC;?Rv7faX~ zde03n^I~0B2vUmr;w^X37tVxUK?4}ifsSH5_kpKZIzpYu0;Kv}SBGfI2AKNp+VN#z`nI{UNDRbo-wqa4NEls zICRJpu)??cj^*WcZ^MAv+;bDbh~gpN$1Cor<{Y2oyIDws^JsfW^5AL$azE(T0p&pP z1Mv~6Q44R&RHoH95&OuGx2srIr<@zYJTOMKiVs;Bx3py89I87LOb@%mr`0)#;7_~Z zzcZj8?w=)>%5@HoCHE_&hnu(n_yQ-L(~VjpjjkbT7e)Dk5??fApg(d>vwLRJ-x{um z*Nt?DqTSxh_MIyogY!vf1mU1`Gld-&L)*43f6dilz`Q@HEz;+>MDDYv9u!s;WXeao zUq=TaL$P*IFgJzrGc>j1dDOd zed+=ZBo?w4mr$2)Ya}?vedDopomhW1`#P<%YOJ_j=WwClX0xJH-f@s?^tmzs_j7t!k zK@j^zS0Q|mM4tVP5Ram$VbS6|YDY&y?Q1r1joe9dj08#CM{RSMTU}(RCh`hp_Rkl- zGd|Cv~G@F{DLhCizAm9AN!^{rNs8hu!G@8RpnGx7e`-+K$ffN<0qjR zGq^$dj_Tv!n*?zOSyk5skI7JVKJ)3jysnjIu-@VSzQiP8r6MzudCU=~?v-U8yzo^7 zGf~SUTvEp+S*!X9uX!sq=o}lH;r{pzk~M*VA(uyQ`3C8!{C;)&6)95fv(cK!%Cuz$ z_Zal57H6kPN>25KNiI6z6F)jzEkh#%OqU#-__Xzy)KyH};81#N6OfX$$IXWzOn`Q& z4f$Z1t>)8&8PcYfEwY5UadU1yg+U*(1m2ZlHoC-!2?gB!!fLhmTl))D@dhvkx#+Yj z1O=LV{(T%{^IeCuFK>%QR!VZ4GnO5tK8a+thWE zg4VytZrwcS?7^ zuZfhYnB8dwd%VLO?DK7pV5Wi<(`~DYqOXn8#jUIL^)12*Dbhk4GmL_E2`WX&iT16o zk(t|hok(Y|v-wzn?4x34T)|+SfZP>fiq!><*%vnxGN~ypST-FtC+@TPv*vYv@iU!_ z@2gf|PrgQ?Ktf*9^CnJ(x*CtZVB8!OBfg0%!wL;Z8(tYYre0vcnPGlyCc$V(Ipl*P z_(J!a=o@vp^%Efme!K74(Ke7A>Y}|sxV+JL^aYa{~m%5#$$+R1? zGaQhZTTX!#s#=Xtpegqero$RNt&`4xn3g$)=y*;=N=Qai)}~`xtxI_N*#MMCIq#HFifT zz(-*m;pVH&+4bixL&Bbg)W5FN^bH87pAHp)zPkWNMfTFqS=l~AC$3FX3kQUSh_C?-ZftyClgM)o_D7cX$RGlEYblux0jv5 zTr|i-I3@ZPCGheCl~BGhImF)K4!9@?pC(gi3ozX=a!|r1)LFxy_8c&wY0<^{2cm|P zv6Y`QktY*;I)IUd5y3ne1CqpVanlY45z8hf4&$EUBnucDj16pDa4&GI&TArYhf*xh zdj>*%APH8(h~c>o@l#%T>R$e>rwVx_WUB|~V`p^JHsg*y12lzj&zF}w6W09HwB2yb z%Q~`es&(;7#*DUC_w-Dmt7|$*?TA_m;zB+-u{2;Bg{O}nV7G_@7~<)Bv8fH^G$XG8$(&{A zwXJK5LRK%M34(t$&NI~MHT{UQ9qN-V_yn|%PqC81EIiSzmMM=2zb`mIwiP_b)x+2M z7Gd`83h79j#SItpQ}luuf2uOU`my_rY5T{6P#BNlb%h%<#MZb=m@y5aW;#o1^2Z)SWo+b`y0gV^iRcZtz5!-05vF z7wNo=hc6h4hc&s@uL^jqRvD6thVYtbErDK9k!;+a0xoE0WL7zLixjn5;$fXvT=O3I zT6jI&^A7k6R{&5#lVjz#8%_RiAa2{di{`kx79K+j72$H(!ass|B%@l%KeeKchYLe_ z>!(JC2fxsv>XVen+Y42GeYPxMWqm`6F$(E<6^s|g(slNk!lL*6v^W2>f6hh^mE$s= z3D$)}{V5(Qm&A6bp%2Q}*GZ5Qrf}n7*Hr51?bJOyA-?B4vg6y_EX<*-e20h{=0Mxs zbuQGZ$fLyO5v$nQ&^kuH+mNq9O#MWSfThtH|0q1i!NrWj^S}_P;Q1OkYLW6U^?_7G zx2wg?CULj7))QU(n{$0JE%1t2dWrMi2g-Os{v|8^wK{@qlj%+1b^?NI z$}l2tjp0g>K3O+p%yK<9!XqmQ?E9>z&(|^Pi~aSRwI5x$jaA62GFz9%fmO3t3a>cq zK8Xbv=5Ps~4mKN5+Eqw12(!PEyedFXv~VLxMB~HwT1Vfo51pQ#D8e$e4pFZ{&RC2P z5gTIzl{3!&(tor^BwZfR8j4k{7Rq#`riKXP2O-Bh66#WWK2w=z;iD9GLl+3 zpHIaI4#lQ&S-xBK8PiQ%dwOh?%BO~DCo06pN7<^dnZCN@NzY{_Z1>rrB0U|nC&+!2 z2y!oBcTd2;@lzyk(B=TkyZ)zy0deK05*Q0zk+o$@nun`VI1Er7pjq>8V zNmlW{p7S^Btgb(TA}jL(uR>`0w8gHP^T~Sh5Tkip^spk4SBAhC{TZU}_Z)UJw-}zm zPq{KBm!k)?P{`-(9?LFt&YN4s%SIZ-9lJ!Ws~B%exHOeVFk3~}HewnnH(d)qkLQ_d z6h>O)pEE{vbOVw}E+jdYC^wM+AAhaI(YAibUc@B#_mDss0Ji&BK{WG`4 zOk>vSNq(Bq2IB@s>>Rxm6Wv?h;ZXkpb1l8u|+_qXWdC*jjcPCixq;!%BVPSp#hP zqo`%cNf&YoQXHC$D=D45RiT|5ngPlh?0T~?lUf*O)){K@*Kbh?3RW1j9-T?%lDk@y z4+~?wKI%Y!-=O|_IuKz|=)F;V7ps=5@g)RrE;;tvM$gUhG>jHcw2Hr@fS+k^Zr~>G z^JvPrZc}_&d_kEsqAEMTMJw!!CBw)u&ZVzmq+ZworuaE&TT>$pYsd9|g9O^0orAe8 z221?Va!l1|Y5X1Y?{G7rt1sX#qFA^?RLG^VjoxPf63;AS=_mVDfGJKg73L zsGdnTUD40y(>S##2l|W2Cy!H(@@5KBa(#gs`vlz}Y~$ot5VsqPQ{{YtjYFvIumZzt zA{CcxZLJR|4#{j7k~Tu*jkwz8QA|5G1$Cl895R`Zyp;irp1{KN){kB30O8P1W5;@bG znvX74roeMmQlUi=v9Y%(wl$ZC#9tKNFpvi3!C}f1m6Ct|l2g%psc{TJp)@yu)*e2> z((p0Fg*8gJ!|3WZke9;Z{8}&NRkv7iP=#_y-F}x^y?2m%-D_aj^)f04%mneyjo_;) z6qc_Zu$q37d~X``*eP~Q>I2gg%rrV8v=kDfpp$=%Vj}hF)^dsSWygoN(A$g*E=Do6FX?&(@F#7pbiJ`;c0c@Ul zDqW_90Wm#5f2L<(Lf3)3TeXtI7nhYwRm(F;*r_G6K@OPW4H(Y3O5SjUzBC}u3d|eQ8*8d@?;zUPE+i#QNMn=r(ap?2SH@vo*m z3HJ%XuG_S6;QbWy-l%qU;8x;>z>4pMW7>R}J%QLf%@1BY(4f_1iixd-6GlO7Vp*yU zp{VU^3?s?90i=!#>H`lxT!q8rk>W_$2~kbpz7eV{3wR|8E=8**5?qn8#n`*(bt1xRQrdGxyx2y%B$qmw#>ZV$c7%cO#%JM1lY$Y0q?Yuo> ze9KdJoiM)RH*SB%^;TAdX-zEjA7@%y=!0=Zg%iWK7jVI9b&Dk}0$Af&08KHo+ zOwDhFvA(E|ER%a^cdh@^wLUlmIv6?_3=BvX8jKk92L=Y}7Jf5OGMfh` zBdR1wFCi-i5@`9km{isRb0O%TX+f~)KNaEz{rXQa89`YIF;EN&gN)cigu6mNh>?Cm zAO&Im2flv6D{jwm+y<%WsPe4!89n~KN|7}Cb{Z;XweER73r}Qp2 zz}WP4j}U0&(uD&9yGy6`!+_v-S(yG*iytsTR#x_Rc>=6u^vnRDnf1gP{#2>`ffrAC% zTZ5WQ@hAK;P;>kX{D)mIXe4%a5p=LO1xXH@8T?mz7Q@d)$3pL{{B!2{-v70L*o1AO+|n5beiw~ zk@(>m?T3{2k2c;NWc^`4@P&Z?BjxXJ@;x1qhn)9Mn*IFdt_J-dIqx5#d`NfyfX~m( zIS~5)MfZ2Uy?_4W`47i}u0ZgPh<{D|w_d#;D}Q&U$Q-G}xM1A@1f{#%A$jh6Qp&0hQ<0bPOM z-{1Wm&p%%#eb_?x7i;bol EfAhh=DF6Tf literal 0 HcmV?d00001 diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..642d572 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..0585319 --- /dev/null +++ b/README.md @@ -0,0 +1,294 @@ +# OpenAPI Apigen Extension + +## Proyecto + +### Esquema + x-apigen-project: + name: string + description: string + version: string + java-properties: + group-id: string + artifact-id: string + +### Definición + - `x-apigen-project`: Apartado donde se define la información sobre el proyecto + - `name`: Nombre del proyecto + - `description`: Breve descripción del proyecto + - `version`: Version en la que se encuentra el proyecto + - `java-properties`: Apartado donde se define la información específica de java del proyecto + - `group-id`: Nombre del paquete inicial donde estará el proyecto, en caso de ser varias palabras, estarán separadas por `.` + - `artifact-id`: Nombre que identificará el proyecto + +### Ejemplo + x-apigen-project: + name: Colores + description: Este sería el proyecto de los colores + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app + +## Modelos + +### Esquema + x-apigen-models: + : + relational-persistence: + table: string + attributes: + - name: string + type: string [ENUM[Array, String, Boolean, Double, Float, Integer, Long, LocalDate y OffsetDateTime, ComposedID] o ] + items-type: string [] + relational-persistence: + column: string + primary-key: boolean + foreign-column: string + intermediate-table: string + owner: boolean + validations: + - type: string [ENUM[NotNull, Size, Min, Max, Email, NotEmpty, NotBlank, Positive, PositiveOrZero, Negative, NegativeOrZero, Past, PastOrPresent, Future, FutureOrPresent, Pattern, Digits, DecimalMin, DecimalMax]] + min: integer + max: integer + regex: string + value: integer | string + integer: integer + fraction: integer + inclusive: boolean + +### Definición +- `x-apigen-models`: Apartado donde se definen los modelos del proyecto + - ``: Nombre del modelo + - `relational-persistence`: Apartado en el que se indican todos los aspectos relacionados con la peristencia relacional del modelo + - `table`: Nombre de la tabla en la base de datos + - `attributes`: Apartado que contiene los atributos del modelo + - `name`: Nombre del atributo + - `type`: Tipo del atributo, los tipos soportados son: [Array, String, Boolean, Double, Float, Integer, Long, LocalDate, OffsetDateTime, ComposedID] o en caso de ser de otro modelo, el nombre de este + - `items-type`: En el caso que el valor de `type` sea un `Array`, este campo se debe definir con el nombre del modelo al que hace referencia + - `attributes`: Apartado similar a `attributes` del modelo, solo se utiliza cuando el type es ComposedID para indicar los atributos que forman parte de dicho identificador + - `relational-persistence`: Apartado en el que se indican todos los aspector relacionados con la persistencia relacional del atributo del modelo + - `column`: Nombre de la columna en la base de datos + - `columns`: Relacion de las columnas de la tabla actual a otra tabla relacionada cuando se trata de un modelo con un identificador compuesto (cada entrada se define como clave : valor donde la clave es el nombre en la tabla actual y el valor es el nombre en la tabla relacionada) + - `primary-key`: Indica si es la clave primaria, en caso de no indicarse el valor por defecto será `false` + - `foreign-column`: Indica el nombre de la columna en la tabla relacionada + - `foreign-columns`: Relacion de las columnas en la tabla relacionada respecto a la actual cuando se trata de un modelo con un identificador compuesto (cada entrada se define como clave : valor donde la clave es el nombre en la tabla relacionada y el valor es el nombre en la tabla actual) + - `intermediate-table`: Nombre de la tabla intermedia en aquellos atributos que representan una relación muchos a muchos + - `owner`: Indica si esta parte de la relación es el propietario de esta, necesario en las relaciones uno a uno o uno a muchos + - `sequence`: Apartado donde se puede indicar de forma opciónal el generador ha utilizar para la clave primaria + - `validations`: Apartado en el que se definen las validaciones del atributo + - `type`: Los tipos soportados para la validación son los siguiente [NotNull, Size, Min, Min, Email, NotEmpty, NotBlank, Positive, PositiveOrZero, Negative, NegativeOrZero, Past, PastOrPresent, Future, FutureOrPresent, Pattern, Digits, DecimalMin, DecimalMax] + - `min`: Valor utilizado cuando el valor del campo `type` es `Size` + - `max`: Valor utilizado cuando el valor del campo `type` es `Size` + - `regex`: Valor utilizado cuando el valor del campo `type` es `Pattern` + - `value`: Valor utilizado cuando el valor del campo `type` es `Min`, `Max`, `DecimalMin` o `DecimalMax` + - `integer`: Valor utilizado cuando el valor del campo `type` es `Digits`, representra cuantos números como máximo puede tener la parte entera del número + - `fraction`: Valor utilizado cuando el valor del campo `type` es `Digits`, representra cuantos números como máximo puede tener la parte decimal del número + - `inclusive`: Valor utilizado cuando el valor del campo `type` es `DecimalMin` o `DecimalMax` + +### Ejemplo + x-apigen-models: + Color: + relational-persistence: + table: colors + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + validations: + - type: Pattern + regex: [A-Z]+ + Composed: + relational-persistence: + table: composed + attributres: + - name: id + type: ComposedID + validations: + - type: NotNull + attributes: + - name: one + type: String + relational-persistence: + column: c_one + validations: + - type: NotNull + - name: two + type: Integer + relational-persistence: + column: c_two + validations: + - type: NotNull + +## Ampliación de los path + +### Esquema + paths: + : + ... + x-apigen-binding: + model: string + +### Definición + - `x-apigen-binding`: Apartado en el que se define la unión entre el endpoint y el modelo que usaremos + - `model`: En este campo podremos el nombre del modelo + +### Ejemplo + paths: + /colors: + x-apigen-binding: + model: Color + +## Ampliación del apartado schema del requestBody + +### Esquema + + schemas: + : + x-apigen-mapping: + model: string + type: object + properties: + : + type: string + x-apigen-mapping: + model: string + field: string + : + x-apigen-mapping: + model: string + +### Definición + +- `x-apigen-mapping`: Apartado en el que se definen todos los datos de mapeo entre los recursos y los modelos. Cada atributo de un recurso de entrada, incluyendo el propio recurso de entrada pueden tener definido este apartado, si no esta definido se asume que tiene los valores por defecto indicados en cada apartado. + - `model`: Nombre del modelo al que representa el recurso o atributo, si a nivel de recurso no se indica no se considera un recurso estandar de entrada + - `field`: Nombre del atributo del modelo al que se mapeará el atributo del recurso, por defecto si no se indica nada se mapeará a un atributo con el mismo nombre si existe, sino se ingnorará. Existen dos casos específicos en los que es obligatorio definir este campo: + - Cuando el nombre del atributo en el recurso y en el modelo son diferentes + ```` + nombre: + type: string + x-apigen-mapping: + field: primerNombre + ```` + - Cuando el atributo en el recurso es una abreviatura del identificador de un modelo anidado + ```` + color: + type: string + x-apigen-mapping: + model: color + field: valorCromatico.id + ```` + +## Ampliación del apartado schema de los responses + +### Precondiciones + +La extensión de Apigen para OpenAPI nos obliga a tener en cuenta una serie de condiciones para que las respuestas de los endpoints cumplan con el estandar. + +#### Tener definida una respuesta estandar + +````yaml + schemas: + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string +```` + +#### Tener definida una única respuesta estandar de colección por cada modelo + +````yaml + schemas: + standard_response_collection_: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + : + type: array + items: + $ref: "#/components/schemas/" +```` + +#### Tener definida una única respuesta estandar simple por cada modelo + +````yaml + schemas: + standard_response_: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/" +```` + +### Esquema + + schemas: + : + x-apigen-mapping: + model: string + type: object + properties: + : + type: string + x-apigen-mapping: + field: string + +### Definición +- `x-apigen-mapping`: Apartado en el que se definen todos los datos de mapeo entre los recursos y los modelos. Cada recurso de salida debe tener definido este apartado + - `model`: Nombre del modelo al que representa el recurso + - `field`: Nombre del atributo del modelo al que se representará el atributo del recurso + +### Ejemplo + + color: + x-apigen-mapping: + model: Color + type: object + properties: + json_id: + type: integer + x-apigen-mapping: + field: id + name: + type: string + forms: + $ref: "#/components/schemas/forms" \ No newline at end of file diff --git a/apigen-examples/composed-id-models/api-test-composed-reduced.yaml b/apigen-examples/composed-id-models/api-test-composed-reduced.yaml new file mode 100644 index 0000000..f89b34d --- /dev/null +++ b/apigen-examples/composed-id-models/api-test-composed-reduced.yaml @@ -0,0 +1,871 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /owners: + x-apigen-binding: + model: Owner + get: + operationId: getOwners + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owners" + + post: + operationId: postOwner + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newOwner" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /owners/{id}: + x-apigen-binding: + model: Owner + get: + operationId: getOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + put: + operationId: putOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updateOwner" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + delete: + operationId: deleteOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /tags: + x-apigen-binding: + model: Tag + get: + operationId: getTags + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_tags" + + + /pets: + x-apigen-binding: + model: Pet + get: + operationId: getPets + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + post: + operationId: postPet + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newPet" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + + /pets/{id}: + x-apigen-binding: + model: Pet + get: + operationId: getPet + parameters: + - $ref: "#/components/parameters/string_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + put: + operationId: putPet + parameters: + - $ref: "#/components/parameters/string_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updatePet" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + delete: + operationId: deletePet + parameters: + - $ref: "#/components/parameters/string_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /pets/search: + x-apigen-binding: + model: Pet + post: + operationId: searchPet + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/standard_search" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /status: + get: + operationId: getStatus + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other: + get: + operationId: getStatus2 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other/nested/nested/end: + get: + operationId: getStatus3 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /entities-related: + get: + operationId: getEntities + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pets' + /entities-related/{id}: + get: + parameters: + - $ref: '#/components/parameters/string_id_param' + operationId: getEntity + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pet' + /standard: + get: + operationId: getStandardCollection + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojos' + /standard/{id}: + get: + parameters: + - $ref: '#/components/parameters/string_id_param' + operationId: getStandardElement + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojo' + /reconfig: + post: + operationId: reconfig + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}: + post: + operationId: reconfig2 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}/nested/level: + post: + operationId: reconfig3 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/configResponse' + /nothing: + get: + operationId: nothing + responses: + '204': + description: Ok + +components: + parameters: + integer_id_param: + in: path + name: id + required: true + schema: + type: integer + format: int64 + string_id_param: + in: path + name: id + required: true + schema: + type: string + init_param: + in: query + name: $init + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + limit_param: + in: query + name: $limit + required: true + schema: + type: integer + format: int32 + minimum: 1 + default: 25 + total_param: + in: query + name: $total + required: true + schema: + type: boolean + default: false + select_param: + in: query + name: $select + required: false + schema: + type: array + items: + type: string + maxItems: 100 + exclude_param: + in: query + name: $exclude + required: false + schema: + type: array + items: + type: string + maxItems: 100 + expand_param: + in: query + name: $expand + required: false + schema: + type: array + items: + type: string + maxItems: 100 + order_by_param: + in: query + name: $orderby + required: false + schema: + type: array + items: + type: string + maxItems: 100 + id: + name: id + in: path + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 23 + schemas: + statusResponse: + type: object + properties: + value: + type: string + configRequest: + type: object + properties: + general: + type: object + properties: + name: + type: string + timeout: + type: integer + custom: + type: array + items: + type: object + properties: + key: + type: string + value: + type: string + configResponse: + type: object + properties: + values: + type: array + items: + type: object + properties: + name: + type: string + status: + type: boolean + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + standard_search: + type: object + properties: + operation: + type: string + enum: [AND, OR, GT, LT, GTEQ, LTEQ, EQ, NEQ, IN, BETWEEN, SUBSTRING, LIKE, ILIKE, NLIKE, REGEXP] + values: + type: array + items: + type: object + properties: + property: + type: string + type: + type: string + enum: [STRING, INTEGER, FLOAT, DATE, DATETIME, BOOLEAN] + value: + type: string + values: + type: array + items: + type: string + filter: + $ref: '#/components/schemas/standard_search' + + + standard_response_pets: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + standard_response_pet: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pet" + + standard_response_owners: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + owners: + type: array + items: + $ref: "#/components/schemas/owner" + + standard_response_owner: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/owner" + + standard_response_tags: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + tags: + type: array + items: + $ref: "#/components/schemas/tag" + + standard_response_tag: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/tag" + + standard_response_pojos: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pojos: + type: array + items: + $ref: "#/components/schemas/pojo" + + standard_response_pojo: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pojo" + + pojo: + type: object + properties: + values: + type: array + items: + type: object + properties: + level: + type: number + format: int64 + name: + type: string + enabled: + type: boolean + pet: + x-apigen-mapping: + model: Pet + type: object + properties: + json_id: + type: string + x-apigen-mapping: + field: id + name: + type: string + tags: + type: array + items: + $ref: "#/components/schemas/tag" + parent: + $ref: "#/components/schemas/pet" + children: + type: array + items: + $ref: "#/components/schemas/pet" + main_tag: + $ref: "#/components/schemas/tag" + owner: + $ref: "#/components/schemas/owner" + + tag: + x-apigen-mapping: + model: Tag + type: object + properties: + id: + type: string + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + owner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + newPet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + minLength: 4 + owner: + type: string + x-apigen-mapping: # Required + model: Owner + field: owner.id + parent: + x-apigen-mapping: + model: Pet + type: object + properties: + id: + type: string + tags: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + maxItems: 10 + main_tag: + x-apigen-mapping: + field: mainTag + type: object + properties: + id: + type: string + name: + type: string + required: + - name + + newOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + x-apigen-mapping: # Optional + name: id + name: + type: string + x-apigen-mapping: # Optional + name: name + + updatePet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + + updateOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + name: + type: string + + + # ------------------------------------------------- + + + x-apigen-models: + Owner: + attributes: + - name: id + type: ComposedID + attributes: + - name: idS + type: String + validations: + - type: NotNull + - name: idN + type: Long + validations: + - type: NotNull + - name: name + type: String + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-columns: + owner_id_s: id_s + owner_id_n: id_n + + Pet: + attributes: + - name: id + type: ComposedID + attributes: + - name: idS + type: String + validations: + - type: NotNull + - name: idN + type: Long + validations: + - type: NotNull + - name: name + type: String + - name: owner + type: Owner + relational-persistence: + columns: + owner_id_s: id_s + owner_id_n: id_n + - name: tags + type: Array + items-type: Tag + relational-persistence: + columns: + pet_id_s: id_s + pet_id_n: id_n + foreign-columns: + tag_id_s: id_s + tag_id_n: id_n + intermediate-table: pet_tags + owner: true + - name: parent + type: Pet + relational-persistence: + columns: + parent_id_s: id_s + parent_id_n: id_n + - name: children + type: Array + items-type: Pet + relational-persistence: + foreign-columns: + parent_id_s: id_s + parent_id_n: id_n + - name: mainTag + type: Tag + relational-persistence: + columns: + tag_id_s: id_s + tag_id_n: id_n + + Tag: + attributes: + - name: id + type: ComposedID + attributes: + - name: idS + type: String + validations: + - type: NotNull + - name: idN + type: Long + validations: + - type: NotNull + - name: name + type: String + validations: + - type: Size + min: 2 + max: 10 + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-columns: + tag_id_s: id_s + tag_id_n: id_n + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/composed-id-models/api-test-composed.yaml b/apigen-examples/composed-id-models/api-test-composed.yaml new file mode 100644 index 0000000..b9c96dc --- /dev/null +++ b/apigen-examples/composed-id-models/api-test-composed.yaml @@ -0,0 +1,901 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /owners: + x-apigen-binding: + model: Owner + get: + operationId: getOwners + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owners" + + post: + operationId: postOwner + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newOwner" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /owners/{id}: + x-apigen-binding: + model: Owner + get: + operationId: getOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + put: + operationId: putOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updateOwner" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + delete: + operationId: deleteOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /tags: + x-apigen-binding: + model: Tag + get: + operationId: getTags + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_tags" + + + /pets: + x-apigen-binding: + model: Pet + get: + operationId: getPets + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + post: + operationId: postPet + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newPet" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + + /pets/{id}: + x-apigen-binding: + model: Pet + get: + operationId: getPet + parameters: + - $ref: "#/components/parameters/string_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + put: + operationId: putPet + parameters: + - $ref: "#/components/parameters/string_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updatePet" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + delete: + operationId: deletePet + parameters: + - $ref: "#/components/parameters/string_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /pets/search: + x-apigen-binding: + model: Pet + post: + operationId: searchPet + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/standard_search" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /status: + get: + operationId: getStatus + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other: + get: + operationId: getStatus2 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other/nested/nested/end: + get: + operationId: getStatus3 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /entities-related: + get: + operationId: getEntities + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pets' + /entities-related/{id}: + get: + parameters: + - $ref: '#/components/parameters/string_id_param' + operationId: getEntity + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pet' + /standard: + get: + operationId: getStandardCollection + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojos' + /standard/{id}: + get: + parameters: + - $ref: '#/components/parameters/string_id_param' + operationId: getStandardElement + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojo' + /reconfig: + post: + operationId: reconfig + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}: + post: + operationId: reconfig2 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}/nested/level: + post: + operationId: reconfig3 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/configResponse' + /nothing: + get: + operationId: nothing + responses: + '204': + description: Ok + +components: + parameters: + integer_id_param: + in: path + name: id + required: true + schema: + type: integer + format: int64 + string_id_param: + in: path + name: id + required: true + schema: + type: string + init_param: + in: query + name: $init + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + limit_param: + in: query + name: $limit + required: true + schema: + type: integer + format: int32 + minimum: 1 + default: 25 + total_param: + in: query + name: $total + required: true + schema: + type: boolean + default: false + select_param: + in: query + name: $select + required: false + schema: + type: array + items: + type: string + maxItems: 100 + exclude_param: + in: query + name: $exclude + required: false + schema: + type: array + items: + type: string + maxItems: 100 + expand_param: + in: query + name: $expand + required: false + schema: + type: array + items: + type: string + maxItems: 100 + order_by_param: + in: query + name: $orderby + required: false + schema: + type: array + items: + type: string + maxItems: 100 + id: + name: id + in: path + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 23 + schemas: + statusResponse: + type: object + properties: + value: + type: string + configRequest: + type: object + properties: + general: + type: object + properties: + name: + type: string + timeout: + type: integer + custom: + type: array + items: + type: object + properties: + key: + type: string + value: + type: string + configResponse: + type: object + properties: + values: + type: array + items: + type: object + properties: + name: + type: string + status: + type: boolean + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + standard_search: + type: object + properties: + operation: + type: string + enum: [AND, OR, GT, LT, GTEQ, LTEQ, EQ, NEQ, IN, BETWEEN, SUBSTRING, LIKE, ILIKE, NLIKE, REGEXP] + values: + type: array + items: + type: object + properties: + property: + type: string + type: + type: string + enum: [STRING, INTEGER, FLOAT, DATE, DATETIME, BOOLEAN] + value: + type: string + values: + type: array + items: + type: string + filter: + $ref: '#/components/schemas/standard_search' + + + standard_response_pets: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + standard_response_pet: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pet" + + standard_response_owners: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + owners: + type: array + items: + $ref: "#/components/schemas/owner" + + standard_response_owner: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/owner" + + standard_response_tags: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + tags: + type: array + items: + $ref: "#/components/schemas/tag" + + standard_response_tag: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/tag" + + standard_response_pojos: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pojos: + type: array + items: + $ref: "#/components/schemas/pojo" + + standard_response_pojo: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pojo" + + pojo: + type: object + properties: + values: + type: array + items: + type: object + properties: + level: + type: number + format: int64 + name: + type: string + enabled: + type: boolean + pet: + x-apigen-mapping: + model: Pet + type: object + properties: + json_id: + type: string + x-apigen-mapping: + field: id + name: + type: string + tags: + type: array + items: + $ref: "#/components/schemas/tag" + parent: + $ref: "#/components/schemas/pet" + children: + type: array + items: + $ref: "#/components/schemas/pet" + main_tag: + $ref: "#/components/schemas/tag" + owner: + $ref: "#/components/schemas/owner" + + tag: + x-apigen-mapping: + model: Tag + type: object + properties: + id: + type: string + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + owner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + newPet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + minLength: 4 + owner: + type: string + x-apigen-mapping: # Required + model: Owner + field: owner.id + parent: + x-apigen-mapping: + model: Pet + type: object + properties: + id: + type: string + tags: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + maxItems: 10 + main_tag: + x-apigen-mapping: + field: mainTag + type: object + properties: + id: + type: string + name: + type: string + required: + - name + + newOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + x-apigen-mapping: # Optional + name: id + name: + type: string + x-apigen-mapping: # Optional + name: name + + updatePet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + + updateOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + name: + type: string + + + # ------------------------------------------------- + + + x-apigen-models: + Owner: + relational-persistence: + table: owners + attributes: + - name: id + type: ComposedID + attributes: + - name: ids + type: String + relational-persistence: + column: id_s + primary-key: true + validations: + - type: NotNull + - name: idn + type: Long + relational-persistence: + column: id_n + primary-key: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-columns: + owner_id_s: id_s + owner_id_n: id_n + + Pet: + relational-persistence: + table: pets + attributes: + - name: id + type: ComposedID + attributes: + - name: ids + type: String + relational-persistence: + column: id_s + primary-key: true + validations: + - type: NotNull + - name: idn + type: Long + relational-persistence: + column: id_n + primary-key: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + - name: owner + type: Owner + relational-persistence: + columns: + owner_id_s: id_s + owner_id_n: id_n + - name: tags + type: Array + items-type: Tag + relational-persistence: + columns: + pet_id_s: id_s + pet_id_n: id_n + foreign-columns: + tag_id_s: id_s + tag_id_n: id_n + intermediate-table: pet_tags + owner: true + - name: parent + type: Pet + relational-persistence: + columns: + parent_id_s: id_s + parent_id_n: id_n + - name: children + type: Array + items-type: Pet + relational-persistence: + foreign-columns: + parent_id_s: id_s + parent_id_n: id_n + - name: mainTag + type: Tag + relational-persistence: + columns: + tag_id_s: id_s + tag_id_n: id_n + + Tag: + relational-persistence: + table: tags + attributes: + - name: id + type: ComposedID + attributes: + - name: ids + type: String + relational-persistence: + column: id_s + primary-key: true + validations: + - type: NotNull + - name: idn + type: Long + relational-persistence: + column: id_n + primary-key: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + validations: + - type: Size + min: 2 + max: 10 + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-columns: + tag_id_s: id_s + tag_id_n: id_n + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/composed-id-models/data.sql b/apigen-examples/composed-id-models/data.sql new file mode 100644 index 0000000..836a548 --- /dev/null +++ b/apigen-examples/composed-id-models/data.sql @@ -0,0 +1,16 @@ +insert into tags(str_value, int_value, name) values ('T', 1, 'Tag 1'); +insert into tags(str_value, int_value, name) values ('T', 2, 'Tag 2'); + +insert into owners(str_value, int_value, name) values ('O', 1, 'Owner 1'); +insert into owners(str_value, int_value, name) values ('O', 2, 'Owner 2'); +insert into owners(str_value, int_value, name) values ('O', 3, 'Owner 3'); + +insert into pets(str_value, int_value, name, owner_s, owner_i, parent_s, parent_i, tag_s, tag_i) values ('P', 1, 'Dog', 'O', 1, null, null, 'T', 1); +insert into pets(str_value, int_value, name, owner_s, owner_i, parent_s, parent_i, tag_s, tag_i) values ('P', 2, 'Cat', 'O', 1, 'P', 1, 'T', 1); +insert into pets(str_value, int_value, name, owner_s, owner_i, parent_s, parent_i, tag_s, tag_i) values ('P', 3, 'Potato', 'O', 1, 'P', 2, 'T', 2); + +insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 1, 'T', 1); +insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 1, 'T', 2); +insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 2, 'T', 1); +insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 2, 'T', 2); +insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 3, 'T', 2); diff --git a/apigen-examples/multifile-definition/api.yaml b/apigen-examples/multifile-definition/api.yaml new file mode 100644 index 0000000..7dc4b5f --- /dev/null +++ b/apigen-examples/multifile-definition/api.yaml @@ -0,0 +1,118 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /owners: + $ref: "./owner_api.yaml#/paths/all" + /owners/{id}: + $ref: "./owner_api.yaml#/paths/byId" + + /tags: + $ref: "./tag_api.yaml#/paths/all" + + /pets: + $ref: "./pet_api.yaml#/paths/all" + /pets/{id}: + $ref: "./pet_api.yaml#/paths/byId" + /pets/search: + $ref: "./pet_api.yaml#/paths/search" + +components: + x-apigen-models: + Owner: + relational-persistence: + table: owners + attributes: + - name: id + type: String + relational-persistence: + column: id + primary-key: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: owner_id + + Pet: + relational-persistence: + table: pets + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + - name: owner + type: Owner + relational-persistence: + column: owner_id + - name: tags + type: Array + items-type: Tag + relational-persistence: + column: pet_id + foreign-column: tag_id + intermediate-table: pet_tags + owner: true + - name: parent + type: Pet + relational-persistence: + column: parent_id + - name: children + type: Array + items-type: Pet + relational-persistence: + foreign-column: parent_id + - name: mainTag + type: Tag + relational-persistence: + column: tag_id + + Tag: + relational-persistence: + table: tags + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + validations: + - type: Size + min: 2 + max: 10 + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: tag_id + + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/multifile-definition/owner_api.yaml b/apigen-examples/multifile-definition/owner_api.yaml new file mode 100644 index 0000000..f3d1b79 --- /dev/null +++ b/apigen-examples/multifile-definition/owner_api.yaml @@ -0,0 +1,138 @@ +paths: + all: + x-apigen-binding: + model: Owner + + get: + operationId: getOwners + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/init_param" + - $ref: "./standard_schemas.yaml#/components/parameters/limit_param" + - $ref: "./standard_schemas.yaml#/components/parameters/total_param" + - $ref: "./standard_schemas.yaml#/components/parameters/select_param" + - $ref: "./standard_schemas.yaml#/components/parameters/exclude_param" + - $ref: "./standard_schemas.yaml#/components/parameters/expand_param" + - $ref: "./standard_schemas.yaml#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owners" + + post: + operationId: postOwner + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newOwner" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + byId: + x-apigen-binding: + model: Owner + get: + operationId: getOwner + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/string_id_param" + - $ref: "./standard_schemas.yaml#/components/parameters/select_param" + - $ref: "./standard_schemas.yaml#/components/parameters/exclude_param" + - $ref: "./standard_schemas.yaml#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + put: + operationId: putOwner + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/string_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updateOwner" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + delete: + operationId: deleteOwner + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/string_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + +components: + schemas: + standard_response_owners: + allOf: + - $ref: "./standard_schemas.yaml#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + owners: + type: array + items: + $ref: "#/components/schemas/owner" + + standard_response_owner: + allOf: + - $ref: "./standard_schemas.yaml#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/owner" + owner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: integer + name: + type: string + pets: + type: array + items: + $ref: "./pet_api.yaml#/components/schemas/pet" + newOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + x-apigen-mapping: # Optional + name: id + name: + type: string + x-apigen-mapping: # Optional + name: name + updateOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + name: + type: string \ No newline at end of file diff --git a/apigen-examples/multifile-definition/pet_api.yaml b/apigen-examples/multifile-definition/pet_api.yaml new file mode 100644 index 0000000..2ad76c9 --- /dev/null +++ b/apigen-examples/multifile-definition/pet_api.yaml @@ -0,0 +1,206 @@ +paths: + all: + x-apigen-binding: + model: Pet + get: + operationId: getPets + parameters: + - $ref: "standard_schemas.yaml#/components/parameters/init_param" + - $ref: "standard_schemas.yaml#/components/parameters/limit_param" + - $ref: "standard_schemas.yaml#/components/parameters/total_param" + - $ref: "standard_schemas.yaml#/components/parameters/select_param" + - $ref: "standard_schemas.yaml#/components/parameters/exclude_param" + - $ref: "standard_schemas.yaml#/components/parameters/expand_param" + - $ref: "standard_schemas.yaml#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + post: + operationId: postPet + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newPet" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + + byId: + x-apigen-binding: + model: Pet + get: + operationId: getPet + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/integer_id_param" + - $ref: "./standard_schemas.yaml#/components/parameters/select_param" + - $ref: "./standard_schemas.yaml#/components/parameters/exclude_param" + - $ref: "./standard_schemas.yaml#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + put: + operationId: putPet + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/integer_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updatePet" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + delete: + operationId: deletePet + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/integer_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + search: + x-apigen-binding: + model: Pet + post: + operationId: searchPet + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/init_param" + - $ref: "./standard_schemas.yaml#/components/parameters/limit_param" + - $ref: "./standard_schemas.yaml#/components/parameters/total_param" + - $ref: "./standard_schemas.yaml#/components/parameters/select_param" + - $ref: "./standard_schemas.yaml#/components/parameters/exclude_param" + - $ref: "./standard_schemas.yaml#/components/parameters/expand_param" + - $ref: "./standard_schemas.yaml#/components/parameters/order_by_param" + requestBody: + content: + application/json: + schema: + $ref: "./standard_schemas.yaml#/components/schemas/standard_search" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + +components: + schemas: + standard_response_pets: + allOf: + - $ref: "./standard_schemas.yaml#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + standard_response_pet: + allOf: + - $ref: "./standard_schemas.yaml#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pet" + + pet: + x-apigen-mapping: + model: Pet + type: object + properties: + json_id: + type: integer + x-apigen-mapping: + field: id + name: + type: string + tags: + type: array + items: + $ref: "./tag_api.yaml#/components/schemas/tag" + parent: + $ref: "#/components/schemas/pet" + children: + type: array + items: + $ref: "#/components/schemas/pet" + main_tag: + $ref: "./tag_api.yaml#/components/schemas/tag" + owner: + $ref: "./owner_api.yaml#/components/schemas/owner" + + newPet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + minLength: 4 + owner: + type: string + x-apigen-mapping: # Required + model: Owner + field: owner.id + parent: + x-apigen-mapping: + model: Pet + type: object + properties: + id: + type: integer + tags: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + maxItems: 10 + main_tag: + x-apigen-mapping: + field: mainTag + type: object + properties: + id: + type: integer + name: + type: string + required: + - name + + updatePet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string \ No newline at end of file diff --git a/apigen-examples/multifile-definition/standard_schemas.yaml b/apigen-examples/multifile-definition/standard_schemas.yaml new file mode 100644 index 0000000..70fcdd7 --- /dev/null +++ b/apigen-examples/multifile-definition/standard_schemas.yaml @@ -0,0 +1,132 @@ +components: + parameters: + integer_id_param: + in: path + name: id + required: true + schema: + type: integer + format: int64 + string_id_param: + in: path + name: id + required: true + schema: + type: string + init_param: + in: query + name: $init + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + limit_param: + in: query + name: $limit + required: true + schema: + type: integer + format: int32 + minimum: 1 + default: 25 + total_param: + in: query + name: $total + required: true + schema: + type: boolean + default: false + select_param: + in: query + name: $select + required: false + schema: + type: array + items: + type: string + maxItems: 100 + exclude_param: + in: query + name: $exclude + required: false + schema: + type: array + items: + type: string + maxItems: 100 + expand_param: + in: query + name: $expand + required: false + schema: + type: array + items: + type: string + maxItems: 100 + order_by_param: + in: query + name: $orderby + required: false + schema: + type: array + items: + type: string + maxItems: 100 + + schemas: + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + standard_search: + type: object + properties: + operation: + type: string + enum: [AND, OR, GT, LT, GTEQ, LTEQ, EQ, NEQ, IN, BETWEEN, SUBSTRING, LIKE, ILIKE, NLIKE, REGEXP] + values: + type: array + items: + type: object + properties: + property: + type: string + type: + type: string + enum: [STRING, INTEGER, FLOAT, DATE, DATETIME, BOOLEAN] + value: + type: string + values: + type: array + items: + type: string + filter: + $ref: '#/components/schemas/standard_search' diff --git a/apigen-examples/multifile-definition/tag_api.yaml b/apigen-examples/multifile-definition/tag_api.yaml new file mode 100644 index 0000000..3005624 --- /dev/null +++ b/apigen-examples/multifile-definition/tag_api.yaml @@ -0,0 +1,58 @@ +paths: + all: + x-apigen-binding: + model: Tag + get: + operationId: getTags + parameters: + - $ref: "./standard_schemas.yaml#/components/parameters/init_param" + - $ref: "./standard_schemas.yaml#/components/parameters/limit_param" + - $ref: "./standard_schemas.yaml#/components/parameters/total_param" + - $ref: "./standard_schemas.yaml#/components/parameters/select_param" + - $ref: "./standard_schemas.yaml#/components/parameters/exclude_param" + - $ref: "./standard_schemas.yaml#/components/parameters/expand_param" + - $ref: "./standard_schemas.yaml#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_tags" + +components: + schemas: + standard_response_tags: + allOf: + - $ref: "./standard_schemas.yaml#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + tags: + type: array + items: + $ref: "#/components/schemas/tag" + + standard_response_tag: + allOf: + - $ref: "./standard_schemas.yaml#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/tag" + + tag: + x-apigen-mapping: + model: Tag + type: object + properties: + id: + type: integer + name: + type: string + pets: + type: array + items: + $ref: "./pet_api.yaml#/components/schemas/pet" \ No newline at end of file diff --git a/apigen-examples/other/ApigenDemoAPI.yml b/apigen-examples/other/ApigenDemoAPI.yml new file mode 100644 index 0000000..3a153b9 --- /dev/null +++ b/apigen-examples/other/ApigenDemoAPI.yml @@ -0,0 +1,1159 @@ +openapi: '3.0.0' +info: + version: 1.0.0 + title: ApigenDemoAPI + +paths: + /medicos: + x-apigen-binding: + model: Medico + post: + operationId: postMedico + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_medico" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_medico" + get: + operationId: getMedicos + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_medicos" + + /medicos/{id}: + x-apigen-binding: + model: Medico + get: + operationId: getMedico + parameters: + - $ref: "#/components/parameters/integerIdParam" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_medico" + put: + operationId: putMedico + parameters: + - $ref: "#/components/parameters/integerIdParam" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_medico" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_medico" + delete: + operationId: deleteMedico + parameters: + - $ref: "#/components/parameters/integerIdParam" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_medico" + + /pacientes: + x-apigen-binding: + model: Paciente + post: + operationId: postPaciente + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_post_paciente" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_paciente" + get: + operationId: getPacientes + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pacientes" + + /pacientes/{id}: + x-apigen-binding: + model: Paciente + get: + operationId: getPaciente + parameters: + - $ref: "#/components/parameters/integerIdParam" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_paciente" + put: + operationId: putPaciente + parameters: + - $ref: "#/components/parameters/integerIdParam" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_put_paciente" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_paciente" + delete: + operationId: deletePaciente + parameters: + - $ref: "#/components/parameters/integerIdParam" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_paciente" + + /direcciones: + x-apigen-binding: + model: Direccion + post: + operationId: postDireccion + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_direccion" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_direccion" + get: + operationId: getDirecciones + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_direcciones" + + /direcciones/{id}: + x-apigen-binding: + model: Direccion + get: + operationId: getDireccion + parameters: + - $ref: "#/components/parameters/integerIdParam" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_direccion" + put: + operationId: putDireccion + parameters: + - $ref: "#/components/parameters/integerIdParam" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_direccion" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_direccion" + delete: + operationId: deleteDireccion + parameters: + - $ref: "#/components/parameters/integerIdParam" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_direccion" + + /facturas: + x-apigen-binding: + model: Factura + post: + operationId: postFactura + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_factura" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_factura" + get: + operationId: getFacturas + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_facturas" + + /facturas/{id}: + x-apigen-binding: + model: Factura + get: + operationId: getFactura + parameters: + - $ref: "#/components/parameters/integerIdParam" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_factura" + put: + operationId: putFactura + parameters: + - $ref: "#/components/parameters/integerIdParam" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_factura" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_factura" + delete: + operationId: deleteFactura + parameters: + - $ref: "#/components/parameters/integerIdParam" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_factura" + + /camas: + x-apigen-binding: + model: Cama + post: + operationId: postCama + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_cama" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_cama" + get: + operationId: getCamas + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_camas" + + /camas/{id}: + x-apigen-binding: + model: Cama + get: + operationId: getCama + parameters: + - $ref: "#/components/parameters/stringIdParam" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_cama" + put: + operationId: putCama + parameters: + - $ref: "#/components/parameters/stringIdParam" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_cama" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_cama" + delete: + operationId: deleteCama + parameters: + - $ref: "#/components/parameters/stringIdParam" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_habitacion" + + /habitaciones: + x-apigen-binding: + model: Habitacion + post: + operationId: postHabitacion + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_habitacion" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_habitacion" + get: + operationId: getHabitaciones + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_habitaciones" + + /habitaciones/{id}: + x-apigen-binding: + model: Habitacion + get: + operationId: getHabitacion + parameters: + - $ref: "#/components/parameters/stringIdParam" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_habitacion" + put: + operationId: putHabitacion + parameters: + - $ref: "#/components/parameters/stringIdParam" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/request_body_habitacion" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_habitacion" + delete: + operationId: deleteHabitacion + parameters: + - $ref: "#/components/parameters/stringIdParam" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_habitacion" + +components: + parameters: + integerIdParam: + name: id + in: path + required: true + schema: + type: integer + format: int64 + stringIdParam: + name: id + in: path + required: true + schema: + type: string + init_param: + in: query + name: $init + schema: + type: integer + format: int32 + limit_param: + in: query + name: $limit + schema: + type: integer + format: int32 + total_param: + in: query + name: $total + schema: + type: boolean + select_param: + in: query + name: $select + schema: + type: array + items: + type: string + exclude_param: + in: query + name: $exclude + schema: + type: array + items: + type: string + expand_param: + in: query + name: $expand + schema: + type: array + items: + type: string + order_by_param: + in: query + name: $orderby + schema: + type: array + items: + type: string + + schemas: + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + + standard_response_medicos: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + medicos: + type: array + items: + $ref: "#/components/schemas/medico" + + standard_response_medico: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/medico" + + standard_response_pacientes: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pacientes: + type: array + items: + $ref: "#/components/schemas/paciente" + + standard_response_paciente: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/paciente" + + standard_response_direcciones: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + direcciones: + type: array + items: + $ref: "#/components/schemas/direccion" + + standard_response_direccion: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/direccion" + + standard_response_facturas: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + facturas: + type: array + items: + $ref: "#/components/schemas/factura" + + standard_response_factura: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/factura" + + standard_response_camas: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + camas: + type: array + items: + $ref: "#/components/schemas/cama" + + standard_response_cama: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/cama" + + standard_response_habitaciones: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + habitaciones: + type: array + items: + $ref: "#/components/schemas/habitacion" + + standard_response_habitacion: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/habitacion" + + medico: + x-apigen-mapping: + model: Medico + type: READ + type: object + properties: + id: + type: integer + format: int64 + dni: + type: string + nombre: + type: string + salario: + type: number + format: double + pacientes: + type: array + items: + $ref: "#/components/schemas/paciente" + + paciente: + x-apigen-mapping: + model: Paciente + type: READ + type: object + properties: + id: + type: integer + format: int64 + dni: + type: string + nombre: + type: string + telefono: + type: string + email: + type: string + direccion: + $ref: "#/components/schemas/direccion" + cama: + $ref: "#/components/schemas/cama" + facturas: + type: array + items: + $ref: "#/components/schemas/factura" + medicos: + type: array + items: + $ref: "#/components/schemas/medico" + + direccion: + x-apigen-mapping: + model: Direccion + type: READ + type: object + properties: + id: + type: integer + format: int64 + calle: + type: string + numero: + type: integer + format: int32 + cp: + type: string + localidad: + type: string + paciente: + $ref: "#/components/schemas/paciente" + + factura: + x-apigen-mapping: + model: Factura + type: READ + type: object + properties: + id: + type: integer + format: int64 + paciente: + $ref: "#/components/schemas/paciente" + importe: + type: number + format: double + fecha: + type: string + format: date + fechayhora: + type: string + format: date-time + + cama: + x-apigen-mapping: + model: Cama + type: READ + type: object + properties: + id: + type: string + habitacion: + $ref: "#/components/schemas/habitacion" + paciente: + $ref: "#/components/schemas/paciente" + + habitacion: + x-apigen-mapping: + model: Habitacion + type: READ + type: object + properties: + id: + type: string + planta: + type: integer + format: int32 + numero: + type: integer + format: int32 + camas: + type: array + items: + $ref: "#/components/schemas/cama" + + request_body_medico: + type: object + properties: + dni: + type: string + nombre: + type: string + salario: + type: number + format: double + pacientes: + type: array + items: + type: integer + format: int64 + request_body_paciente: + type: object + properties: + dni: + type: string + nombre: + type: string + telefono: + type: string + email: + type: string + cama: + type: string + facturas: + type: array + items: + type: integer + format: int64 + medicos: + type: array + items: + type: integer + format: int64 + request_body_post_paciente: + allOf: + - $ref: "#/components/schemas/request_body_paciente" + - type: object + properties: + direccion: + $ref: "#/components/schemas/request_body_direccion" + request_body_put_paciente: + allOf: + - $ref: "#/components/schemas/request_body_paciente" + - type: object + properties: + direccion: + type: integer + format: int64 + request_body_direccion: + type: object + properties: + calle: + type: string + numero: + type: integer + format: int32 + cp: + type: string + localidad: + type: string + paciente: + type: integer + format: int64 + request_body_factura: + type: object + properties: + paciente: + type: integer + format: int64 + importe: + type: number + format: double + fecha: + type: string + format: date + fechayhora: + type: string + format: date-time + request_body_cama: + type: object + properties: + habitacion: + type: string + paciente: + type: integer + format: int64 + request_body_habitacion: + type: object + properties: + planta: + type: integer + format: int32 + numero: + type: integer + format: int32 + camas: + type: array + items: + type: string + # ------------------------------------------------- + x-apigen-models: + Medico: + relational-persistence: + table: medicos + attributes: + - name: id + type: Long + relational-persistence: + column: medico_id + primary-key: true + validations: + - type: NotNull + - name: dni + type: String + relational-persistence: + column: medico_dni + primary-key: true + validations: + - type: NotEmpty + - name: nombre + type: String + relational-persistence: + column: nombre + - name: salario + type: Double + relational-persistence: + column: salario + validations: + - type: Positive + - name: pacientes #@ManyToMany-Owner + type: Array + items-type: Paciente + relational-persistence: + column: medico_id + foreign-column: paciente_id + intermediate-table: consultas + owner: true + + Paciente: + relational-persistence: + table: pacientes + attributes: + - name: id + type: Long + relational-persistence: + column: paciente_id + primary-key: true + validations: + - type: NotNull + - name: dni + type: String + relational-persistence: + column: paciente_id + primary-key: true + validations: + - type: NotEmpty + - name: nombre + type: String + relational-persistence: + column: nombre + - name: telefono + type: String + relational-persistence: + column: telefono + validations: + - type: Pattern + regex: '\\d{9}' + - name: email + type: String + relational-persistence: + column: email + validations: + - type: Email + - name: direccion #@OneToOne-Owner + type: Direccion + relational-persistence: + column: direccion_id + - name: cama #@OneToOne-Owner + type: Cama + relational-persistence: + column: cama_id + - name: facturas #@OneToMany + type: Array + items-type: Factura + relational-persistence: + foreign-column: paciente_id + - name: medicos #@ManyToMany + type: Array + items-type: Medico + relational-persistence: + column: paciente_id + foreign-column: medico_id + intermediate-table: consultas + owner: false + validations: + - type: Size + min: 1 + + Direccion: + relational-persistence: + table: direcciones + attributes: + - name: id + type: Long + relational-persistence: + column: habitacion_id + primary-key: true + validations: + - type: NotNull + - name: calle + type: String + relational-persistence: + column: calle + - name: numero + type: Integer + relational-persistence: + column: numero + - name: cp + type: String + relational-persistence: + column: cp + - name: localidad + type: String + relational-persistence: + column: localidad + - name: paciente #@OneToOne + type: Paciente + relational-persistence: + foreign-column: direccion_id + + Factura: + relational-persistence: + table: facturas + attributes: + - name: id + type: Long + relational-persistence: + column: factura_id + primary-key: true + validations: + - type: NotNull + - name: paciente #@ManyToOne + type: Paciente + relational-persistence: + column: paciente_id + - name: importe + type: Double + relational-persistence: + column: importe + validations: + - type: DecimalMin + value: 100.0 + - name: fecha + type: LocalDate + relational-persistence: + column: fecha + validations: + - type: PastOrPresent + - name: fechaYHora + type: OffsetDateTime + relational-persistence: + column: fecha_y_hora + validations: + - type: PastOrPresent + + Cama: + relational-persistence: + table: camas + attributes: + - name: id + type: String + relational-persistence: + column: cama_id + primary-key: true + validations: + - type: NotNull + - name: habitacion #@ManyToOne + type: Habitacion + relational-persistence: + column: habitacion_id + - name: paciente #@OneToOne + type: Paciente + relational-persistence: + foreign-column: cama_id + + Habitacion: + relational-persistence: + table: habitaciones + attributes: + - name: id + type: String + relational-persistence: + column: habitacion_id + primary-key: true + validations: + - type: NotNull + - name: planta + type: Integer + relational-persistence: + column: planta + - name: numero + type: Integer + relational-persistence: + column: numero + validations: + - type: Min + value: 1 + - type: Max + value: 2 + - name: camas #@OneToMany + type: Array + items-type: Cama + relational-persistence: + foreign-column: habitacion_id + +# ---------------------------- + +x-apigen-project: + name: ApigenDemoAPI + description: Apigen Demo API + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/other/date-api.yaml b/apigen-examples/other/date-api.yaml new file mode 100644 index 0000000..5474250 --- /dev/null +++ b/apigen-examples/other/date-api.yaml @@ -0,0 +1,195 @@ +openapi: '3.0.0' +info: + version: 1.0.0 + title: Date Time API + +paths: + /date: + x-apigen-binding: + model: EDate + post: + operationId: postDate + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newDate" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_date" + /date/{id}: + x-apigen-binding: + model: EDate + get: + operationId: getDate + parameters: + - $ref: "#/components/parameters/integer_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_date" + /date-time: + x-apigen-binding: + model: EDateTime + post: + operationId: postDateTime + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newDateTime" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_datetime" + /date-time/{id}: + x-apigen-binding: + model: EDateTime + get: + operationId: getDateTime + parameters: + - $ref: "#/components/parameters/integer_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_datetime" + +components: + parameters: + integer_id_param: + in: path + name: id + required: true + schema: + type: integer + format: int64 + + schemas: + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + standard_response_date: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/date" + standard_response_datetime: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/datetime" + date: + x-apigen-mapping: + model: EDate + type: object + properties: + id: + type: integer + date: + type: string + format: date + datetime: + x-apigen-mapping: + model: EDateTime + type: object + properties: + id: + type: integer + datetime: + type: string + format: date-time + newDate: + x-apigen-mapping: + model: EDate + type: object + properties: + date: + type: string + format: date + newDateTime: + x-apigen-mapping: + model: EDateTime + type: object + properties: + datetime: + type: string + format: date-time + + x-apigen-models: + EDate: + relational-persistence: + table: edates + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + - name: date + type: LocalDate + relational-persistence: + column: date + + EDateTime: + relational-persistence: + table: edatetimes + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + - name: datetime + type: OffsetDateTime + +x-apigen-project: + name: dates + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app diff --git a/apigen-examples/simple-id-models/api-test-reduced.yaml b/apigen-examples/simple-id-models/api-test-reduced.yaml new file mode 100644 index 0000000..59a68f9 --- /dev/null +++ b/apigen-examples/simple-id-models/api-test-reduced.yaml @@ -0,0 +1,843 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /owners: + x-apigen-binding: + model: Owner + get: + operationId: getOwners + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owners" + + post: + operationId: postOwner + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newOwner" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /owners/{id}: + x-apigen-binding: + model: Owner + get: + operationId: getOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + put: + operationId: putOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updateOwner" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + delete: + operationId: deleteOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /tags: + x-apigen-binding: + model: Tag + get: + operationId: getTags + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_tags" + + + /pets: + x-apigen-binding: + model: Pet + get: + operationId: getPets + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + post: + operationId: postPet + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newPet" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + + /pets/{id}: + x-apigen-binding: + model: Pet + get: + operationId: getPet + parameters: + - $ref: "#/components/parameters/integer_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + put: + operationId: putPet + parameters: + - $ref: "#/components/parameters/integer_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updatePet" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + delete: + operationId: deletePet + parameters: + - $ref: "#/components/parameters/integer_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /pets/search: + x-apigen-binding: + model: Pet + post: + operationId: searchPet + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/standard_search" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /status: + get: + operationId: getStatus + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other: + get: + operationId: getStatus2 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other/nested/nested/end: + get: + operationId: getStatus3 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /entities-related: + get: + operationId: getEntities + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pets' + /entities-related/{id}: + get: + parameters: + - $ref: '#/components/parameters/integer_id_param' + operationId: getEntity + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pet' + /standard: + get: + operationId: getStandardCollection + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojos' + /standard/{id}: + get: + parameters: + - $ref: '#/components/parameters/integer_id_param' + operationId: getStandardElement + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojo' + /reconfig: + post: + operationId: reconfig + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}: + post: + operationId: reconfig2 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}/nested/level: + post: + operationId: reconfig3 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/configResponse' + /nothing: + get: + operationId: nothing + responses: + '204': + description: Ok + +components: + parameters: + integer_id_param: + in: path + name: id + required: true + schema: + type: integer + format: int64 + string_id_param: + in: path + name: id + required: true + schema: + type: string + init_param: + in: query + name: $init + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + limit_param: + in: query + name: $limit + required: true + schema: + type: integer + format: int32 + minimum: 1 + default: 25 + total_param: + in: query + name: $total + required: true + schema: + type: boolean + default: false + select_param: + in: query + name: $select + required: false + schema: + type: array + items: + type: string + maxItems: 100 + exclude_param: + in: query + name: $exclude + required: false + schema: + type: array + items: + type: string + maxItems: 100 + expand_param: + in: query + name: $expand + required: false + schema: + type: array + items: + type: string + maxItems: 100 + order_by_param: + in: query + name: $orderby + required: false + schema: + type: array + items: + type: string + maxItems: 100 + id: + name: id + in: path + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 23 + schemas: + statusResponse: + type: object + properties: + value: + type: string + configRequest: + type: object + properties: + general: + type: object + properties: + name: + type: string + timeout: + type: integer + custom: + type: array + items: + type: object + properties: + key: + type: string + value: + type: string + configResponse: + type: object + properties: + values: + type: array + items: + type: object + properties: + name: + type: string + status: + type: boolean + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + standard_search: + type: object + properties: + operation: + type: string + enum: [AND, OR, GT, LT, GTEQ, LTEQ, EQ, NEQ, IN, BETWEEN, SUBSTRING, LIKE, ILIKE, NLIKE, REGEXP] + values: + type: array + items: + type: object + properties: + property: + type: string + type: + type: string + enum: [STRING, INTEGER, FLOAT, DATE, DATETIME, BOOLEAN] + value: + type: string + values: + type: array + items: + type: string + filter: + $ref: '#/components/schemas/standard_search' + + + standard_response_pets: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + standard_response_pet: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pet" + + standard_response_owners: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + owners: + type: array + items: + $ref: "#/components/schemas/owner" + + standard_response_owner: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/owner" + + standard_response_tags: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + tags: + type: array + items: + $ref: "#/components/schemas/tag" + + standard_response_tag: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/tag" + + standard_response_pojos: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pojos: + type: array + items: + $ref: "#/components/schemas/pojo" + + standard_response_pojo: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pojo" + + pojo: + type: object + properties: + values: + type: array + items: + type: object + properties: + level: + type: number + format: int64 + name: + type: string + enabled: + type: boolean + pet: + x-apigen-mapping: + model: Pet + type: object + properties: + json_id: + type: integer + x-apigen-mapping: + field: id + name: + type: string + tags: + type: array + items: + $ref: "#/components/schemas/tag" + parent: + $ref: "#/components/schemas/pet" + children: + type: array + items: + $ref: "#/components/schemas/pet" + main_tag: + $ref: "#/components/schemas/tag" + owner: + $ref: "#/components/schemas/owner" + + tag: + x-apigen-mapping: + model: Tag + type: object + properties: + id: + type: integer + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + owner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: integer + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + newPet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + minLength: 4 + owner: + type: string + x-apigen-mapping: # Required + model: Owner + field: owner.id + parent: + x-apigen-mapping: + model: Pet + type: object + properties: + id: + type: integer + tags: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + maxItems: 10 + main_tag: + x-apigen-mapping: + field: mainTag + type: object + properties: + id: + type: integer + name: + type: string + required: + - name + + newOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + x-apigen-mapping: # Optional + name: id + name: + type: string + x-apigen-mapping: # Optional + name: name + + updatePet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + + updateOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + name: + type: string + + + # ------------------------------------------------- + + + x-apigen-models: + Owner: + attributes: + - name: id + type: String + relational-persistence: + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: owner_id + + Pet: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + - name: owner + type: Owner + relational-persistence: + column: owner_id + - name: tags + type: Array + items-type: Tag + relational-persistence: + column: pet_id + foreign-column: tag_id + intermediate-table: pet_tags + owner: true + - name: parent + type: Pet + relational-persistence: + column: parent_id + - name: children + type: Array + items-type: Pet + relational-persistence: + foreign-column: parent_id + - name: mainTag + type: Tag + relational-persistence: + column: tag_id + + Tag: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + validations: + - type: Size + min: 2 + max: 10 + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: tag_id + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/simple-id-models/api-test.yaml b/apigen-examples/simple-id-models/api-test.yaml new file mode 100644 index 0000000..48fe4b7 --- /dev/null +++ b/apigen-examples/simple-id-models/api-test.yaml @@ -0,0 +1,858 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /owners: + x-apigen-binding: + model: Owner + get: + operationId: getOwners + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owners" + + post: + operationId: postOwner + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newOwner" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /owners/{id}: + x-apigen-binding: + model: Owner + get: + operationId: getOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + put: + operationId: putOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updateOwner" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + delete: + operationId: deleteOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /tags: + x-apigen-binding: + model: Tag + get: + operationId: getTags + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_tags" + + + /pets: + x-apigen-binding: + model: Pet + get: + operationId: getPets + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + post: + operationId: postPet + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newPet" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + + /pets/{id}: + x-apigen-binding: + model: Pet + get: + operationId: getPet + parameters: + - $ref: "#/components/parameters/integer_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + put: + operationId: putPet + parameters: + - $ref: "#/components/parameters/integer_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updatePet" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + delete: + operationId: deletePet + parameters: + - $ref: "#/components/parameters/integer_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /pets/search: + x-apigen-binding: + model: Pet + post: + operationId: searchPet + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/standard_search" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /status: + get: + operationId: getStatus + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other: + get: + operationId: getStatus2 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other/nested/nested/end: + get: + operationId: getStatus3 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /entities-related: + get: + operationId: getEntities + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pets' + /entities-related/{id}: + get: + parameters: + - $ref: '#/components/parameters/integer_id_param' + operationId: getEntity + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pet' + /standard: + get: + operationId: getStandardCollection + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojos' + /standard/{id}: + get: + parameters: + - $ref: '#/components/parameters/integer_id_param' + operationId: getStandardElement + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojo' + /reconfig: + post: + operationId: reconfig + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}: + post: + operationId: reconfig2 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}/nested/level: + post: + operationId: reconfig3 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/configResponse' + /nothing: + get: + operationId: nothing + responses: + '204': + description: Ok + +components: + parameters: + integer_id_param: + in: path + name: id + required: true + schema: + type: integer + format: int64 + string_id_param: + in: path + name: id + required: true + schema: + type: string + init_param: + in: query + name: $init + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + limit_param: + in: query + name: $limit + required: true + schema: + type: integer + format: int32 + minimum: 1 + default: 25 + total_param: + in: query + name: $total + required: true + schema: + type: boolean + default: false + select_param: + in: query + name: $select + required: false + schema: + type: array + items: + type: string + maxItems: 100 + exclude_param: + in: query + name: $exclude + required: false + schema: + type: array + items: + type: string + maxItems: 100 + expand_param: + in: query + name: $expand + required: false + schema: + type: array + items: + type: string + maxItems: 100 + order_by_param: + in: query + name: $orderby + required: false + schema: + type: array + items: + type: string + maxItems: 100 + id: + name: id + in: path + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 23 + schemas: + statusResponse: + type: object + properties: + value: + type: string + configRequest: + type: object + properties: + general: + type: object + properties: + name: + type: string + timeout: + type: integer + custom: + type: array + items: + type: object + properties: + key: + type: string + value: + type: string + configResponse: + type: object + properties: + values: + type: array + items: + type: object + properties: + name: + type: string + status: + type: boolean + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + standard_search: + type: object + properties: + operation: + type: string + enum: [AND, OR, GT, LT, GTEQ, LTEQ, EQ, NEQ, IN, BETWEEN, SUBSTRING, LIKE, ILIKE, NLIKE, REGEXP] + values: + type: array + items: + type: object + properties: + property: + type: string + type: + type: string + enum: [STRING, INTEGER, FLOAT, DATE, DATETIME, BOOLEAN] + value: + type: string + values: + type: array + items: + type: string + filter: + $ref: '#/components/schemas/standard_search' + + + standard_response_pets: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + standard_response_pet: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pet" + + standard_response_owners: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + owners: + type: array + items: + $ref: "#/components/schemas/owner" + + standard_response_owner: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/owner" + + standard_response_tags: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + tags: + type: array + items: + $ref: "#/components/schemas/tag" + + standard_response_tag: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/tag" + + standard_response_pojos: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pojos: + type: array + items: + $ref: "#/components/schemas/pojo" + + standard_response_pojo: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pojo" + + pojo: + type: object + properties: + values: + type: array + items: + type: object + properties: + level: + type: number + format: int64 + name: + type: string + enabled: + type: boolean + pet: + x-apigen-mapping: + model: Pet + type: object + properties: + json_id: + type: integer + x-apigen-mapping: + field: id + name: + type: string + tags: + type: array + items: + $ref: "#/components/schemas/tag" + parent: + $ref: "#/components/schemas/pet" + children: + type: array + items: + $ref: "#/components/schemas/pet" + main_tag: + $ref: "#/components/schemas/tag" + owner: + $ref: "#/components/schemas/owner" + + tag: + x-apigen-mapping: + model: Tag + type: object + properties: + id: + type: integer + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + owner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + newPet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + minLength: 4 + owner: + type: string + x-apigen-mapping: # Required + model: Owner + field: owner.id + parent: + x-apigen-mapping: + model: Pet + type: object + properties: + id: + type: integer + tags: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + maxItems: 10 + main_tag: + x-apigen-mapping: + field: mainTag + type: object + properties: + id: + type: integer + name: + type: string + required: + - name + + newOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + x-apigen-mapping: # Optional + name: id + name: + type: string + x-apigen-mapping: # Optional + name: name + + updatePet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + + updateOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + name: + type: string + + + # ------------------------------------------------- + + + x-apigen-models: + Owner: + relational-persistence: + table: owners + attributes: + - name: id + type: String + relational-persistence: + column: id + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: owner_id + + Pet: + relational-persistence: + table: pets + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + - name: owner + type: Owner + relational-persistence: + column: owner_id + - name: tags + type: Array + items-type: Tag + relational-persistence: + column: pet_id + foreign-column: tag_id + intermediate-table: pet_tags + owner: true + - name: parent + type: Pet + relational-persistence: + column: parent_id + - name: children + type: Array + items-type: Pet + relational-persistence: + foreign-column: parent_id + - name: mainTag + type: Tag + relational-persistence: + column: tag_id + + Tag: + relational-persistence: + table: tags + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + validations: + - type: Size + min: 2 + max: 10 + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: tag_id + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/simple-id-models/data.sql b/apigen-examples/simple-id-models/data.sql new file mode 100644 index 0000000..a11c2de --- /dev/null +++ b/apigen-examples/simple-id-models/data.sql @@ -0,0 +1,16 @@ +insert into tags(id, name) values (1, 'T1'); +insert into tags(id, name) values (2, 'T2'); + +insert into owners(id, name) values ('1', 'Owner 1'); +insert into owners(id, name) values ('2', 'Owner 2'); +insert into owners(id, name) values ('3', 'Owner 3'); + +insert into pets(id, name, owner_id, parent_id, tag_id) values (1, 'Dog', '1', null, 1); +insert into pets(id, name, owner_id, parent_id, tag_id) values (2, 'Cat', '1', 1, 1); +insert into pets(id, name, owner_id, parent_id, tag_id) values (3, 'Potato', '1', 2, 2); + +insert into pet_tags(pet_id, tag_id) values (1, 1); +insert into pet_tags(pet_id, tag_id) values (1, 2); +insert into pet_tags(pet_id, tag_id) values (2, 1); +insert into pet_tags(pet_id, tag_id) values (2, 2); +insert into pet_tags(pet_id, tag_id) values (3, 2); diff --git a/archetype-core/README.md b/archetype-core/README.md new file mode 100644 index 0000000..3ef533d --- /dev/null +++ b/archetype-core/README.md @@ -0,0 +1,340 @@ +# Procedimientos + +## Sobreescribir el mensaje y/o código de un error + +Los errores actualmente gestionados de forma automática se pueden encontrar en la enum `DefaultApigenError`. + +Si queremos sobreescribir el mensaje de un error en concreto debemos definirlo en el archivo `.properties` de nuestra aplicación de la siguiente forma + + apigen.errors..code + apigen.errors..message-template + +Donde `` es el valor de la enum que queremos sobreescribir. +En caso de que queramos interpolar los valores en el mensaje se deberán indicar con `{n}` donde `n` es la posición del valor a interpolar en base cero, podemos saber cuantos parámetros tiene viendo el mensaje por defecto. Hay que tener en cuenta que este template se gestiona mediante `MessageFormat.format` por lo que acepta cualquier expresión que este método soporte. + +Ejemplo: + + apigen.errors.PATH_NOT_IMPLEMENTED.code=9191 + apigen.errors.PATH_NOT_IMPLEMENTED.message-template=Not implemented ({0}) + +## Definir un nuevo error + +Su definición en el `.properties` es exactamente igual que cuando sobreescribimos uno estándar. + +A la hora de implementar errores estándar se recomienda definirlos en una o varias clases / enums así como excepciones específicas. + +Ejemplo: + + enum BussinessErrors { + WRONG_INVOICE + } + + public class WrongInvoiceException extends CustomApigenException { + public WrongInvoiceException(String invoiceID) { + super(BusinessErrors.WRONG_INVOICE.name(), invoiceID); + setHttpStatus(HttpStatus.I_AM_A_TEAPOT); + } + } + +## Definir un nuevo recurso de visualización + +Actualmente solo se genera un recurso de visualización por entidad, esto permite simplificar y unificar nuestros recursos, pero en ocasiones puede que necesitemos definir recursos de visualización especiales. + +Para definir un nuevo recurso de visualización necesitamos definir la clase o clases que van a representar estos recursos y anotarlas todas con `@ApigenEntityOutResource`, esta anotación permitirá que de forma automática nuestro `ResourceNamingTranslator` traduzca los nombres de `json` a `java` + +Ejemplo: + + @ApigenEntityOutResource + class CustomUserResource { + @JsonProperty("email") + private String username; + @JsonProperty("scopes") + private List roles; + } + + @ApigenEntityOutResource + class CustomRoleResource { + @JsonProperty("value") + private String name; + } + +Estos nuevos recursos podemos rellenarlos de forma manual, definiendo un nuevo método en el mapper correspondiente o combinando ambas estrategias. + +## Modificar el comportamiento a la hora de crear un recurso + +Cuando creamos un recurso se realiza el siguiente flujo: + +1 - Los datos del recurso `CreateResource` se transforman en una entidad `` + +2 - Las entidades relacionadas que se hayan indicado se crean o recuperan y se asocian a la entidad. Esta lógica se realiza dentro del `RelationManager` correspondiente. + +3 - Se crea la entidad en sí + +Si deseamos ejecutar código adicional durante este flujo recomendamos usar los métodos adicionales descritos en el apartado [Ampliar los servicios](#ampliar-la-funcionalidad-de-los-servicios-definidos) + +Si deseamos modificar el comportamiento del `RelationManager` somos libres de hacerlo pero hay que tener en cuenta que los métodos definidos se llaman siempre por lo que si la lógica que queremos implementar es diferente, se recomienda implementar otro método y sobreescribir en el servicio la parte oportuna. +Por defecto el `RelationManager` solo se encarga de gestionar aquellas relaciones de las cuales se es el propietario (owner), son aquellas que no disponen de un `mappedBy`. + +## Modificar el comportamiento a la hora de actualizar un recurso + +Cuando modificamos un recurso se realiza el siguiente flujo: + +1 - Los datos del recurso `CreateResource` se transforman en una entidad `` + +2 - Se recupera la entidad persistida + +3 - Se actualizan los datos básicos (aquellos que no son entidades) de la entidad a la entidad persistida mediante el método `updateBasicDataPartially` del `Service` + +4 - Las entidades relacionadas que se hayan indicado se relacionan a partir del método `updateRelations` del `RelationManager` correspondiente. + +5 - Se guardan los cambios + +Si deseamos ejecutar código adicional durante este flujo recomendamos usar los métodos adicionales descritos en el apartado [Ampliar los servicios](#ampliar-la-funcionalidad-de-los-servicios-definidos) + +Si deseamos modificar el comportamiento del `RelationManager` somos libres de hacerlo pero hay que tener en cuenta que los métodos definidos se llaman siempre por lo que si la lógica que queremos implementar es diferente, se recomienda implementar otro método y sobreescribir en el servicio la parte oportuna. +Por defecto el `RelationManager` solo se encarga de gestionar aquellas relaciones de las cuales se es el propietario (owner), son aquellas que no disponen de un `mappedBy`. + +## Redefinir la gestión de errores + +Por defecto la clase `ApigenControllerAdvice` se encarga de la gestión de los errores. Si queremos gestionar un erro actualmente no gestionado o sobreescribir la gestión sobre uno ya definido, debemos de definir otro `@ControllerAdvice` en nuestro proyecto, gestionando dicha excepción en concreto y definiéndolo con un orden de prioridad mayor. + +Ejemplo: + + @ControllerAdvice + @Order(Ordered.HIGHEST_PRECEDENCE) + class CustomControllerAdvice { + + @ResponseBody + @ExceptionHandler(Exception.class) // <-- The exception class + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResponse exception(Exception ex) { + // TODO implement logic here + } + + } + +## Redefinir la gestión de los `ApiResponse` + +Por defecto la clase `ApiResponseBodyAdvice` se encarga de ampliar los datos de las respuestas (`ApiResponse` y `ResponseEntity`) + +Si deseamos modificar dicha lógica debemos de extender la clase e implementar nuestra propia lógica. + +Ejemplo: + + @ControllerAdvice + public class CustomBodyAdvice extends ApiResponseBodyAdvice { + + public CustomBodyAdvice(ApigenProperties apigenProperties) { + super(apigenProperties); + } + + @Override + public ApiResponse beforeBodyWrite(ApiResponse apiResponse, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { + apiResponse.withResultUpdatedElements(100); + return apiResponse; + } + } + +## Definir limitaciones en los recursos de búsqueda + +Por defecto en todos los recursos de búsqueda está configurada una profundidad de expansión de `1` nivel. (Por ejemplo si nuestro recurso es un usuario y tiene roles podremos hacer un `$expand=roles`) + +Podemos configurar el nivel de la profundidad de expansión mediante la siguiente propiedad en el `.properties` + + apigen.api.expand=2 + +Dado que la configuración anterior nos permite definir un comportamiento genérico en nuestra api, en ocasiones podemos tener la necesidad de no permitir expandir algunas propiedades de un recurso anidado o solo permitir cierto subconjunto de ellas, para ello se pueden definir una serie de condiciones de expansión en el `.properties` + + apigen.api.paths[PATH_EXPRESSION].expand.allowed=value,other_value + apigen.api.paths[PATH_EXPRESSION].expand.excluded=value,other_value + apigen.api.paths[PATH_EXPRESSION].expand.level=3 + +El valor `allowed` indica aquellas propiedades por las que permitimos expandir. +El valor `excluded` indica aquellas propiedades por las que no permitimos expandir. +Se recomienda usar solo `allowed` o `excluded` en un mismo `PATH_EXPRESSION`. + +El valor `level` indica el nivel de profundidad permitido (sobreescribe el valor general si lo indicamos, sino se utiliza el valor general), en caso de que indiquemos el valor `allowed` no haría falta definirlo. + +En `PAT_EXPRESSION` debemos de indicar un valor fijo o un path regex. + + /explicit/path + + /path-by-id/{\:.*} + Se aplica a valores como /path-by-id/23 + + /path-by-regex-exclude/{\:^(?!explicit$|other$).*} + Se aplica a valores como /path-by-regex-exclude/algo pero no a /path-by-regex-exclude/explicit ni /path-by-regex-exclude/other + +Aunque se permita definir las propiedades del expand para un a url o un grupo de ellas mediante las properities, esto no está recomendado si podemos utilizar anotaciones. + +Para definir los limites de un expand mediante anotaciones debemos de añadir la anotación `@ApigenExpand` en el método que implementa el endpoint en el controlador. + +Esta anotación nos permite definir 3 valores: + +- `allowed`: Permite definir un conjunto de valores permitidos en el expand. Si este valor se indica no se tendrá en cuenta lo definido en `excluded` ni en `maxLevel`. +- `excluded`: Permite definir un conjunto de valores no permitidos en el expand. A los valores que no estén indicados en el `excluded` se les aplicará la validación de máxima profundidad. +- `maxLevel`: Permite definir el nivel máximo de profundidad de un expand. Si no se indica se utilizará el nivel genérico definido en las porperties y si este tampoco ha sido indicado entonces se utilizará su valor por defecto (`1`). + +Ejemplo: + + @ApigenExpand(excluded = {"one", "one.two", "one.two.three"}, maxLevel = 3) + @GetMapping("/annotation/excluded-and-level") + public void getAnnotationExcludedAndLevel() { + ... + } + +Cualquier endpoint que tenga una anotación de `@ApigenExpand` será ignorado por el evaluador de paths del interceptor de expansión. +Por lo tanto si un endpoint que tiene dicha anotación coincide con algún path especificado en las properties este será ignorado y solo se aplicarán las validaciones indicadas mediante la anotación. + +## Modificar la cabecera de trazabilidad + +Por defecto la cabecera de trazabilidad tiene el nombre `x-trace-id`. + +Si deseamos cambiar ese valor debemos definirlo en el archivo `.properties` + + apigen.trace-header=x-custom-name + +## Ampliar la funcionalidad de los servicios definidos + +Todos los servicios que realizan operaciones de escritura extienden del `AbstractCrudService`, este ofrece una serie de métodos que se pueden sobreescribir para implementar lógicas determinadas, si queremos mayor grado de personalización también podemos sobreescribir los otros métodos que ofrece. + +| Método | +| ------ | +| `preCreate(E entity)` | +| `preCreateBeforeManageRelations(E entity)` | +| `preCreateAfterManageRelations(E entity)` | +| `postCreate(E entity)` | +| `preUpdate(E persistedEntity, E entity, Set fields)` | +| `preUpdateBeforeManageBasicData(E persistedEntity, E entity, Set fields)` | +| `preUpdateAfterManageBasicData(E persistedEntity, E entity, Set fields)` | +| `preUpdateBeforeManageRelations(E persistedEntity, E entity, Set fields)` | +| `preUpdateAfterManageRelations(E persistedEntity, E entity, Set fields)` | +| `postUpdate(E persistedEntity, E entity, Set fields)` | +| `preDelete(E entity)` | +| `postDelete(E entity)` | +| `preSave(E entity)` | +| `postSave(E entity)` | + +## Ampliar la funcionalidad del `ApigenContext` + +Por defecto `ApigenContext` nos permite acceder al valor de la cabecera de trazabilidad de forma segura dentro del contexto de la petición. + +Hay ciertas ocasiones en las que nos puede ser de utilidad almacenar más información dentro del contexto de la petición, como por ejemplo otras cabeceras. + +Para ello se recomienda extender `ApigenContext` e implementar los interceptores que creamos oportunos. + +Ejemplo: + + public class MyAppContext extends ApigenContext { + private static final String BUSINESS_ID = "BUSINESS_ID"; + public static Long getBusinessId() { + return (Long) getRequestAttribute(BUSINESS_ID); + } + public static void setBusinessId(Long bussinesId) { + setRequestAttribute(BUSINESS_ID, bussinesId); + } + } + +## Dialecto de Hibernate no soportado + +Apigen actualmente soporta los dialectos de H2, MySQL, Oracle y PostgreSQL. +Si intentamos utilizar un dialecto distinto nos aparecerá el siguiente mensaje de error en el log: + + Dialect DIALECT_CLASS not supported natively by Apigen, consult documentation to extend your own dialect + +Actualmente Apigen amplia la definicón de funciones del dialecto para que las funciones avanzadas del filtro (como REGEXP) estén soportadas. +Cuando arraquemos una aplicación que no tenga definida algúna función requerida nos aparecerá el siguente mensaje de error en el log: + + Function FUNCTION_NAME not defined in current dialect DIALECT_CLASS, operation REGEXP not supported, consult documentation to extend your own dialect + +En este caso debemos podemos crear en nuestro proyecto una clase por cada dialecto al que queramos dar soporte y configurarlo en el perfil oportuno. + +Ejemplo: + +**Nota**: Por simplicidad aunque H2 esté soportada, en este ejemplo asumiremos que no lo está. + + Mensajes de error en el LOG: + + Dialect org.hibernate.dialect.H2Dialect not supported natively by Apigen, consult documentation to extend your own dialect + Function apigen_regexp not defined in current dialect org.hibernate.dialect.H2Dialect, operation REGEXP not supported, consult documentation to extend your own dialect + + + + package my.project.dialects + + public class MyH2Dialect extends org.hibernate.dialect.H2Dialect { + + public MyH2Dialect() { + super(); + this.registerFunction("apigen_regexp", new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, "REGEXP_LIKE(?1,?2)")); + } + } + + + + application-dev.proeprties + + spring.jpa.properties.hibernate.dialect = my.project.dialects.dialects.MyH2Dialect + + +# Tabla descriptiva del comportamiento por defecto en la creación + +| Tipo de campo | Dato en el json | Resultado | Notas | +|-------------------|-----------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------| +| BASIC | NULL | NULL | | +| BASIC | VALUE | VALUE | | +| ENTITY | NULL | NULL | | +| ENTITY | WITH ID | RETRIEVE AND ASSIGN | | +| ENTITY | WITHOUT ID | CREATE AND ASSIGN | La creación vuelve a aplicar de forma recursiva la lógica de esta tabla | +| ENTITY COLLECTION | NULL | NULL | | +| ENTITY COLLECTION | WITH ID | RETRIEVE AND ASSIGN | Se aplica a cada elemento de la colección que tenga id | +| ENTITY COLLEICTON | WITHOUT ID | CREATE AND ASSIGN | Se aplica a cada elemento de la colección que no tenga id, la creación vuelve a aplicar de forma recursiva la lógica de esta tabla | + +# Tabla descriptiva del comportamiento por defecto en la actualización + +| Tipo de campo | Dato en el json | Dato persistido | Resultado | Notas | +|-------------------|-----------------|-----------------|---------------------|-----------------------------------------------------------------------------------------------| +| BASIC | NULL | ANY | NULL | | +| BASIC | VALUE | ANY | VALUE | | +| BASIC | UNDEFINED | ANY | ANY | | +| ENTITY | NULL | NULL | NULL | | +| ENTITY | NULL | ENTITY | NULL | Simplemente se elimina la relación, la subentidad continua existiendo | +| ENTITY | WITH ID | NULL | RETRIEVE AND ASSIGN | | +| ENTITY | WITH ID | ENTITY | RETRIEVE AND ASSIGN | La antigua entidad relacionada continua existiendo, simplemente se modifica la relación | +| ENTITY | WITHOUT ID | NULL | ERROR | | +| ENTITY | WITHOUT ID | ENTITY | ERROR | | +| ENTITY | UNDEFINED | ANY | ANY | | +| ENTITY COLLECTION | NULL | NULL | NULL | | +| ENTITY COLLECTION | NULL | ENTITY | NULL | Simplemente se eliminan las relaciónes, la subentidades continua existiendo | +| ENTITY COLLECTION | WITH ID | NULL | RETRIEVE AND ASSIGN | | +| ENTITY COLLECTION | WITH ID | ENTITY | RETRIEVE AND ASSIGN | Las antiguas entidades relacionadas continuan existiendo, simplemente se modifica la relación | +| ENTITY COLLECTION | WITHOUT ID | NULL | ERROR | | +| ENTITY COLLECTION | WITHOUT ID | ENTITY | ERROR | | +| ENTITY COLLECTION | UNDEFINED | ANY | ANY | | + +# Fechas y horas + +Los dos tipos de Java soportados actualmente de forma automática son `LocalDate` para las fechas y `OffsetDateTime` para las fechas con hora. +Las fechas con hora se recomienda almacenarlas en formato UTC por lo que por defecto el api las devolverá en UTC. + +Relación de tipos con bases de datos más comunes: + +| Tipo java | Ejemplo input | Ejemplo output | Tipo PostgreSQL | Ejemplo PostgreSQL | Explicación | +| --- | ---- | --- | ---- | --- | ---- | +| LocalDate | 2020-05-05 | 2020-05-05 | DATE | 2020-05-05 | - | +| OffsetDateTime | 2020-05-05T14:00:00+04:00 | 2020-05-05T10:00:00Z | TIMESTAMP | 2020-05-03 10:00:00 | En este caso java utiliza el offset del timezone que tiene configurado para transformar el valor y almacenarlo en base de datos, debido a esto se recomienda configurar la aplicación para usar UTC| +| OffsetDateTime | 2020-05-05T10:00:00Z | 2020-05-05T10:00:00Z | TIMESTAMP | 2020-05-03 10:00:00 | Igual que el caso anterior pero ya usamos UTC en el formato de entrada | + + +| Tipo java | Ejemplo input | Ejemplo output | Tipo MySql | Ejemplo MySql | Explicación | +| --- | ---- | --- | ---- | --- | ---- | +| LocalDate | 2020-05-05 | 2020-05-05 | DATE | 2020-05-05 | - | +| OffsetDateTime | 2020-05-05T14:00:00+04:00 | 2020-05-05T10:00:00Z | DATETIME | 2020-05-03 10:00:00 | En este caso java utiliza el offset del timezone que tiene configurado para transformar el valor y almacenarlo en base de datos, debido a esto se recomienda configurar la aplicación para usar UTC| +| OffsetDateTime | 2020-05-05T10:00:00Z | 2020-05-05T10:00:00Z | DATETIME | 2020-05-03 10:00:00 | Igual que el caso anterior pero ya usamos UTC en el formato de entrada | + + +**IMPORTANTE**: Hay que tener en cuenta que si consultamos las bases de datos desde una herramienta puede que tengamos que configurarla para que no nos convierta los valores automáticamente antes de visualizarlos. Por ejemplo en Dbeaver hay que modificar su `.ini` y añadir `-Duser.timezone=UTC` + +# Health check + +Por defecto viene configurado Actuator para que solo tenga activa la url de heakth check en `http://HOST:PORT/actuator/health`, para mas información consultar la documentación de Spring Boot Actuator y sus properties. \ No newline at end of file diff --git a/archetype-core/pom.xml b/archetype-core/pom.xml new file mode 100644 index 0000000..84b245d --- /dev/null +++ b/archetype-core/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + + net.cloudappi.apigen + apigen + 0.0.1-SNAPSHOT + ../pom.xml + + + archetype-core + archetype-core + ${revision} + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + + + io.springfox + springfox-swagger2 + + + commons-beanutils + commons-beanutils + + + org.reflections + reflections + + + + javassist + org.javassist + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + com.h2database + h2 + test + + + + + \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenApplication.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenApplication.java new file mode 100644 index 0000000..c00a0bf --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenApplication.java @@ -0,0 +1,17 @@ +package net.cloudappi.apigen.archetypecore.autoconfigure; + +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepositoryImpl; +import org.springframework.context.annotation.Import; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +import java.lang.annotation.*; + + +@Inherited +@Documented +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@EnableJpaRepositories(repositoryBaseClass = ApigenRepositoryImpl.class) +@Import(ApigenConfiguration.class) +public @interface ApigenApplication { +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenConfiguration.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenConfiguration.java new file mode 100644 index 0000000..372cad5 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenConfiguration.java @@ -0,0 +1,62 @@ +package net.cloudappi.apigen.archetypecore.autoconfigure; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.cloudappi.apigen.archetypecore.core.advice.ApigenControllerAdvice; +import net.cloudappi.apigen.archetypecore.core.errors.ApigenErrorManager; +import net.cloudappi.apigen.archetypecore.core.errors.DefaultApigenErrorManager; +import net.cloudappi.apigen.archetypecore.core.resource.ResourceNamingTranslator; +import net.cloudappi.apigen.archetypecore.core.resource.ResourceNamingTranslatorByReflection; +import net.cloudappi.apigen.archetypecore.interceptors.response.ApiResponseBodyAdvice; +import net.cloudappi.apigen.archetypecore.interceptors.update.CachingRequestBodyFilter; +import net.cloudappi.apigen.archetypecore.interceptors.update.UpdateRequestBodyAdvice; +import net.cloudappi.apigen.archetypecore.interceptors.WebConfig; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +@Import({ApigenDocumentationConfiguration.class, WebConfig.class}) +public class ApigenConfiguration { + + @Bean + @ConditionalOnMissingBean(ResourceNamingTranslator.class) + public ResourceNamingTranslator resourceNamingTranslator(ApplicationContext context) { + return new ResourceNamingTranslatorByReflection(context); + } + + @Bean + @ConditionalOnMissingBean(ApigenErrorManager.class) + public ApigenErrorManager apigenErrorManager(ApigenProperties apigenProperties) { + return new DefaultApigenErrorManager(apigenProperties); + } + + @Bean + @ConditionalOnMissingBean(ApigenControllerAdvice.class) + public ApigenControllerAdvice apigenControllerAdvice(DefaultApigenErrorManager errorManager) { + return new ApigenControllerAdvice(errorManager); + } + + @Bean + @ConditionalOnMissingBean(ApigenProperties.class) + public ApigenProperties apigenProperties() { + return new ApigenProperties(); + } + + @Bean + @ConditionalOnMissingBean(ApiResponseBodyAdvice.class) + public ApiResponseBodyAdvice apiResponseBodyAdvice(ApigenProperties properties) { + return new ApiResponseBodyAdvice(properties); + } + + @Bean + @ConditionalOnMissingBean(UpdateRequestBodyAdvice.class) + public UpdateRequestBodyAdvice updateRequestBodyAdvice(ObjectMapper mapper) { + return new UpdateRequestBodyAdvice(mapper); + } + + @Bean + @ConditionalOnMissingBean(CachingRequestBodyFilter.class) + public CachingRequestBodyFilter cachingRequestBodyFilter() { + return new CachingRequestBodyFilter(); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java new file mode 100644 index 0000000..eb41a31 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java @@ -0,0 +1,66 @@ +package net.cloudappi.apigen.archetypecore.autoconfigure; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.ResourceProperties; +import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.web.bind.annotation.RestController; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@EnableSwagger2 +@ConditionalOnProperty(prefix = "apigen.documentation", name = "enabled", havingValue = "true") +public class ApigenDocumentationConfiguration { + + @Value("${spring.application.name:}") + private String name; + + @Value("${spring.application.description:}") + private String description; + + @Value("${spring.application.version:}") + private String version; + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) + .build() + .apiInfo(new ApiInfoBuilder().description(description).title(name).version(version).build()) + .forCodeGeneration(true) + .useDefaultResponseMessages(false); + } + + @Bean + public BeanPostProcessor swaggerBeanPostProcessor() { + return new SwaggerBeanPostProcessor(); + } + + @Slf4j + public static class SwaggerBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof WebMvcProperties) { + WebMvcProperties properties = (WebMvcProperties) bean; + String newValue = "/swagger-ui.html"; + log.warn("Overriding value of 'spring.mvc.static-path-pattern' from {} to {}", properties.getStaticPathPattern(), newValue); + properties.setStaticPathPattern(newValue); + } else if (bean instanceof ResourceProperties) { + ResourceProperties properties = (ResourceProperties) bean; + boolean newValue = true; + log.warn("Overriding value of 'spring.resources.add-mappings' from {} to {}", properties.isAddMappings(), newValue); + properties.setAddMappings(newValue); + } + return bean; + } + + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenProperties.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenProperties.java new file mode 100644 index 0000000..b065b8f --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenProperties.java @@ -0,0 +1,45 @@ +package net.cloudappi.apigen.archetypecore.autoconfigure; + +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.errors.ApigenError; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +@Data +@ConfigurationProperties(prefix = "apigen") +public class ApigenProperties { + + private static final int DEFAULT_EXPAND_LEVEL = 1; + + private String traceHeader = "x-trace-id"; + private Map errors; + private Api api = new Api(); + + @Data + public static class Api { + + private Expand expand = new Expand(); + private Map paths = new HashMap<>(); + + @Data + public static class Expand { + private Integer level = DEFAULT_EXPAND_LEVEL; + } + + @Data + public static class PathConfig { + + private ExpandConfig expand; + + @Data + public static class ExpandConfig { + private Integer level; + private Set allowed; + private Set excluded; + } + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudService.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudService.java new file mode 100644 index 0000000..c0e3a57 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudService.java @@ -0,0 +1,236 @@ +package net.cloudappi.apigen.archetypecore.core; + +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; +import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; +import org.springframework.lang.Nullable; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.Objects.nonNull; + +@Transactional +@SuppressWarnings("squid:S1192") +public abstract class AbstractCrudService, K extends Serializable, R extends ApigenRepository> + extends AbstractReadService { + + protected final AbstractRelationsManager relationsManager; + protected final ApigenMapper mapper; + + public AbstractCrudService(R repository, @Nullable AbstractRelationsManager relationsManager, @Nullable ApigenMapper mapper) { + super(repository); + this.relationsManager = relationsManager; + this.mapper = mapper; + } + + @Transactional + public E create(E entity) { + Assert.notNull(entity, "The argument entity cannot be null."); + preCreate(entity); + preCreateBeforeManageRelations(entity); + if (nonNull(relationsManager)) relationsManager.createOrRetrieveRelations(entity); + preCreateAfterManageRelations(entity); + entity = save(entity); + postCreate(entity); + return entity; + } + + @Transactional + public Set create(Set entities) { + Assert.notNull(entities, "The argument entities cannot be null."); + return entities.stream().map(this::create).collect(Collectors.toSet()); + } + + @Transactional + public List create(List entities) { + Assert.notNull(entities, "The argument entities cannot be null."); + return entities.stream().map(this::create).collect(Collectors.toList()); + } + + @Transactional + public E update(K id, E entity) { + return update(id, entity, null); + } + + @Transactional + public E update(K id, E entity, Set fields) { + Assert.notNull(id, "The argument id cannot be null."); + Assert.notNull(entity, "The argument entity cannot be null."); + E persistedEntity = getOne(id) + .orElseThrow(() -> new EntityNotFoundException(id, clazz)); + return update(persistedEntity, entity, fields); + } + + @Transactional + public E update(E persistedEntity, E entity) { + return update(persistedEntity, entity, null); + } + + @Transactional + public E update(E persistedEntity, E entity, Set fields) { + Assert.notNull(persistedEntity, "The argument persistedEntity cannot be null."); + Assert.notNull(entity, "The argument entity cannot be null."); + preUpdate(persistedEntity, entity, fields); + updateData(persistedEntity, entity, fields); + persistedEntity = save(persistedEntity); + postUpdate(persistedEntity, entity, fields); + return persistedEntity; + } + + protected E updateData(E persistedEntity, E entity) { + return updateData(persistedEntity, entity, null); + } + + protected E updateData(E persistedEntity, E entity, Set fields) { + updateBasicData(persistedEntity, entity, fields); + preUpdateBeforeManageRelations(persistedEntity, entity, fields); + if (nonNull(relationsManager)) relationsManager.updateRelations(persistedEntity, entity, fields); + preUpdateAfterManageRelations(persistedEntity, entity, fields); + return persistedEntity; + } + + @Transactional(propagation = Propagation.MANDATORY) + public E updateBasicData(E persistedEntity, E entity) { + return updateBasicData(persistedEntity, entity, null); + } + + @Transactional(propagation = Propagation.MANDATORY) + public E updateBasicData(E persistedEntity, E entity, Set fields) { + preUpdateAfterManageBasicData(persistedEntity, entity, fields); + K id = persistedEntity.getId(); + updateBasicDataPartially(persistedEntity, entity, fields); + persistedEntity.setId(id); + preUpdateBeforeManageBasicData(persistedEntity, entity, fields); + return persistedEntity; + } + + protected abstract void updateBasicDataPartially(E persistedEntity, E entity, Set fields); + + @Transactional + public Set update(Set persistedEntities, Collection entities) { + Assert.notNull(persistedEntities, "The argument persistedEntities cannot be null."); + Assert.notNull(entities, "The argument entities cannot be null."); + + updateCollection(persistedEntities, entities); + + return persistedEntities; + } + + @Transactional + public List update(List persistedEntities, Collection entities) { + Assert.notNull(persistedEntities, "The argument persistedEntities cannot be null."); + Assert.notNull(entities, "The argument entities cannot be null."); + + updateCollection(persistedEntities, entities); + + return persistedEntities; + } + + private void updateCollection(Collection persistedEntities, Collection entities) { + Map entitiesWithId = entities.stream() + .filter(entity -> entity.getId() != null) + .collect(Collectors.toMap(E::getId, Function.identity())); + + for (E persistedEntity : persistedEntities) { + K id = persistedEntity.getId(); + if (entitiesWithId.containsKey(id)) { + update(persistedEntity, entitiesWithId.get(id)); + entitiesWithId.remove(id); + } + } + } + + protected E save(E entity) { + Assert.notNull(entity, "The argument entity cannot be null."); + preSave(entity); + entity = this.repository.save(entity); + postSave(entity); + return entity; + } + + @Transactional + public void delete(K id) { + Assert.notNull(id, "The argument id cannot be null."); + E persistedEntity = getOne(id).orElseThrow(() -> new EntityNotFoundException(id, clazz)); + delete(persistedEntity); + } + + @Transactional + public void delete(E entity) { + Assert.notNull(entity, "The argument entity cannot be null."); + preDelete(entity); + this.repository.delete(entity); + postDelete(entity); + } + + @Transactional + public void delete(Collection entities) { + Assert.notNull(entities, "The argument entities cannot be null."); + entities.forEach(this::delete); + } + + protected void preCreate(E entity) { + // Override if required + } + + protected void preCreateBeforeManageRelations(E entity) { + // Override if required + } + + protected void preCreateAfterManageRelations(E entity) { + // Override if required + } + + protected void postCreate(E entity) { + // Override if required + } + + protected void preUpdate(E persistedEntity, E entity, Set fields) { + // Override if required + } + + protected void preUpdateAfterManageBasicData(E persistedEntity, E entity, Set fields) { + // Override if required + } + + protected void preUpdateBeforeManageBasicData(E persistedEntity, E entity, Set fields) { + // Override if required + } + + protected void preUpdateAfterManageRelations(E persistedEntity, E entity, Set fields) { + // Override if required + } + + protected void preUpdateBeforeManageRelations(E persistedEntity, E entity, Set fields) { + // Override if required + } + + protected void postUpdate(E persistedEntity, E entity, Set fields) { + // Override if required + } + + protected void preDelete(E entity) { + // Override if required + } + + protected void postDelete(E entity) { + // Override if required + } + + protected void preSave(E entity) { + // Override if required + } + + protected void postSave(E entity) { + // Override if required + } +} \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractReadService.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractReadService.java new file mode 100644 index 0000000..f23e45e --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractReadService.java @@ -0,0 +1,81 @@ +package net.cloudappi.apigen.archetypecore.core; + +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearch; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.core.persistence.pagination.Pagination; +import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; +import org.springframework.core.GenericTypeResolver; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +@Transactional +@SuppressWarnings("squid:S1192") +public abstract class AbstractReadService, K extends Serializable, R extends ApigenRepository> { + + protected R repository; + protected Class clazz; + + @SuppressWarnings({"unchecked", "ConstantConditions"}) + public AbstractReadService(R repository) { + this.repository = repository; + this.clazz = (Class) GenericTypeResolver.resolveTypeArguments(getClass(), AbstractReadService.class)[0]; + } + + @Transactional(readOnly = true) + public Optional getOne(K id) { + Assert.notNull(id, "The argument id cannot be null"); + return repository.findById(id); + } + + @Transactional(readOnly = true) + public List getAll(Iterable ids) { + Assert.notNull(ids, "The argument ids cannot be null"); + return repository.findAllById(ids); + } + + @Transactional(readOnly = true) + public List getAll() { + return repository.findAll(); + } + + @Transactional(readOnly = true) + public List getAll(Sort sort) { + Assert.notNull(sort, "The argument sort cannot be null"); + return repository.findAll(sort); + } + + @Transactional(readOnly = true) + public Page getAll(Pageable pageable) { + Assert.notNull(pageable, "The argument pageable cannot be null"); + return repository.findAll(pageable); + } + + @Transactional(readOnly = true) + public E search(K id, List select, List exclude, List expand) { + Assert.notNull(id, "The argument id cannot be null"); + ApigenSearch search = new ApigenSearch(select, exclude, expand); + return repository.searchById(id, search).orElseThrow(() -> new EntityNotFoundException(id, clazz)); + } + + @Transactional(readOnly = true) + public ApigenSearchResult search(List select, List exclude, List expand, Filter filter, List orderBy, Integer init, Integer limit, Boolean total) { + Pagination pagination = null; + if (init != null && limit != null) { + pagination = new Pagination(init, limit); + } + if (total == null) total = false; + ApigenSearch search = new ApigenSearch(select, exclude, expand, filter, orderBy, pagination, total); + search.setTotal(total); + return repository.search(search); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractRelationsManager.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractRelationsManager.java new file mode 100644 index 0000000..3870409 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractRelationsManager.java @@ -0,0 +1,19 @@ +package net.cloudappi.apigen.archetypecore.core; + +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Set; + +public class AbstractRelationsManager { + + @Transactional(propagation = Propagation.MANDATORY) + public void createOrRetrieveRelations(E entity) { + // Override if required + } + + @Transactional(propagation = Propagation.MANDATORY) + public void updateRelations(E persistedEntity, E entity, Set fields) { + // Override if required + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/ApigenMapper.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/ApigenMapper.java new file mode 100644 index 0000000..3a3fe9e --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/ApigenMapper.java @@ -0,0 +1,5 @@ +package net.cloudappi.apigen.archetypecore.core; + +public interface ApigenMapper { + void updateBasicData(E source, E target); +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdvice.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdvice.java new file mode 100644 index 0000000..f537f98 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdvice.java @@ -0,0 +1,405 @@ +package net.cloudappi.apigen.archetypecore.core.advice; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.core.errors.ApigenErrorManager; +import net.cloudappi.apigen.archetypecore.core.errors.DefaultApigenError; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; +import net.cloudappi.apigen.archetypecore.exceptions.*; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.FieldError; +import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.servlet.NoHandlerFoundException; + +import javax.validation.ConstraintViolation; +import javax.validation.ConstraintViolationException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.lang.reflect.ParameterizedType; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Slf4j +@ControllerAdvice +public class ApigenControllerAdvice { + + private final ApigenErrorManager errorManager; + + public ApigenControllerAdvice(ApigenErrorManager errorManager) { + this.errorManager = errorManager; + } + + @ResponseBody + @ExceptionHandler(MissingServletRequestParameterException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse requiredQueryParamNotFound(MissingServletRequestParameterException ex) { + String paramName = ex.getParameterName(); + ApiError error = errorManager.getError(DefaultApigenError.QUERY_PARAM_REQUIRED, paramName); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(ConstraintViolationException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse queryParamNotValid(ConstraintViolationException ex) { + List errors = new ArrayList<>(); + for (ConstraintViolation violation : ex.getConstraintViolations()) { + String path = violation.getPropertyPath().toString(); + String[] pathParts = path.split("\\."); + String methodName = pathParts[0]; + String parameterName = pathParts[1]; + Method[] methods = violation.getRootBeanClass().getDeclaredMethods(); + String fieldName = Stream.of(methods) + .filter(m -> methodName.equals(m.getName())) + .flatMap(m -> Stream.of(m.getParameters())) + .filter(p -> parameterName.equals(p.getName())) + .filter(p -> p.isAnnotationPresent(RequestParam.class)) + .map(p -> p.getDeclaredAnnotation(RequestParam.class)) + .map(RequestParam::value) + .findFirst().orElse(parameterName); + String code = violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(); + Object[] args = violation.getExecutableParameters(); + errors.add(getValidationError(fieldName, code, args)); + } + return new ApiResponse().withResultErrors(errors); + } + + @ResponseBody + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse mismatch(MethodArgumentTypeMismatchException ex) { + Parameter parameter = ex.getParameter().getParameter(); + String variableName = parameter.getName(); + if (parameter.isAnnotationPresent(PathVariable.class)) { + PathVariable pathVariable = parameter.getDeclaredAnnotation(PathVariable.class); + if (!pathVariable.value().trim().equals("")) variableName = pathVariable.value(); + } + ApiError error = errorManager.getError(DefaultApigenError.PATH_VARIABLE_ERROR, variableName); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse argumentNotValid(MethodArgumentNotValidException ex) { + List errors = new ArrayList<>(); + Object target = ex.getBindingResult().getTarget(); + Class clazz = target.getClass(); + FieldNameTranslator fieldNamesTranslator = new FieldNameTranslator(clazz); + for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) { + String field = fieldNamesTranslator.translate(fieldError.getField()); + String code = fieldError.getCode(); + ApiError error = getValidationError(field, code, fieldError.getArguments()); + errors.add(error); + } + return new ApiResponse().withResultErrors(errors); + } + + private ApiError getValidationError(String field, String code, Object[] args) { + DefaultApigenError error = getError(code, args); + Object[] errorArgs = getArgs(code, args); + return errorManager.getError(error, field, errorArgs); + } + + private DefaultApigenError getError(String code, Object[] args) { + boolean inclusive; + switch (code) { + case "NotNull": + return DefaultApigenError.VALIDATION_NOT_NULL; + case "Email": + return DefaultApigenError.VALIDATION_EMAIL; + case "NotEmpty": + return DefaultApigenError.VALIDATION_NOT_EMPTY; + case "NotBlank": + return DefaultApigenError.VALIDATION_NOT_BLANK; + case "Positive": + return DefaultApigenError.VALIDATION_MIN_VALUE; + case "Min": + case "PositiveOrZero": + return DefaultApigenError.VALIDATION_MIN_EQ_VALUE; + case "Negative": + return DefaultApigenError.VALIDATION_MAX_VALUE; + case "Max": + case "NegativeOrZero": + return DefaultApigenError.VALIDATION_MAX_EQ_VALUE; + case "Past": + return DefaultApigenError.VALIDATION_PAST_DATE; + case "PastOrPresent": + return DefaultApigenError.VALIDATION_PAST_NOW_DATE; + case "Future": + return DefaultApigenError.VALIDATION_FUTURE_DATE; + case "FutureOrPresent": + return DefaultApigenError.VALIDATION_FUTURE_NOW_DATE; + case "DecimalMin": + inclusive = (boolean) args[1]; + return inclusive ? DefaultApigenError.VALIDATION_MIN_EQ_VALUE : DefaultApigenError.VALIDATION_MIN_VALUE; + case "DecimalMax": + inclusive = (boolean) args[1]; + return inclusive ? DefaultApigenError.VALIDATION_MAX_EQ_VALUE : DefaultApigenError.VALIDATION_MAX_VALUE; + case "Size": + return DefaultApigenError.VALIDATION_BAD_SIZE; + case "Digits": + return DefaultApigenError.VALIDATION_DECIMAL; + case "Pattern": + return DefaultApigenError.VALIDATION_REGEX; + default: + return DefaultApigenError.VALIDATION_ERROR; + } + } + + private Object[] getArgs(String code, Object[] allArgs) { + Object[] args = {}; + switch (code) { + case "Positive": + case "PositiveOrZero": + case "Negative": + case "NegativeOrZero": + return toArgs(0); + case "Min": + case "Max": + return toArgs(allArgs[1]); + case "DecimalMin": + case "DecimalMax": + case "Pattern": + return toArgs(allArgs[2]); + case "Size": + case "Digits": + return toArgs(allArgs[2], allArgs[1]); + default: + return args; + } + } + + private Object[] toArgs(Object... args) { + return args; + } + + @ResponseBody + @ExceptionHandler(InvalidPropertyPath.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse requiredQueryParamNotFound(InvalidPropertyPath ex) { + List errors = new ArrayList<>(); + for (String path : ex.getInvalidSelectPath()) { + errors.add(errorManager.getError(DefaultApigenError.BAD_PROPERTY_IN_SELECT, path)); + } + for (String path : ex.getInvalidExcludePath()) { + errors.add(errorManager.getError(DefaultApigenError.BAD_PROPERTY_IN_EXCLUDE, path)); + } + for (String path : ex.getInvalidExpandPath()) { + errors.add(errorManager.getError(DefaultApigenError.BAD_PROPERTY_IN_EXPAND, path)); + } + for (String path : ex.getInvalidFilterPath()) { + errors.add(errorManager.getError(DefaultApigenError.BAD_PROPERTY_IN_FILTER, path)); + } + for (String path : ex.getInvalidOrderByPath()) { + errors.add(errorManager.getError(DefaultApigenError.BAD_PROPERTY_IN_ORDER_BY, path)); + } + for (String path : ex.getInvalidOrderByToManyPath()) { + errors.add(errorManager.getError(DefaultApigenError.PROPERTY_NOT_ALLOWED_IN_ORDER_BY, path)); + } + return new ApiResponse().withResultErrors(errors); + } + + @ResponseBody + @ExceptionHandler(EntityNotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ApiResponse entityNotFound(EntityNotFoundException ex) { + ApiError error = errorManager.getError(DefaultApigenError.ELEMENT_NOT_FOUND, ex.getId().toString(), ex.getClazz().getSimpleName()); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(RelationalErrorsException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse relatedEntitiesNotFound(RelationalErrorsException ex) { + List errors = ex.getRelationalErrors().getErrors().stream() + .map(e -> errorManager.getError( + DefaultApigenError.RELATED_ELEMENT_NOT_FOUND, + e.getId() == null ? "null" : e.getId().toString(), + e.getClazz().getSimpleName() + )) + .collect(Collectors.toList()); + return new ApiResponse().withResultErrors(errors); + } + + @ResponseBody + @ExceptionHandler(CustomApigenException.class) + public ResponseEntity customException(CustomApigenException ex) { + List errors = ex.getErrors().stream(). + map(error -> errorManager.getError(error.getKey(), error.getElement(), error.getOtherElements())) + .collect(Collectors.toList()); + ApiResponse response = new ApiResponse().withResultErrors(errors); + return new ResponseEntity<>(response, ex.getHttpStatus()); + } + + @ResponseBody + @ExceptionHandler(HttpMediaTypeNotAcceptableException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse mediaTypeNotSupported(HttpMediaTypeNotAcceptableException ex) { + ApiError error = errorManager.getError(DefaultApigenError.UNSUPPORTED_FORMAT, null, ex.getSupportedMediaTypes()); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(HttpMessageNotReadableException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse requiredBodyNotFound(HttpMessageNotReadableException ex) { + Throwable cause = ex.getCause(); + if (cause instanceof InvalidFormatException) { + ApiResponse response = getResponseFromInvalidFormatException((InvalidFormatException) cause); + if (response != null) return response; + } + log.error("Body error", ex); + ApiError error = errorManager.getError(DefaultApigenError.EMPTY_REQUEST_BODY); + return new ApiResponse().withResultErrors(errors(error)); + } + + private ApiResponse getResponseFromInvalidFormatException(InvalidFormatException exception) { + Throwable cause = exception.getCause(); + if (cause instanceof DateTimeParseException) { + DateTimeParseException explicit = (DateTimeParseException) cause; + ApiError error = errorManager.getError(DefaultApigenError.ERROR_PARSING_ISO_DATE, explicit.getParsedString()); + return new ApiResponse().withResultErrors(errors(error)); + } + String message = exception.getMessage(); + if (message.contains("Enum class")) { + int s = message.indexOf('['); + int e = message.indexOf(']'); + if (s > -1 && e > -1) { + String values = message.substring(s + 1, e); + Object currentValue = exception.getValue(); + ApiError error = errorManager.getError(DefaultApigenError.UNSUPPORTED_VALUE, currentValue.toString(), values); + return new ApiResponse().withResultErrors(errors(error)); + } + } + return null; + } + + @ResponseBody + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + @ResponseStatus(HttpStatus.NOT_IMPLEMENTED) + public ApiResponse methodNotSupported(HttpRequestMethodNotSupportedException ex) { + ApiError error = errorManager.getError(DefaultApigenError.METHOD_NOT_IMPLEMENTED, ex.getMethod()); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(NotImplementedException.class) + @ResponseStatus(HttpStatus.NOT_IMPLEMENTED) + public ApiResponse methodNotImplemented(NotImplementedException ex) { + ApiError error = errorManager.getError(DefaultApigenError.METHOD_NOT_IMPLEMENTED, ex.getMessage()); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(NoHandlerFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ApiResponse handlerNotFound(NoHandlerFoundException ex) { + ApiError error = errorManager.getError(DefaultApigenError.PATH_NOT_IMPLEMENTED, ex.getRequestURL()); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(PatternSyntaxException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleRegexException(PatternSyntaxException ex) { + ApiError error = errorManager.getError(DefaultApigenError.INVALID_REGEX_EXPRESSION, ex.getPattern()); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(InvalidDataAccessApiUsageException.class) + public ResponseEntity handleDataAccessApiUsage(InvalidDataAccessApiUsageException ex) { + ApiError error; + if (ex.getCause() instanceof PatternSyntaxException) { + error = errorManager.getError(DefaultApigenError.INVALID_REGEX_EXPRESSION, ((PatternSyntaxException)ex.getCause()).getPattern()); + } else if (ex.getCause() instanceof IllegalArgumentException) { + error = errorManager.getError(DefaultApigenError.ILLEGAL_ARGUMENT, ex.getCause().getMessage()); + } else { + return new ResponseEntity<>(exception(ex), HttpStatus.INTERNAL_SERVER_ERROR); + } + return new ResponseEntity<>(new ApiResponse().withResultErrors(errors(error)), HttpStatus.BAD_REQUEST); + } + + @ResponseBody + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResponse exception(Exception ex) { + log.error("Unexpected error", ex); + ApiError error = errorManager.getError(DefaultApigenError.UNEXPECTED_ERROR); + return new ApiResponse().withResultErrors(errors(error)); + } + + private List errors(ApiError... apiErrors) { + return Arrays.asList(apiErrors); + } + + private static class FieldNameTranslator { + Map dictionary; + + FieldNameTranslator(Class clazz) { + dictionary = getDictionary(clazz); + } + + private Map getDictionary(Class clazz) { + Map levelDictionary = new HashMap<>(); + for (Field field : clazz.getDeclaredFields()) { + String javaName = field.getName(); + String resourceName = javaName; + if (field.isAnnotationPresent(JsonProperty.class)) { + JsonProperty annotation = field.getDeclaredAnnotation(JsonProperty.class); + resourceName = annotation.value(); + } + levelDictionary.put(javaName, resourceName); + Class fieldClazz = field.getType(); + if (field.getGenericType() instanceof ParameterizedType) { + fieldClazz = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + } + Package pkg = fieldClazz.getPackage(); + if (pkg != null && !pkg.getName().startsWith("java") && !fieldClazz.equals(Filter.class)) { + Map nestedNames = getDictionary(fieldClazz); + String resName = resourceName; + nestedNames.forEach((key, value) -> levelDictionary.put(javaName + "." + key, resName + "." + value)); + } + } + return levelDictionary; + } + + public String translate(String name) { + String[] originalParts = name.split("\\."); + String[] arrayParts = new String[originalParts.length]; + for (int i = 0; i < originalParts.length; i++) { + int idx = originalParts[i].indexOf('['); + if (idx > -1) { + arrayParts[i] = originalParts[i].substring(idx); + originalParts[i] = originalParts[i].substring(0, idx); + } else { + arrayParts[i] = ""; + } + } + String key = String.join(".", originalParts); + String value = dictionary.get(key); + if (value == null) return name; + String[] newParts = value.split("\\."); + for (int i = 0; i < originalParts.length; i++) { + newParts[i] = newParts[i] + arrayParts[i]; + } + return String.join(".", newParts); + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenError.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenError.java new file mode 100644 index 0000000..e99f599 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenError.java @@ -0,0 +1,17 @@ +package net.cloudappi.apigen.archetypecore.core.errors; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApigenError { + @NotNull + private Integer code; + @NotNull + private String messageTemplate; +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenErrorManager.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenErrorManager.java new file mode 100644 index 0000000..f3a7f7c --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenErrorManager.java @@ -0,0 +1,13 @@ +package net.cloudappi.apigen.archetypecore.core.errors; + +import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; + +public interface ApigenErrorManager { + ApiError getError(DefaultApigenError key); + + ApiError getError(String key); + + ApiError getError(DefaultApigenError key, String element, Object... otherElements); + + ApiError getError(String key, String element, Object... otherElements); +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenError.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenError.java new file mode 100644 index 0000000..11c6708 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenError.java @@ -0,0 +1,59 @@ +package net.cloudappi.apigen.archetypecore.core.errors; + +import lombok.Getter; + +@Getter +public enum DefaultApigenError { + + // 10xx : Validation error + VALIDATION_NOT_NULL(1000, "Property ''{0}'' must be not null"), + VALIDATION_BAD_SIZE(1001, "Property ''{0}'' must have a length between ''{1}'' and ''{2}''"), + VALIDATION_MIN_VALUE(1002, "Property ''{0}'' must have a value greater than ''{1}''"), + VALIDATION_MIN_EQ_VALUE(1003, "Property ''{0}'' must have a value greater or equal to ''{1}''"), + VALIDATION_MAX_VALUE(1004, "Property ''{0}'' must have a value less than ''{1}''"), + VALIDATION_MAX_EQ_VALUE(1005, "Property ''{0}'' must have a value less or equal to ''{1}''"), + VALIDATION_REGEX(1006, "Property ''{0}'' must follow the regex ''{1}''"), + VALIDATION_NOT_EMPTY(1007, "Property ''{0}'' must not be empty"), + VALIDATION_NOT_BLANK(1008, "Property ''{0}'' must not be blank"), + VALIDATION_EMAIL(1009, "Property ''{0}'' must be an email"), + VALIDATION_PAST_DATE(1010, "Property ''{0}'' must be a past date"), + VALIDATION_PAST_NOW_DATE(1011, "Property ''{0}'' must be a past or present date"), + VALIDATION_FUTURE_DATE(1012, "Property ''{0}'' must be a future"), + VALIDATION_FUTURE_NOW_DATE(1013, "Property ''{0}'' must be a future or present"), + VALIDATION_DECIMAL(1014, "Property ''{0}'' must be a decimal with ''{1}'' integer digits and ''{2}'' decimal digits"), + VALIDATION_ERROR(1099, "Property ''{0}'' must be valid"), + + // 110x : Property path error + BAD_PROPERTY_IN_FILTER(1100, "Invalid property ''{0}'' in $filter"), + BAD_PROPERTY_IN_SELECT(1101, "Invalid property ''{0}'' in $select"), + BAD_PROPERTY_IN_EXCLUDE(1102, "Invalid property ''{0}'' in $exclude"), + BAD_PROPERTY_IN_EXPAND(1103, "Invalid property ''{0}'' in $expand"), + BAD_PROPERTY_IN_ORDER_BY(1104, "Invalid property ''{0}'' in $orderby"), + PROPERTY_NOT_ALLOWED_IN_ORDER_BY(1105, "Invalid property ''{0}'' in $orderby"), + ELEMENT_NOT_FOUND(1106, "Element with id ''{0}'' of type ''{1}'' not found"), + RELATED_ELEMENT_NOT_FOUND(1107, "Related element with id ''{0}'' of type ''{1}'' not found"), + ERROR_PARSING_ISO_DATE(1108, "Error parsing ISO date ''{0}''"), + PATH_VARIABLE_ERROR(1109, "Error parsing path variable ''{0}''"), + QUERY_PARAM_REQUIRED(1110, "Query parameter ''{0}'' is required"), + INVALID_REGEX_EXPRESSION(1111, "Invalid regex expression ''{0}''"), + ILLEGAL_ARGUMENT(1112, "Illegal argument: {0}"), + + + // 12xx : General errors + METHOD_NOT_IMPLEMENTED(1200, "Method ''{0}'' not implemented"), + PATH_NOT_IMPLEMENTED(1201, "Path ''{0}'' not implemented"), + EMPTY_REQUEST_BODY(1202, "Request body required"), + UNSUPPORTED_FORMAT(1203, "Format not supported, supported formats: ''{1}''"), + UNSUPPORTED_VALUE(1204, "Unsupported value ''{0}'', accepted values: ''{1}''"), + + // 13xx : Unexpected errors + UNEXPECTED_ERROR(1300, "Unexpected error"); + + private final Integer defaultCode; + private final String defaultMessage; + + private DefaultApigenError(Integer defaultCode, String defaultMessage) { + this.defaultCode = defaultCode; + this.defaultMessage = defaultMessage; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java new file mode 100644 index 0000000..466da1b --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java @@ -0,0 +1,57 @@ +package net.cloudappi.apigen.archetypecore.core.errors; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenProperties; +import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; + +import java.text.MessageFormat; +import java.util.*; + +@Slf4j +public class DefaultApigenErrorManager implements ApigenErrorManager { + + private Map errorTemplates = new HashMap<>(); + + public DefaultApigenErrorManager(ApigenProperties properties) { + for (DefaultApigenError defaultErrorCodes : DefaultApigenError.values()) { + registerErrorTemplate(defaultErrorCodes.name(), defaultErrorCodes.getDefaultCode(), defaultErrorCodes.getDefaultMessage()); + } + if (properties.getErrors() != null) { + properties.getErrors().forEach(this::registerErrorTemplate); + } + } + + public ApigenError registerErrorTemplate(String key, Integer code, String errorTemplate) { + ApigenError errorCode = new ApigenError(code, errorTemplate); + return registerErrorTemplate(key, errorCode); + } + + public ApigenError registerErrorTemplate(String key, ApigenError errorCode) { + return errorTemplates.put(key, errorCode); + } + + public ApiError getError(DefaultApigenError key) { + return getError(key.name()); + } + + public ApiError getError(String key) { + return getError(key, null); + } + + public ApiError getError(DefaultApigenError key, String element, Object... otherElements) { + return getError(key.name(), element, otherElements); + } + + public ApiError getError(String key, String element, Object... otherElements) { + ApigenError errorCode = errorTemplates.get(key); + if (errorCode == null) { + log.error("Error code not configured for {}, using unexpected error message", key); + errorCode = errorTemplates.get(DefaultApigenError.UNEXPECTED_ERROR.name()); + } + List params = new ArrayList<>(); + params.add(element); + params.addAll(Arrays.asList(otherElements)); + String message = MessageFormat.format(errorCode.getMessageTemplate(), params.toArray()); + return new ApiError(errorCode.getCode(), message, element); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java new file mode 100644 index 0000000..8ef751a --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java @@ -0,0 +1,55 @@ +package net.cloudappi.apigen.archetypecore.core.persistence; + +import org.springframework.data.domain.Persistable; +import org.springframework.data.util.ProxyUtils; +import org.springframework.lang.Nullable; + +import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; +import java.io.Serializable; + +@MappedSuperclass +public abstract class ApigenAbstractPersistable implements Persistable { + private static final long serialVersionUID = -5554308939380869754L; + + public ApigenAbstractPersistable() { + } + + @Nullable + @Transient + public abstract K getId(); + + @Transient + public abstract void setId(@Nullable K id); + + @Transient + public boolean isNew() { + return null == this.getId(); + } + + @Transient + public abstract boolean isReference(); + + public String toString() { + return String.format("Entity of type %s with id: %s", this.getClass().getName(), this.getId()); + } + + public boolean equals(Object obj) { + if (null == obj) { + return false; + } else if (this == obj) { + return true; + } else if (!this.getClass().equals(ProxyUtils.getUserClass(obj))) { + return false; + } else { + ApigenAbstractPersistable that = (ApigenAbstractPersistable) obj; + return null != this.getId() && this.getId().equals(that.getId()); + } + } + + public int hashCode() { + int hashCode = 17; + if (this.getId() != null) hashCode += this.getId().hashCode() * 31; + return hashCode; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepository.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepository.java new file mode 100644 index 0000000..54c37be --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepository.java @@ -0,0 +1,15 @@ +package net.cloudappi.apigen.archetypecore.core.persistence; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +import java.io.Serializable; +import java.util.Optional; + +@NoRepositoryBean +public interface ApigenRepository extends JpaRepository { + + ApigenSearchResult search(ApigenSearch search); + + Optional searchById(K id, ApigenSearch search); +} \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java new file mode 100644 index 0000000..173f5a0 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java @@ -0,0 +1,35 @@ +package net.cloudappi.apigen.archetypecore.core.persistence; + +import net.cloudappi.apigen.archetypecore.core.persistence.executor.ApigenSearchExecutor; +import net.cloudappi.apigen.archetypecore.core.persistence.executor.EntityInfo; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.SimpleJpaRepository; + +import javax.persistence.EntityManager; +import java.io.Serializable; +import java.util.Optional; + +public class ApigenRepositoryImpl extends SimpleJpaRepository implements ApigenRepository { + + private Class clazz; + private EntityManager em; + private ApigenSearchExecutor searchExecutor; + + public ApigenRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) { + super(entityInformation, entityManager); + this.clazz = entityInformation.getJavaType(); + this.em = entityManager; + this.searchExecutor = new ApigenSearchExecutor(em, EntityInfo.getInstance(em)); + } + + @Override + public ApigenSearchResult search(ApigenSearch search) { + return searchExecutor.search(search.getSelect(), search.getExclude(), search.getOrderBy(), search.getExpand(), search.getFilter(), search.getPagination(), search.getTotal(), clazz); + } + + @Override + public Optional searchById(K id, ApigenSearch search) { + return searchExecutor.searchById(id, search.getSelect(), search.getExclude(), search.getOrderBy(), search.getExpand(), clazz); + } + +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearch.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearch.java new file mode 100644 index 0000000..93e15af --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearch.java @@ -0,0 +1,34 @@ +package net.cloudappi.apigen.archetypecore.core.persistence; + +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.core.persistence.pagination.Pagination; + +import java.util.List; + +@Data +public class ApigenSearch { + private List select; + private List exclude; + private List expand; + private Filter filter; + private List orderBy; + private Pagination pagination; + private Boolean total = false; + + public ApigenSearch(List select, List exclude, List expand) { + this.select = select; + this.exclude = exclude; + this.expand = expand; + } + + public ApigenSearch(List select, List exclude, List expand, Filter filter, List orderBy, Pagination pagination, Boolean total) { + this.select = select; + this.exclude = exclude; + this.expand = expand; + this.filter = filter; + this.orderBy = orderBy; + this.pagination = pagination; + this.total = total; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearchResult.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearchResult.java new file mode 100644 index 0000000..6bc61eb --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearchResult.java @@ -0,0 +1,21 @@ +package net.cloudappi.apigen.archetypecore.core.persistence; + +import lombok.Data; + +import java.util.List; + +@Data +public class ApigenSearchResult { + + private List searchResult; + private Long total; + + public ApigenSearchResult(List searchResult, Long total) { + this.searchResult = searchResult; + this.total = total; + } + + public ApigenSearchResult(List searchResult) { + this.searchResult = searchResult; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java new file mode 100644 index 0000000..2b724d6 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java @@ -0,0 +1,369 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.executor; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Value; +import net.cloudappi.apigen.archetypecore.core.persistence.pagination.Pagination; + +import javax.persistence.EntityManager; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.*; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static java.util.Objects.isNull; +import static net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation.AND; +import static net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation.OR; +import static net.cloudappi.apigen.archetypecore.core.persistence.functions.ApigenFunctionsMetadataBuilderInitializer.REGEXP_FUNCTION; + +@Slf4j +public class ApigenSearchExecutor { + + private EntityManager em; + private EntityInfo entityData; + + public ApigenSearchExecutor(EntityManager em, EntityInfo entityData) { + this.em = em; + this.entityData = entityData; + } + + public Optional searchById(K id, List select, List exclude, List orderBy, List expand, Class clazz) { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + + CriteriaQuery query = builder.createTupleQuery(); + + Root root = query.from(clazz); + + Map joins = join(expand, root); + List enhancedFields = getFields(select, exclude, root, joins); + List> selections = select(enhancedFields, root, joins); + query.multiselect(selections); + List order = order(orderBy, builder, root, joins); + query.orderBy(order); + + String idAttribute = entityData.getIdAttribute(clazz); + Expression expression = getPath(idAttribute, root, joins); + Predicate predicate = builder.equal(expression, id); + + if (predicate != null) query.where(predicate); + TypedQuery createQuery = em.createQuery(query); + List result = createQuery.getResultList(); + List found = new TupleMapper(enhancedFields, entityData, clazz).map(result); + + if (found.isEmpty()) return Optional.empty(); + return Optional.of(found.get(0)); + } + + public ApigenSearchResult search(List select, List exclude, List orderBy, List expand, Filter filter, Pagination pagination, boolean total, Class clazz) { + + CriteriaBuilder builder = em.getCriteriaBuilder(); + + CriteriaQuery query = builder.createTupleQuery(); + + Root root = query.from(clazz); + + Map joins = join(expand, root); + List enhancedFields = getFields(select, exclude, root, joins); + List> selections = select(enhancedFields, root, joins); + query.multiselect(selections); + Long count = null; + if (pagination != null) { + addPaginationPredicate(query, root, orderBy, expand, filter, pagination, clazz, builder); + } else { + addPredicate(query, filter, root, joins, builder); + } + if (total) { + count = count(clazz, expand, filter, builder); + } + addOrder(query, orderBy, root, joins, builder); + List result = em.createQuery(query).getResultList(); + return new ApigenSearchResult<>(new TupleMapper(enhancedFields, entityData, clazz).map(result), count); + } + + private void addPaginationPredicate(CriteriaQuery query, Root root, List orderBy, List expand, Filter filter, Pagination pagination, Class clazz, CriteriaBuilder builder) { + List ids = getMatchingIds(orderBy, expand, filter, pagination, clazz, builder); + String idAttribute = entityData.getIdAttribute(clazz); + Path path = root.get(idAttribute); + Predicate predicate = path.in(ids); + query.where(predicate); + } + + private List getMatchingIds(List orderBy, List expand, Filter filter, Pagination pagination, Class clazz, CriteriaBuilder builder) { + CriteriaQuery query = builder.createTupleQuery(); + Root root = query.from(clazz); + Map joins = join(expand, root); + String idAttribute = entityData.getIdAttribute(clazz); + List allFields = new LinkedList<>(orderBy); + if (!allFields.contains(idAttribute)) allFields.add(idAttribute); + List> orderSelect = orderFields(allFields, builder, root, joins); + query.multiselect(orderSelect); + query.distinct(true); + addPredicate(query, filter, root, joins, builder); + addOrder(query, orderBy, root, joins, builder); + TypedQuery createQuery = em.createQuery(query); + addPaging(createQuery, pagination); + List resultId = createQuery.getResultList(); + ArrayList idValues = new ArrayList<>(); + int i = allFields.indexOf(idAttribute); + for (Tuple tuple : resultId) { + idValues.add(tuple.get(i)); + } + return idValues; + } + + private Long count(Class clazz, List expand, Filter filter, CriteriaBuilder builder) { + CriteriaQuery query = builder.createQuery(Long.class); + Root root = query.from(clazz); + Map joins = join(expand, root); + query.select(builder.count(root)); + query.distinct(true); + addPredicate(query, filter, root, joins, builder); + return em.createQuery(query).getSingleResult(); + } + + private Map join(List expand, Root root) { + Map joins = new HashMap<>(); + if (expand == null || expand.isEmpty()) return joins; + for (String e : expand) { + String[] path = e.split("\\."); + String fullPath = path[0]; + Join join = joins.computeIfAbsent(fullPath, k -> root.join(path[0], JoinType.LEFT)); + for (int i = 1; i < path.length; i++) { + Join parentJoin = join; + fullPath = fullPath.concat(".").concat(path[i]); + int finalI = i; + join = joins.computeIfAbsent(fullPath, k -> parentJoin.join(path[finalI], JoinType.LEFT)); + } + } + return joins; + } + + private List getFields(List select, List exclude, Root root, Map joins) { + if (select == null || select.isEmpty()) { + return getFieldsFromExclusion(root.getJavaType(), exclude, joins); + } else { + return getFieldsFromInclusion(select, joins, root.getJavaType()); + } + } + + private List getFieldsFromExclusion(Class clazz, List exclude, Map joins) { + List fields = entityData.getBasicAttributes(clazz); + if (joins != null) fields.addAll(getFieldsFromExpands(clazz, joins)); + if (exclude != null) fields.removeAll(exclude); + return fields; + } + + private List getFieldsFromExpands(Class clazz, Map joins) { + return joins.keySet().stream().flatMap(field -> entityData.getBasicAttributes(clazz, field).stream()).collect(Collectors.toList()); + } + + private List getFieldsFromInclusion(List fields, Map joins, Class clazz) { + List enhancedFields = new ArrayList<>(fields); + String idAttribute = entityData.getIdAttribute(clazz); + if (!enhancedFields.contains(idAttribute)) enhancedFields.add(idAttribute); + joins.keySet().stream().map(f -> f.concat("." + entityData.getIdAttribute(clazz, f))).filter(f -> !enhancedFields.contains(f)).forEach(enhancedFields::add); + return enhancedFields; + } + + private List> select(List fields, Root root, Map joins) { + return fields.stream().map(f -> getPath(f, root, joins)).collect(Collectors.toList()); + } + + private List> orderFields(List orderBy, CriteriaBuilder builder, Root root, Map joins) { + if (orderBy == null || orderBy.isEmpty()) return Collections.emptyList(); + return orderBy.stream().map(o -> { + boolean asc = true; + if (o.charAt(0) == '+') { + o = o.substring(1); + } else if (o.charAt(0) == '-') { + asc = false; + o = o.substring(1); + } + return getPath(o, root, joins); + }).collect(Collectors.toList()); + } + + private List order(List orderBy, CriteriaBuilder builder, Root root, Map joins) { + if (orderBy == null || orderBy.isEmpty()) return Collections.emptyList(); + return orderBy.stream().map(o -> { + boolean asc = true; + if (o.charAt(0) == '+') { + o = o.substring(1); + } else if (o.charAt(0) == '-') { + asc = false; + o = o.substring(1); + } + Path s = getPath(o, root, joins); + return asc ? builder.asc(s) : builder.desc(s); + }).collect(Collectors.toList()); + } + + private Path getPath(String field, Root root, Map joins) { + int i = field.lastIndexOf('.'); + if (i > -1) { + String base = field.substring(0, i); + String simpleField = field.substring(i + 1); + return joins.get(base).get(simpleField); + } else { + return root.get(field); + } + } + + private void addOrder(CriteriaQuery query, List orderBy, Root root, Map joins, CriteriaBuilder builder) { + List order = order(orderBy, builder, root, joins); + query.orderBy(order); + } + + private void addPredicate(CriteriaQuery query, Filter filter, Root root, Map joins, CriteriaBuilder builder) { + Predicate predicate = filter(filter, builder, root, joins); + if (predicate != null) query.where(predicate); + } + + private void addPaging(TypedQuery createQuery, Pagination pagination) { + int firstResult = getFirstResult(pagination); + if (firstResult > 0) { + createQuery.setFirstResult(firstResult); + } + if (pagination.getLimit() > 0) { + createQuery.setMaxResults(pagination.getLimit()); + } + } + + + private static int getFirstResult(Pagination search) { + int firstResult = search.getInit(); + return Math.max(firstResult, 0); + } + + @SuppressWarnings("unchecked") + private Predicate filter(Filter filter, CriteriaBuilder builder, Root root, Map joins) { + + if (filter == null) return null; + + FilterOperation operation = filter.getOperation(); + + if (isLogicalFunction(operation)) return getPredicateOfConditions(filter, builder, root, joins); + + Value value = filter.getValues().get(0); + Expression expression = (Expression) getPath(value.getProperty(), root, joins); + + if (operation.isSingleValue()) { + return getSingleValueCondition(operation, value, expression, builder); + } else { + return getListValueCondition(operation, value, expression, builder); + } + } + + private boolean isLogicalFunction(FilterOperation operation) { + return operation == AND || operation == OR; + } + + private Predicate getPredicateOfConditions(Filter filter, CriteriaBuilder builder, Root root, Map joins) { + List filters = new ArrayList<>(); + for (Value v : filter.getValues()) { + filters.add(filter(v.getFilter(), builder, root, joins)); + } + Predicate[] predicates = filters.toArray(new Predicate[]{}); + if (filter.getOperation() == AND) return builder.and(predicates); + else return builder.or(predicates); + } + + @SuppressWarnings("unchecked") + private Predicate getSingleValueCondition(FilterOperation operation, Value rootValue, Expression expression, CriteriaBuilder builder) { + if (operation != FilterOperation.EQ && operation != FilterOperation.NEQ && isNull(rootValue.getValue())) throw new IllegalArgumentException(operation + " requires a single value"); + String valueStr = rootValue.getValue(); + Comparable value = rootValue.getType().convert(rootValue.getValue()); + + Class attributeType = expression.getJavaType(); + if (entityData.isEmbeddedId(attributeType)) value = getComposedId(attributeType, rootValue.getValue()); + + if (attributeType.isEnum()) { + value = getEnum(attributeType, rootValue.getValue()); + } + + if (attributeType.equals(Instant.class) && value instanceof OffsetDateTime) { + value = ((OffsetDateTime) value).toInstant(); + } + + switch (operation) { + case GT: + return builder.greaterThan(expression, value); + case LT: + return builder.lessThan(expression, value); + case GTEQ: + return builder.greaterThanOrEqualTo(expression, value); + case LTEQ: + return builder.lessThanOrEqualTo(expression, value); + case EQ: + return value == null ? builder.isNull(expression) : builder.equal(expression, value); + case NEQ: + return value == null ? builder.isNotNull(expression) : builder.notEqual(expression, value); + case SUBSTRING: + return builder.like((Expression) expression, "%" + valueStr + "%"); + case LIKE: + return builder.like((Expression) expression, valueStr); + case ILIKE: + return builder.like(builder.lower((Expression) expression), valueStr.toLowerCase()); + case NLIKE: + return builder.notLike((Expression) expression, valueStr); + case REGEXP: + Pattern regexPattern = Pattern.compile(valueStr); + Expression patternExpression = builder.literal(regexPattern.pattern()); + return builder.isTrue(builder.function(REGEXP_FUNCTION, Boolean.class, expression, patternExpression)); + default: + throw new IllegalArgumentException("Operation " + operation + " not supported"); + } + } + + @SuppressWarnings("unchecked") + private Predicate getListValueCondition(FilterOperation operation, Value rootValue, Expression expression, CriteriaBuilder builder) { + if (isNull(rootValue.getValues()) || rootValue.getValues().isEmpty()) + throw new IllegalArgumentException(operation + " requires a list of values"); + + List values = rootValue.getType().convert(rootValue.getValues()); + + Class attributeType = expression.getJavaType(); + if (entityData.isEmbeddedId(attributeType)) values = rootValue.getValues().stream().map(v -> getComposedId(attributeType, v)).collect(Collectors.toList()); + + if (attributeType.isEnum()) { + values = rootValue.getValues().stream().map(v -> getEnum(attributeType, v)).collect(Collectors.toList()); + } + + switch (operation) { + case IN: + CriteriaBuilder.In inCb = builder.in(expression); + values.forEach(inCb::value); + return inCb; + case BETWEEN: + if (values.size() != 2) throw new IllegalArgumentException(operation + " requires 2 values"); + return builder.between(expression, values.get(0), values.get(1)); + default: + throw new IllegalArgumentException("Operation " + operation + " not supported"); + } + } + + private Comparable getComposedId(Class clazz, String value) { + try { + Method m = clazz.getMethod("from", String.class); + Comparable composedId = (Comparable) m.invoke(null, (Object) value); + return composedId; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + log.error("Error: ", e); + return null; + } + } + + private Comparable getEnum(Class clazz, String value) { + return Enum.valueOf(clazz, value); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/AttributeInfo.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/AttributeInfo.java new file mode 100644 index 0000000..b60c082 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/AttributeInfo.java @@ -0,0 +1,55 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.executor; + +import java.util.List; +import java.util.Set; + +public class AttributeInfo { + + private Class type; + private Class itemType; + private boolean isBasic; + private boolean isPrimaryKey; + private boolean isEmbedded; + private boolean isEnum; + + AttributeInfo(Class type, Class itemType, boolean isPrimaryKey, boolean isEmbedded) { + this.type = type; + this.itemType = itemType; + isBasic = itemType.equals(type) && type.getCanonicalName().startsWith("java"); + this.isPrimaryKey = isPrimaryKey; + this.isEmbedded = isEmbedded; + this.isEnum = itemType.equals(type) && Enum.class.isAssignableFrom(type); + } + + public boolean isBasic() { + return isBasic; + } + + public boolean isEnum() { + return isEnum; + } + + public boolean isList() { + return List.class.equals(type); + } + + public boolean isSet() { + return Set.class.equals(type); + } + + public boolean isCollection() { + return isList() || isSet(); + } + + public Class getSimpleType() { + return isCollection() ? itemType : type; + } + + public boolean isPrimaryKey() { + return isPrimaryKey; + } + + public boolean isEmbedded() { + return isEmbedded; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityInfo.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityInfo.java new file mode 100644 index 0000000..8e7ab9c --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityInfo.java @@ -0,0 +1,151 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.executor; + +import org.springframework.stereotype.Component; + +import javax.persistence.EntityManager; +import javax.persistence.metamodel.Attribute; +import javax.persistence.metamodel.Bindable; +import javax.persistence.metamodel.EntityType; +import javax.persistence.metamodel.SingularAttribute; +import java.util.*; +import java.util.stream.Collectors; + +@Component +public class EntityInfo { + + private static EntityInfo INSTANCE = null; + + private Map> entityAttributesInfo = new HashMap<>(); + private Map entityId = new HashMap<>(); + private Set embeddedIds = new HashSet<>(); + + private EntityInfo(EntityManager em) { + discoverEntities(em); + } + + public static synchronized EntityInfo getInstance(EntityManager em) { + if (INSTANCE == null) { + INSTANCE = new EntityInfo(em); + } + return INSTANCE; + } + + private void discoverEntities(EntityManager em) { + for (EntityType entityType : em.getMetamodel().getEntities()) { + entityAttributesInfo.put(entityType.getName(), new HashMap<>()); + Map params = entityAttributesInfo.get(entityType.getName()); + for (Attribute attribute : entityType.getAttributes()) { + String name = attribute.getName(); + Class type = attribute.getJavaType(); + Class itemType = null; + boolean isId = false; + boolean isEmbedded = false; + if (attribute instanceof Bindable) { + itemType = ((Bindable) attribute).getBindableJavaType(); + } + if (attribute instanceof SingularAttribute) { + isId = ((SingularAttribute) attribute).isId(); + } + if (isId) { + entityId.put(entityType.getName(), name); + } + if(attribute.getPersistentAttributeType().equals(Attribute.PersistentAttributeType.EMBEDDED)){ + isEmbedded = true; + } + params.put(name, new AttributeInfo(type, itemType, isId, isEmbedded)); + if (isId && isEmbedded) embeddedIds.add(itemType); + } + } + } + + /** + * Given a attribute path and a root class, returns the entity class of the attribute + * + * @param path path of the attribute + * @param clazz class of the root class + * @return entity class of the attribute + */ + public Class getClass(String path, Class clazz) { + if (path == null) return clazz; + String[] pathParts = path.split("\\."); + String className; + for (String pathPart : pathParts) { + className = clazz.getSimpleName(); + clazz = entityAttributesInfo.get(className).get(pathPart).getSimpleType(); + } + return clazz; + } + + public AttributeInfo getAttributeInfo(Object entity, String attributeName) { + return getAttributeInfo(entity.getClass(), attributeName); + } + + public AttributeInfo getAttributeInfo(Class entityClass, String attributeName) { + return getAttributeInfo(entityClass.getSimpleName(), attributeName); + } + + public AttributeInfo getAttributeInfo(String entityName, String attributeName) { + return entityAttributesInfo.get(entityName).get(attributeName); + } + + public List getAttributes(Object entity) { + return getAttributes(entity.getClass()); + } + + public List getAttributes(Class entityClass) { + return getAttributes(entityClass.getSimpleName()); + } + + public List getAttributes(String entityName) { + return new ArrayList<>(entityAttributesInfo.get(entityName).keySet()); + } + + public String getIdAttribute(Class entityClass) { + return getIdAttribute(entityClass.getSimpleName()); + } + + public String getIdAttribute(String entityName) { + String primaryKey = entityId.get(entityName); + if (primaryKey == null) throw new IllegalArgumentException(String.format("Not found primary key of the entity '%s'", entityName)); + return primaryKey; + } + + /** + * Given a root class and an attribute path, returns the name of the entity class of the attribute + * + * @param entityClass class of the root class + * @param path path of the attribute + * @return entity class of the attribute + */ + public String getIdAttribute(Class entityClass, String path) { + entityClass = getClass(path, entityClass); + return getIdAttribute(entityClass); + } + + public List getBasicAttributes(Class entityClass) { + return getAttributes(entityClass).stream() + .filter(attribute -> { + AttributeInfo info = getAttributeInfo(entityClass, attribute); + return info.isBasic() || info.isEmbedded() || info.isEnum(); + }) + .collect(Collectors.toList()); + } + + /** + * Given a root class and an attribute path, returns the basic attributes of the entity class of the attribute + * + * @param entityClass class of the root class + * @param path path of the attribute + * @return list of basic attributes + */ + public List getBasicAttributes(Class entityClass, String path) { + entityClass = getClass(path, entityClass); + return getBasicAttributes(entityClass).stream() + .map(field -> path.concat("." + field)) + .collect(Collectors.toList()); + } + + public boolean isEmbeddedId(Class idClass) { + return embeddedIds.contains(idClass); + } +} \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityLookup.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityLookup.java new file mode 100644 index 0000000..c6a5c5f --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityLookup.java @@ -0,0 +1,26 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.executor; + +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +public class EntityLookup { + + private Map> entitiesByPath = new HashMap<>(); + + public ApigenAbstractPersistable register(ApigenAbstractPersistable entity, String path) { + entitiesByPath.putIfAbsent(path, new HashMap<>()); + entitiesByPath.get(path).putIfAbsent(entity.getId(), entity); + return entitiesByPath.get(path).get(entity.getId()); + } + + public ApigenAbstractPersistable get(ApigenAbstractPersistable entity, String path) { + return get(path, entity.getId()); + } + + public ApigenAbstractPersistable get(String path, Serializable id) { + return entitiesByPath.get(path).get(id); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/TupleMapper.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/TupleMapper.java new file mode 100644 index 0000000..104e7e4 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/TupleMapper.java @@ -0,0 +1,186 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.executor; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.beanutils.PropertyUtils; +import org.springframework.util.StringUtils; + +import javax.persistence.Tuple; +import java.io.Serializable; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@Slf4j +public class TupleMapper { + + private static final String ROOT_PATH = null; + + private Class clazz; + private EntityInfo entityData; + private EntityLookup lookup; + private List groups; + + public TupleMapper(List fields, EntityInfo entityData, Class clazz) { + this.clazz = clazz; + this.entityData = entityData; + this.lookup = new EntityLookup(); + initializeGroups(fields); + } + + private void initializeGroups(List fields) { + List orderedFields = getOrderedFields(fields); + groups = getGroups(orderedFields); + } + + private List getOrderedFields(List fields) { + return IntStream.range(0, fields.size()) + .mapToObj(i -> new OrderedField(fields.get(i), i)) + .sorted(Comparator.comparing(OrderedField::getLevel).thenComparing((OrderedField::compareToField))) + .collect(Collectors.toList()); + } + + private List getGroups(List orderedFields) { + List foundGroups = new ArrayList<>(); + HashMap attributesGroupByPath = new HashMap<>(); + + orderedFields.forEach(f -> { + String fieldName = f.getField(); + boolean isNested = fieldName.contains("."); + String path = isNested ? fieldName.substring(0, fieldName.lastIndexOf('.')) : ROOT_PATH; + if (!attributesGroupByPath.containsKey(path)) { + String primaryKey = this.entityData.getIdAttribute(clazz, path); + EntityAttributesGroup attributesGroup = new EntityAttributesGroup(path, primaryKey, new HashSet<>()); + foundGroups.add(attributesGroup); + attributesGroupByPath.put(path, attributesGroup); + } + if (isNested) { + String field = fieldName.substring(fieldName.lastIndexOf('.') + 1); + attributesGroupByPath.get(path).getSimpleFields().add(new OrderedField(field, f.getIndex())); + } else { + attributesGroupByPath.get(ROOT_PATH).getSimpleFields().add(f); + } + }); + return foundGroups; + } + + @SuppressWarnings("unchecked") + public List map(List result) { + List rootIds = new ArrayList<>(); + for (Tuple tuple : result) { + Map tupleEntities = new HashMap<>(); + for (EntityAttributesGroup group : groups) { + String fieldId = group.getFieldId(); + ApigenAbstractPersistable entity = createInstance(group.path, clazz); + tupleEntities.put(group.path, entity); + for (OrderedField orderedField : group.simpleFields) { + if (isRootPath(group.path) && orderedField.field.equals(fieldId)) { + rootIds.add((Serializable) tuple.get(orderedField.index)); + } + setFieldValue(entity, orderedField.field, tuple.get(orderedField.index)); + } + if (entity.getId() == null) continue; + entity = lookup.register(entity, group.path); + addToParent(entity, group.path, tupleEntities, lookup); + } + } + return (List) rootIds.stream().distinct().map(id -> lookup.get(ROOT_PATH, id)).collect(Collectors.toList()); + } + + /** + * Given a attribute path and a root class, returns an empty instance (all params to null) of the attribute class + * + * @param path path of the attribute + * @param clazz class of the root class + * @return instance of the entity class of the attribute + */ + @SuppressWarnings("unchecked") + private ApigenAbstractPersistable createInstance(String path, Class clazz) { + try { + return (ApigenAbstractPersistable) entityData.getClass(path, clazz).getConstructor().newInstance(); + } catch (Exception e) { + log.error("Attribute '{}' of entity {} can not be instanced", path, clazz, e); + throw new RuntimeException(e); + } + } + + private void addToParent(ApigenAbstractPersistable entity, String path, Map instanced, EntityLookup lookup) { + if (isRootPath(path)) return; + boolean isNested = path.contains("."); + String key = isNested ? path.substring(0, path.lastIndexOf('.')) : null; + String attributeName = isNested ? path.substring(path.lastIndexOf('.') + 1) : path; + ApigenAbstractPersistable parent = instanced.get(key); + ApigenAbstractPersistable trueParent = lookup.get(parent, key); + addIfAbsent(trueParent, attributeName, entity); + } + + private void addIfAbsent(ApigenAbstractPersistable entity, String attribute, Object value) { + try { + Object currentValue = PropertyUtils.getSimpleProperty(entity, attribute); + AttributeInfo attributeInfo = entityData.getAttributeInfo(entity, attribute); + if (attributeInfo.isCollection()) { + addIfAbsentCollection(entity, attribute, value, currentValue, attributeInfo.isSet()); + } else { + addIfAbsentObject(entity, attribute, value, currentValue); + } + } catch (Exception e) { + log.error("Error adding entity to parent", e); + } + } + + @SuppressWarnings("unchecked") + private void addIfAbsentCollection(ApigenAbstractPersistable entity, String attribute, Object value, Object currentValue, boolean isSet) { + Collection collection = (Collection) currentValue; + if (collection == null) { + collection = isSet ? new HashSet() : new ArrayList(); + setFieldValue(entity, attribute, collection); + } + if (!collection.contains(value)) { + collection.add(value); + } + } + + private void addIfAbsentObject(ApigenAbstractPersistable entity, String attribute, Object value, Object currentValue) { + if (currentValue == null) { + setFieldValue(entity, attribute, value); + } + } + + private void setFieldValue(ApigenAbstractPersistable instance, String field, Object value) { + try { + BeanUtils.copyProperty(instance, field, value); + } catch (Exception e) { + log.warn("Attribute '{}' of entity {} can not be set", instance.getClass(), e); + } + } + + private boolean isRootPath(String path) { + return path == null; + } + + @Getter + @AllArgsConstructor + private static class EntityAttributesGroup { + String path; + String fieldId; + Set simpleFields; + } + + @Getter + @AllArgsConstructor + private static class OrderedField { + String field; + int index; + + int compareToField(OrderedField object) { + return field.compareTo(object.field); + } + + int getLevel() { + return StringUtils.countOccurrencesOf(field, "."); + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Filter.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Filter.java new file mode 100644 index 0000000..426d7a0 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Filter.java @@ -0,0 +1,18 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.filter; + +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +public class Filter { + @NotNull + private FilterOperation operation; + @Valid + @NotNull + @NotEmpty + private List values; +} \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/FilterOperation.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/FilterOperation.java new file mode 100644 index 0000000..05f04b8 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/FilterOperation.java @@ -0,0 +1,36 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.filter; + +public enum FilterOperation { + AND("AND", false), + OR("OR", false), + GT("GT", true), + LT("LT", true), + GTEQ("GTEQ", true), + LTEQ("LTEQ", true), + EQ("EQ", true), + NEQ("NEQ", true), + IN("IN", false), + BETWEEN("BETWEEN", false), + SUBSTRING("SUBSTRING", true), + LIKE("LIKE", true), + ILIKE("ILIKE", true), + NLIKE("NLIKE", true), + REGEXP("REGEXP", true); + + private final String operation; + private final boolean singleValue; + + FilterOperation(final String operation, boolean singleValue) { + this.operation = operation; + this.singleValue = singleValue; + } + + public boolean isSingleValue() { + return singleValue; + } + + @Override + public String toString() { + return this.operation; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/PropType.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/PropType.java new file mode 100644 index 0000000..fe5aace --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/PropType.java @@ -0,0 +1,73 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.filter; + +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; + +public enum PropType { + STRING("STRING") { + @Override + public String convert(String value) { + return value; + } + }, + INTEGER("INTEGER") { + @Override + public Long convert(String value) { + if (value == null) return null; + return Long.parseLong(value); + } + }, + FLOAT("FLOAT") { + @Override + public Double convert(String value) { + if (value == null) return null; + return Double.parseDouble(value); + } + }, + DATE("DATE") { + @Override + public LocalDate convert(String value) { + if (value == null) return null; + return LocalDate.parse(value); + } + }, + DATETIME("DATETIME") { + @Override + public OffsetDateTime convert(String value) { + if (value == null) return null; + return OffsetDateTime.parse(value); + } + }, + BOOLEAN("BOOLEAN") { + @Override + public Boolean convert(String value) { + if (value == null) return null; + return Boolean.parseBoolean(value); + } + }; + + private final String value; + + PropType(final String value) { + this.value = value; + } + + @Override + public String toString() { + return this.value; + } + + public abstract T convert(String value); + + public List convert(List values) { + List convertedValues = new ArrayList<>(); + for (String v : values) { + T cast = this.convert(v); + convertedValues.add(cast); + } + return convertedValues; + } + +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Value.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Value.java new file mode 100644 index 0000000..89643e3 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Value.java @@ -0,0 +1,39 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.filter; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.AssertTrue; +import java.util.List; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + +@Data +public class Value { + private String property; + private PropType type; + private String value; + private List values; + @Valid + private Filter filter; + + @JsonIgnore + @AssertTrue + public boolean isValid() { + return isFilter() ^ isExpression(); // ^ -> XOR + } + + private boolean isFilter() { + return nonNull(filter); + } + + private boolean isExpression() { + return nonNull(property) && nonNull(type) && hasOnlyOneValue(); + } + + private boolean hasOnlyOneValue() { + return ( nonNull(value) && isNull(values) ) || ( isNull(value) && nonNull(values) ); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java new file mode 100644 index 0000000..5e6765e --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java @@ -0,0 +1,44 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.functions; + +import ch.qos.logback.core.db.dialect.OracleDialect; +import ch.qos.logback.core.db.dialect.PostgreSQLDialect; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.boot.MetadataBuilder; +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.spi.MetadataBuilderInitializer; +import org.hibernate.dialect.*; +import org.hibernate.dialect.function.SQLFunctionTemplate; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.type.StandardBasicTypes; + +@Slf4j +public class ApigenFunctionsMetadataBuilderInitializer implements MetadataBuilderInitializer { + + public static final String REGEXP_FUNCTION = "apigen_regexp"; + private static final String REGEXP_FUNCTION_STANDARD = "REGEXP_LIKE(?1,?2)"; + private static final String REGEXP_FUNCTION_POSTGRESQL = "?1 ~ ?2"; + + @Override + public void contribute(MetadataBuilder metadataBuilder, StandardServiceRegistry serviceRegistry) { + + final JdbcEnvironment jdbcEnvironment = serviceRegistry.getService(JdbcEnvironment.class); + final Dialect dialect = jdbcEnvironment.getDialect(); + final Class dialectClass = dialect.getClass(); + + if (PostgreSQLDialect.class.isAssignableFrom(dialectClass) || PostgreSQL81Dialect.class.isAssignableFrom(dialectClass)) { + metadataBuilder.applySqlFunction(REGEXP_FUNCTION, new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, REGEXP_FUNCTION_POSTGRESQL)); + log.debug("Apigen functions initialized for PostgreSQL in dialect: {}", dialectClass); + } else if (H2Dialect.class.isAssignableFrom(dialectClass)) { + metadataBuilder.applySqlFunction(REGEXP_FUNCTION, new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, REGEXP_FUNCTION_STANDARD)); + log.debug("Apigen functions initialized for H2 in dialect: {}", dialectClass); + } else if (OracleDialect.class.isAssignableFrom(dialectClass) || Oracle8iDialect.class.isAssignableFrom(dialectClass)) { + metadataBuilder.applySqlFunction(REGEXP_FUNCTION, new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, REGEXP_FUNCTION_STANDARD)); + log.debug("Apigen functions initialized for Oracle in dialect: {}", dialectClass); + } else if (MySQLDialect.class.isAssignableFrom(dialectClass)) { + metadataBuilder.applySqlFunction(REGEXP_FUNCTION, new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN, REGEXP_FUNCTION_STANDARD)); + log.debug("Apigen functions initialized for MySQL in dialect: {}", dialectClass); + } else { + log.error("Dialect {} not supported natively by Apigen, functions {} not defined, consult documentation to extend your own dialect", REGEXP_FUNCTION, dialectClass); + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/pagination/Pagination.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/pagination/Pagination.java new file mode 100644 index 0000000..bebb016 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/pagination/Pagination.java @@ -0,0 +1,15 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.pagination; + +import lombok.Data; + +@Data +public class Pagination { + + private int init = -1; + private int limit = -1; + + public Pagination(int init, int limit) { + this.init = init; + this.limit = limit; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ApigenEntityOutResource.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ApigenEntityOutResource.java new file mode 100644 index 0000000..44b7798 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ApigenEntityOutResource.java @@ -0,0 +1,9 @@ +package net.cloudappi.apigen.archetypecore.core.resource; + +import java.lang.annotation.*; + +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApigenEntityOutResource { +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/FilterResource.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/FilterResource.java new file mode 100644 index 0000000..49cf1a6 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/FilterResource.java @@ -0,0 +1,14 @@ +package net.cloudappi.apigen.archetypecore.core.resource; + +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; + +import javax.validation.Valid; +import javax.validation.constraints.NotNull; + +@Data +public class FilterResource { + @NotNull + @Valid + Filter filter; +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslator.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslator.java new file mode 100644 index 0000000..13d882b --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslator.java @@ -0,0 +1,12 @@ +package net.cloudappi.apigen.archetypecore.core.resource; + +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; + +import java.util.List; + +public interface ResourceNamingTranslator { + void translate(List select, List exclude, List expand, Class resourceClass); + void translate(List select, List exclude, List expand, List orderBy, Class resourceClass); + void translate(List select, List exclude, List expand, Filter filter, List orderBy, Class resourceClass); + void translate(Filter filter, Class resourceClass); +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java new file mode 100644 index 0000000..abafde1 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java @@ -0,0 +1,206 @@ +package net.cloudappi.apigen.archetypecore.core.resource; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.exceptions.InvalidPropertyPath; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.reflections.scanners.TypeAnnotationsScanner; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.util.*; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +@Slf4j +public class ResourceNamingTranslatorByReflection implements ResourceNamingTranslator { + + private Map> attributesByResource = new HashMap<>(); + + public ResourceNamingTranslatorByReflection(ApplicationContext context) { + this(getApplicationPackage(context)); + } + + public ResourceNamingTranslatorByReflection(String... packageNames) { + Set> resources = detectResources(packageNames); + resources.forEach(resource -> analyzeResource(resource, resources)); + } + + private static String getApplicationPackage(ApplicationContext context) { + return context.getBean(context.getBeanNamesForAnnotation(SpringBootApplication.class)[0]) + .getClass().getPackage().getName(); + } + + private Set> detectResources(String... packageNames) { + return new Reflections(packageNames, new TypeAnnotationsScanner(), new SubTypesScanner()) + .getTypesAnnotatedWith(ApigenEntityOutResource.class); + } + + private void analyzeResource(Class resource, Set> resources) { + String name = resource.getCanonicalName(); + for (Field field : resource.getDeclaredFields()) { + analyzeField(name, field, resources); + } + } + + private void analyzeField(String resourceName, Field field, Set> resources) { + if (field.isSynthetic()) return; + String fieldName = field.getName(); + String jsonName = field.getDeclaredAnnotation(JsonProperty.class).value(); + Class fieldType = field.getType(); + + boolean isSingle = true; + if (Collection.class.isAssignableFrom(fieldType)) { + isSingle = false; + ParameterizedType stringListType = (ParameterizedType) field.getGenericType(); + fieldType = (Class) stringListType.getActualTypeArguments()[0]; + } + + String fieldResourceType = null; + if (resources.contains(fieldType)) { + fieldResourceType = fieldType.getCanonicalName(); + } + + attributesByResource.putIfAbsent(resourceName, new HashMap<>()); + attributesByResource.get(resourceName).put(jsonName, new AttributeInfo(fieldName, fieldResourceType, isSingle)); + } + + @Override + public void translate(List select, List exclude, List expand, Class resourceClass) { + translate(select, exclude, expand, null, resourceClass); + } + + @Override + public void translate(List select, List exclude, List expand, List orderBy, Class resourceClass) { + translate(select, exclude, expand, null, orderBy, resourceClass); + } + + @Override + public void translate(List select, List exclude, List expand, Filter filter, List orderBy, Class resourceClass) { + String resourceName = resourceClass.getCanonicalName(); + InvalidPropertyPath errors = new InvalidPropertyPath(); + translateSelect(select, resourceName, errors); + translateExclude(exclude, resourceName, errors); + translateExpand(expand, resourceName, errors); + translateOrderBy(orderBy, resourceName, errors); + translate(filter, resourceClass, errors); + if (errors.errorsDetected()) throw errors; + } + + private void translateSelect(List select, String resourceName, InvalidPropertyPath errors) { + translate(select, field -> translateField(field, resourceName, errors.getInvalidSelectPath())); + } + + private void translateExclude(List exclude, String resourceName, InvalidPropertyPath errors) { + translate(exclude, field -> translateField(field, resourceName, errors.getInvalidExcludePath())); + } + + private String translateField(String field, String resourceName, List errors) { + return translate(field, resourceName, false, false, false, errors, null); + } + + private void translateExpand(List expand, String resourceName, InvalidPropertyPath errors) { + translate(expand, field -> translateExpand(field, resourceName, errors.getInvalidExpandPath())); + } + + private String translateExpand(String field, String resourceName, List errors) { + return translate(field, resourceName, true, false, false, errors, null); + } + + private void translateOrderBy(List orderBy, String resourceName, InvalidPropertyPath errors) { + translate(orderBy, field -> translateOrderBy(field, resourceName, errors.getInvalidOrderByPath(), errors.getInvalidOrderByToManyPath())); + } + + private String translateOrderBy(String field, String resourceName, List errors, List orderByMany) { + return translate(field, resourceName, false, true, true, errors, orderByMany); + } + + private void translate(List fields, UnaryOperator translate) { + if (fields == null) return; + List newFields = fields.stream() + .map(translate) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + fields.clear(); + fields.addAll(newFields); + } + + private String translate(String field, String resourceName, boolean lastIsResource, boolean pathAllSingle, boolean handleOrder, List pathErrors, List orderByManyErrors) { + String[] fieldParts = field.split("\\."); + String order = null; + if (handleOrder) order = cleanOrder(fieldParts); + String[] newFieldParts = new String[fieldParts.length]; + for (int i = 0; i < fieldParts.length; i++) { + Map attributes = attributesByResource.get(resourceName); + if (attributes == null) { + pathErrors.add(field); + return null; + } + AttributeInfo attributeInfo = attributes.get(fieldParts[i]); + if (attributeInfo == null) { + pathErrors.add(field); + return null; + } + if (pathAllSingle && !attributeInfo.isSingle()) { + orderByManyErrors.add(field); + return null; + } + String newFieldPart = attributeInfo.getJavaName(); + resourceName = attributeInfo.getResourceType(); + newFieldParts[i] = newFieldPart; + } + if ((lastIsResource && resourceName == null) || (!lastIsResource && resourceName != null)) { + pathErrors.add(field); + return null; + } + if (handleOrder) newFieldParts[0] = order + newFieldParts[0]; + return String.join(".", newFieldParts); + } + + private String cleanOrder(String[] pathParts) { + if (pathParts[0].charAt(0) == '+' || pathParts[0].charAt(0) == ' ') { + pathParts[0] = pathParts[0].substring(1); + return "+"; + } else if (pathParts[0].charAt(0) == '-') { + pathParts[0] = pathParts[0].substring(1); + return "-"; + } + return "+"; + } + + @Override + public void translate(Filter filter, Class resourceClass) { + InvalidPropertyPath errors = new InvalidPropertyPath(); + translate(filter, resourceClass, errors.getInvalidFilterPath()); + if (errors.errorsDetected()) throw errors; + } + + public void translate(Filter filter, Class resourceClass, InvalidPropertyPath errors) { + translate(filter, resourceClass, errors.getInvalidFilterPath()); + } + + private void translate(Filter filter, Class resourceClass, List errors) { + if (filter == null) return; + String resourceName = resourceClass.getCanonicalName(); + if (filter.getValues() != null) { + filter.getValues().forEach(v -> { + if (v.getProperty() != null) v.setProperty(translateField(v.getProperty(), resourceName, errors)); + translate(v.getFilter(), resourceClass, errors); + }); + } + } + + @Getter + @AllArgsConstructor + private static class AttributeInfo { + private String javaName; + private String resourceType; + private boolean single; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponse.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponse.java new file mode 100644 index 0000000..703176a --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponse.java @@ -0,0 +1,99 @@ +package net.cloudappi.apigen.archetypecore.core.responses; + +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.responses.metadata.Metadata; +import net.cloudappi.apigen.archetypecore.core.responses.metadata.Paging; +import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; +import net.cloudappi.apigen.archetypecore.core.responses.result.ApiResult; +import org.springframework.util.Assert; + +import java.util.List; + +@Data +public class ApiResponse { + + private ApiResult result; + private Metadata metadata; + private T data; + + public ApiResponse() { + this(null); + } + + protected ApiResponse(T data) { + this.data = data; + } + + @SuppressWarnings("unchecked") + public ApiResponse withMetadata(Metadata metadata) { + Assert.isNull(this.metadata, "Metadata already declared"); + this.metadata = metadata; + return this; + } + + @SuppressWarnings("unchecked") + public > R withMetadataPagination(Paging paging) { + initMetadata(); + Assert.isNull(this.metadata.getPaging(), "Paging already declared"); + this.metadata.setPaging(paging); + return (R) this; + } + + @SuppressWarnings("unchecked") + public > R withMetadataPagination(Integer init, Integer limit, Long total) { + initMetadata(); + Assert.isNull(this.metadata.getPaging(), "Paging already declared"); + this.metadata.setPaging(Paging.from(init, limit, total)); + return (R) this; + } + + @SuppressWarnings("unchecked") + public > R withResult(ApiResult result) { + Assert.isNull(this.result, "Result already declared"); + this.result = result; + return (R) this; + } + + @SuppressWarnings("unchecked") + public > R withResultStatus(Boolean status) { + initResult(); + this.result.setStatus(status); + return (R) this; + } + + @SuppressWarnings("unchecked") + public > R withResultHttpCode(Integer httpCode) { + initResult(); + this.result.setHttpCode(httpCode); + return (R) this; + } + + @SuppressWarnings("unchecked") + public > R withResultInfo(String info) { + initResult(); + this.result.setInfo(info); + return (R) this; + } + + @SuppressWarnings("unchecked") + public > R withResultUpdatedElements(Integer updatedElements) { + initResult(); + this.result.setUpdatedElements(updatedElements); + return (R) this; + } + + @SuppressWarnings("unchecked") + public > R withResultErrors(List errors) { + initResult(); + this.result.setErrors(errors); + return (R) this; + } + + private void initMetadata() { + if (this.metadata == null) this.metadata = new Metadata(); + } + + private void initResult() { + if (this.result == null) this.result = new ApiResult(); + } +} \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/content/ApiListResponseContent.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/content/ApiListResponseContent.java new file mode 100644 index 0000000..11f306e --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/content/ApiListResponseContent.java @@ -0,0 +1,19 @@ +package net.cloudappi.apigen.archetypecore.core.responses.content; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +import java.util.List; + +@Data +public class ApiListResponseContent { + @JsonIgnore + protected List content; + + public ApiListResponseContent() { + } + + public ApiListResponseContent(List content) { + this.content = content; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Links.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Links.java new file mode 100644 index 0000000..d55793b --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Links.java @@ -0,0 +1,77 @@ +package net.cloudappi.apigen.archetypecore.core.responses.metadata; + +import lombok.Data; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +@Data +public class Links { + private Link self; + private Link previous; + private Link next; + private Link first; + private Link last; + + @Data + public static class Link { + private String href; + + public Link(String link) { + href = link; + } + } + + public static Links buildLinksMetadata(Integer init, Integer limit, Long total, UriComponentsBuilder baseLink) { + UriComponents uriComponents = baseLink.build(); + String query = getUrl(uriComponents); + Links links = new Links(); + + long nextInit = init + limit; + uriComponents = updateQueryParams(uriComponents, baseLink, String.valueOf(nextInit)); + links.next = new Link(getUrl(uriComponents)); + + if (total != null) { + if (total <= nextInit) { + links.next = null; + } + + long lastInit = total / limit * limit; + if (lastInit >= total) lastInit -= limit; + uriComponents = updateQueryParams(uriComponents, baseLink, String.valueOf(lastInit)); + links.last = new Link(getUrl(uriComponents)); + } + + links.self = new Link(query); + + if (init > 0) { + long previousInit = Math.max(init - limit, 0); + uriComponents = updateQueryParams(uriComponents, baseLink, String.valueOf(previousInit)); + links.previous = new Link(getUrl(uriComponents)); + } + + uriComponents = updateQueryParams(uriComponents, baseLink, String.valueOf(0)); + Link first = new Link(getUrl(uriComponents)); + links.first = first; + + return links; + } + + private static String getUrl(UriComponents uriComponents) { + if(uriComponents.getQuery() == null || uriComponents.getQuery().isEmpty()){ + return uriComponents.getPath(); + }else{ + return uriComponents.getPath() + "?" + uriComponents.getQuery(); + } + } + + private static UriComponents updateQueryParams(UriComponents uriComponents, UriComponentsBuilder baseLink, String value) { + MultiValueMap args = new LinkedMultiValueMap<>(uriComponents.getQueryParams()); + args.remove("$init"); + args.add("$init", value); + return baseLink.replaceQueryParams(args).build(); + } + + +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Metadata.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Metadata.java new file mode 100644 index 0000000..a3ee66a --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Metadata.java @@ -0,0 +1,8 @@ +package net.cloudappi.apigen.archetypecore.core.responses.metadata; + +import lombok.Data; + +@Data +public class Metadata { + private Paging paging; +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Paging.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Paging.java new file mode 100644 index 0000000..2a17524 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Paging.java @@ -0,0 +1,54 @@ +package net.cloudappi.apigen.archetypecore.core.responses.metadata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import org.springframework.web.util.UriComponentsBuilder; + +@Data +public class Paging { + + @JsonProperty("init") + private Integer init; + @JsonProperty("limit") + private Integer limit; + @JsonProperty("total") + private Long total; + + @JsonProperty("total_pages") + private Integer totalPages; + @JsonProperty("num_page") + private Integer numPage; + + @JsonProperty("links") + private Links links; + + public static Paging from(Integer init, Integer limit, Long total) { + + Paging paging = new Paging(); + + if (total != null && init != null && limit != null) { + if (limit <= 0 || total <= 0) { + paging.totalPages = 1; + } + else { + paging.totalPages = (int) Math.ceil((double) total / limit); + } + + paging.total = total; + paging.numPage = init / limit + 1; + } + + if (init != null && limit != null) { + paging.init = init; + paging.limit = limit; + } + + return paging; + } + + public void addLinks(UriComponentsBuilder baseLink) { + if (init != null && limit != null) { + this.links = Links.buildLinksMetadata(init, limit, total, baseLink); + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiError.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiError.java new file mode 100644 index 0000000..3b58b8d --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiError.java @@ -0,0 +1,14 @@ +package net.cloudappi.apigen.archetypecore.core.responses.result; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiError { + private Integer code; + private String message; + private String element; +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiResult.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiResult.java new file mode 100644 index 0000000..ed76dbb --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiResult.java @@ -0,0 +1,28 @@ +package net.cloudappi.apigen.archetypecore.core.responses.result; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ApiResult { + + @JsonProperty("status") + private Boolean status; + + @JsonProperty("http_code") + private Integer httpCode; + + @JsonProperty("info") + private String info; + + @JsonProperty("errors") + private List errors; + + @JsonProperty("trace_id") + private String traceId; + + @JsonProperty("updated_elements") + private Integer updatedElements; +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/CustomApigenException.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/CustomApigenException.java new file mode 100644 index 0000000..aae2143 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/CustomApigenException.java @@ -0,0 +1,59 @@ +package net.cloudappi.apigen.archetypecore.exceptions; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +import java.util.ArrayList; +import java.util.List; + +public class CustomApigenException extends RuntimeException { + + private transient List errors = new ArrayList<>(); + private HttpStatus httpStatus = HttpStatus.BAD_REQUEST; + + public CustomApigenException(String key) { + this.errors.add(new Error(key)); + } + + public CustomApigenException(String key, String element, Object... otherElements) { + this.errors.add(new Error(key, element, otherElements)); + } + + public CustomApigenException(List errors) { + this.errors = errors; + } + + public List getErrors() { + return errors; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public void setHttpStatus(HttpStatus httpStatus) { + this.httpStatus = httpStatus; + } + + @Getter + public static class Error { + private String key; + private String element; + private Object[] otherElements; + + public Error(String key) { + this.key = key; + } + + public Error(String key, String element) { + this.key = key; + this.element = element; + } + + public Error(String key, String element, Object[] otherElements) { + this.key = key; + this.element = element; + this.otherElements = otherElements; + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/EntityNotFoundException.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/EntityNotFoundException.java new file mode 100644 index 0000000..76ebdb1 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/EntityNotFoundException.java @@ -0,0 +1,16 @@ +package net.cloudappi.apigen.archetypecore.exceptions; + +import lombok.Getter; + +import java.io.Serializable; + +@Getter +public class EntityNotFoundException extends RuntimeException { + private Serializable id; + private Class clazz; + + public EntityNotFoundException(Serializable id, Class clazz) { + this.id = id; + this.clazz = clazz; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/InvalidPropertyPath.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/InvalidPropertyPath.java new file mode 100644 index 0000000..bb3e9f1 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/InvalidPropertyPath.java @@ -0,0 +1,25 @@ +package net.cloudappi.apigen.archetypecore.exceptions; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class InvalidPropertyPath extends RuntimeException { + private List invalidSelectPath = new ArrayList<>(); + private List invalidExcludePath = new ArrayList<>(); + private List invalidExpandPath = new ArrayList<>(); + private List invalidOrderByPath = new ArrayList<>(); + private List invalidOrderByToManyPath = new ArrayList<>(); + private List invalidFilterPath = new ArrayList<>(); + + public boolean errorsDetected() { + return !invalidSelectPath.isEmpty() + || !invalidExcludePath.isEmpty() + || !invalidExpandPath.isEmpty() + || !invalidOrderByPath.isEmpty() + || !invalidOrderByToManyPath.isEmpty() + || !invalidFilterPath.isEmpty(); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/NotImplementedException.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/NotImplementedException.java new file mode 100644 index 0000000..ffdeed8 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/NotImplementedException.java @@ -0,0 +1,8 @@ +package net.cloudappi.apigen.archetypecore.exceptions; + +public class NotImplementedException extends RuntimeException { + + public NotImplementedException(String message) { + super(message); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrors.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrors.java new file mode 100644 index 0000000..a40740e --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrors.java @@ -0,0 +1,44 @@ +package net.cloudappi.apigen.archetypecore.exceptions; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.Serializable; +import java.util.*; +import java.util.stream.Collectors; + +public class RelationalErrors implements Serializable { + private Map> errors = new HashMap<>(); + + public void register(Class clazz, Serializable id) { + if (!errors.containsKey(clazz)) { + errors.put(clazz, new HashSet<>()); + } + errors.get(clazz).add(id); + } + + public void merge(RelationalErrors unknownRelations) { + for (Map.Entry> entry : unknownRelations.errors.entrySet()) { + Class clazz = entry.getKey(); + if (!errors.containsKey(clazz)) { + errors.put(clazz, new HashSet<>()); + } + errors.get(clazz).addAll(entry.getValue()); + } + } + + public boolean isEmpty() { + return errors.isEmpty(); + } + + public List getErrors() { + return errors.entrySet().stream().flatMap(e -> e.getValue().stream().map(v -> new Error(e.getKey(), v))).collect(Collectors.toList()); + } + + @Getter + @AllArgsConstructor + public static class Error { + private Class clazz; + private Serializable id; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrorsException.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrorsException.java new file mode 100644 index 0000000..2ff3d10 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrorsException.java @@ -0,0 +1,13 @@ +package net.cloudappi.apigen.archetypecore.exceptions; + +public class RelationalErrorsException extends RuntimeException { + private final RelationalErrors relationalErrors; + + public RelationalErrorsException(RelationalErrors relationalErrors) { + this.relationalErrors = relationalErrors; + } + + public RelationalErrors getRelationalErrors() { + return relationalErrors; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/ApigenContext.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/ApigenContext.java new file mode 100644 index 0000000..6c3c437 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/ApigenContext.java @@ -0,0 +1,29 @@ +package net.cloudappi.apigen.archetypecore.interceptors; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; + +public class ApigenContext { + + private static final String TRACE_ID = "TRACE_ID"; + + protected ApigenContext() { + // Intentional blank + } + + public static Object getRequestAttribute(String name) { + return RequestContextHolder.getRequestAttributes().getAttribute(name, RequestAttributes.SCOPE_REQUEST); + } + + public static void setRequestAttribute(String name, Object value) { + RequestContextHolder.getRequestAttributes().setAttribute(name, value, RequestAttributes.SCOPE_REQUEST); + } + + public static String getTraceId() { + return (String) getRequestAttribute(TRACE_ID); + } + + public static void setTraceId(String traceId) { + setRequestAttribute(TRACE_ID, traceId); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/WebConfig.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/WebConfig.java new file mode 100644 index 0000000..f775b9e --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/WebConfig.java @@ -0,0 +1,59 @@ +package net.cloudappi.apigen.archetypecore.interceptors; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenProperties; +import net.cloudappi.apigen.archetypecore.interceptors.expand.ExpandPathInterceptor; +import net.cloudappi.apigen.archetypecore.interceptors.expand.ExpandAnnotationInterceptor; +import net.cloudappi.apigen.archetypecore.interceptors.trace.TraceIdInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.Collections; +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public class WebConfig implements WebMvcConfigurer { + + @Autowired + ApigenProperties properties; + + @Override + public void configurePathMatch(PathMatchConfigurer configurer) { + configurer.setUseTrailingSlashMatch(false); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + log.debug("Adding interceptors"); + addApigenTraceIdInterceptor(registry); + addApigenApiInterceptors(registry); + + } + + private void addApigenTraceIdInterceptor(InterceptorRegistry registry) { + registry.addInterceptor(new TraceIdInterceptor(properties.getTraceHeader())); + } + + private void addApigenApiInterceptors(InterceptorRegistry registry) { + ApigenProperties.Api api = properties.getApi(); + List expandPaths = new ArrayList<>(); + api.getPaths().forEach((path, config) -> { + log.debug("Adding path '{}' interceptors", path); + ApigenProperties.Api.PathConfig.ExpandConfig expandConfig = config.getExpand(); + if (expandConfig == null) return; + log.debug("Adding path '{}' expand interceptor {}", path, expandConfig); + int expandLevel = expandConfig.getLevel() == null ? api.getExpand().getLevel() : expandConfig.getLevel(); + ExpandPathInterceptor interceptor = new ExpandPathInterceptor(expandLevel, expandConfig.getAllowed(), expandConfig.getExcluded()); + registry.addInterceptor(interceptor).addPathPatterns(path); + expandPaths.add(path); + }); + log.debug("Adding generic expand interceptor {} excluding paths {}", api.getExpand(), expandPaths); + ExpandPathInterceptor interceptor = new ExpandPathInterceptor(api.getExpand().getLevel(), Collections.emptySet(), Collections.emptySet()); + registry.addInterceptor(interceptor).excludePathPatterns(expandPaths); + ExpandAnnotationInterceptor annotatedExpandInterceptor = new ExpandAnnotationInterceptor(api.getExpand().getLevel()); + registry.addInterceptor(annotatedExpandInterceptor); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ApigenExpand.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ApigenExpand.java new file mode 100644 index 0000000..9befd76 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ApigenExpand.java @@ -0,0 +1,15 @@ +package net.cloudappi.apigen.archetypecore.interceptors.expand; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ApigenExpand { + public String[] allowed() default {}; + public String[] excluded() default {}; + public int maxLevel() default -1; + +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java new file mode 100644 index 0000000..e530a8c --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java @@ -0,0 +1,46 @@ +package net.cloudappi.apigen.archetypecore.interceptors.expand; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.method.HandlerMethod; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Slf4j +public class ExpandAnnotationInterceptor extends ExpandInterceptor { + + private final int defaultMaxLevel; + + public ExpandAnnotationInterceptor(Integer defaultMaxLevel) { + this.defaultMaxLevel = defaultMaxLevel; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + + if (!(handler instanceof HandlerMethod)) return true; + + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + ApigenExpand expandAnnotation = method.getAnnotation(ApigenExpand.class); + + if (expandAnnotation == null) return true; + + Set allowed = toSet(expandAnnotation.allowed()); + Set excluded = toSet(expandAnnotation.excluded()); + int maxLevel = expandAnnotation.maxLevel(); + if (maxLevel < 0) maxLevel = defaultMaxLevel; + + validate(request, allowed, excluded, maxLevel); + + return true; + } + + private Set toSet(String[] values) { + return Stream.of(values).collect(Collectors.toSet()); + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java new file mode 100644 index 0000000..0d60e57 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java @@ -0,0 +1,84 @@ +package net.cloudappi.apigen.archetypecore.interceptors.expand; + +import net.cloudappi.apigen.archetypecore.exceptions.InvalidPropertyPath; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Objects.nonNull; + +public class ExpandInterceptor extends HandlerInterceptorAdapter { + + protected void validate(HttpServletRequest request, Set allowed, Set excluded, int maxLevel) { + Set expands = getExpands(request); + int level = getLevel(expands); + if (hasValues(excluded)) { + if (containsExcludedValue(expands, excluded)) { + throwException(getExcludedValues(expands, excluded)); + } + if (level > maxLevel) { + throwException(getIllegalLevelValues(expands, maxLevel)); + } + } else if (hasValues(allowed)) { + if (containsNotAllowedValue(expands, allowed)) { + throwException(getNotAllowedValues(expands, allowed)); + } + } else { + if (level > maxLevel) { + throwException(getIllegalLevelValues(expands, maxLevel)); + } + } + } + + private boolean hasValues(Set values) { + return nonNull(values) && !values.isEmpty(); + } + + private boolean containsExcludedValue(Set values, Set excluded) { + return !Collections.disjoint(excluded, values); + } + + private boolean containsNotAllowedValue(Set values, Set allowed) { + return !allowed.containsAll(values); + } + + private List getIllegalLevelValues(Set values, int maxLevel) { + return values.stream().filter(value -> getLevel(value) > maxLevel).collect(Collectors.toList()); + } + + private Set getNotAllowedValues(Set values, Set allowed) { + values.removeAll(allowed); + return values; + } + + private Set getExcludedValues(Set values, Set excluded) { + values.retainAll(excluded); + return values; + } + + private int getLevel(Set expands) { + return expands.stream().map(this::getLevel).mapToInt(i -> i).max().orElse(0); + } + + private int getLevel(String expand) { + return StringUtils.countOccurrencesOf(expand, ".") + 1; + } + + private Set getExpands(HttpServletRequest request) { + Set values = new HashSet<>(); + String[] queryValues = request.getParameterValues("$expand"); + if (queryValues == null) return values; + Stream.of(queryValues).flatMap(queryValue -> Stream.of(queryValue.split(","))).forEach(values::add); + return values; + } + + private void throwException(Collection values) { + InvalidPropertyPath errors = new InvalidPropertyPath(); + errors.getInvalidExpandPath().addAll(values); + throw errors; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java new file mode 100644 index 0000000..e54e1c4 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java @@ -0,0 +1,39 @@ +package net.cloudappi.apigen.archetypecore.interceptors.expand; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.method.HandlerMethod; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; +import java.util.Set; + +@Slf4j +public class ExpandPathInterceptor extends ExpandInterceptor { + + private int maxLevel; + private Set allowed; + private Set excluded; + + public ExpandPathInterceptor(Integer maxLevel, Set allowed, Set excluded) { + this.maxLevel = maxLevel; + this.allowed = allowed; + this.excluded = excluded; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + + if (handler instanceof HandlerMethod) { + + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + ApigenExpand expandAnnotation = method.getAnnotation(ApigenExpand.class); + + if (expandAnnotation != null) return true; + } + + validate(request, allowed, excluded, maxLevel); + return true; + } +} \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java new file mode 100644 index 0000000..c23d155 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java @@ -0,0 +1,39 @@ +package net.cloudappi.apigen.archetypecore.interceptors.response; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenProperties; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +@ControllerAdvice +@Slf4j +public class ApiResponseBodyAdvice extends ApiResponseEnhancer implements ResponseBodyAdvice { + + private final String traceHeader; + + public ApiResponseBodyAdvice(ApigenProperties apigenProperties) { + this.traceHeader = apigenProperties.getTraceHeader(); + } + + @Override + public boolean supports(MethodParameter methodParameter, Class> aClass) { + return ApiResponse.class.isAssignableFrom(methodParameter.getParameterType()) + || ResponseEntity.class.isAssignableFrom(methodParameter.getParameterType()); + } + + @Override + public Object beforeBodyWrite(Object maybeApiResponse, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { + if (ApiResponse.class.isAssignableFrom(maybeApiResponse.getClass())) { + ApiResponse apiResponse = (ApiResponse) maybeApiResponse; + return enhance(apiResponse, serverHttpRequest, serverHttpResponse, traceHeader); + } + return maybeApiResponse; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java new file mode 100644 index 0000000..65a1034 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java @@ -0,0 +1,69 @@ +package net.cloudappi.apigen.archetypecore.interceptors.response; + +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.archetypecore.core.responses.result.ApiResult; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.web.util.UriComponentsBuilder; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static java.util.Objects.isNull; + +public abstract class ApiResponseEnhancer { + + public ApiResponse enhance(ApiResponse apiResponse, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, String traceHeader) { + if (apiResponse == null) { + return null; + } + + HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest(); + HttpServletResponse httpServletResponse = ((ServletServerHttpResponse) serverHttpResponse).getServletResponse(); + + completeResponseResult(apiResponse, httpServletRequest, httpServletResponse, traceHeader); + completeResponseMetadata(apiResponse, httpServletRequest); + return apiResponse; + } + + private void completeResponseResult(ApiResponse apiResponse, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String traceHeader) { + ApiResult result = apiResponse.getResult(); + if (result == null) { + result = new ApiResult(); + } + + int status = httpServletResponse.getStatus(); + boolean success = HttpStatus.valueOf(status).is2xxSuccessful() || HttpStatus.valueOf(status).is3xxRedirection(); + + if (isNull(result.getStatus())) result.setStatus(success); + if (isNull(result.getHttpCode())) result.setHttpCode(status); + if (isNull(result.getInfo())) result.setInfo(success ? "OK" : "ERROR"); + if (isNull(result.getTraceId())) result.setTraceId(httpServletRequest.getHeader(traceHeader)); + + apiResponse.setResult(result); + } + + private void completeResponseMetadata(ApiResponse apiResponse, HttpServletRequest httpServletRequest) { + if (apiResponse.getMetadata() == null || apiResponse.getMetadata().getPaging() == null) { + return; + } + String path = getCorrectPath(httpServletRequest); + UriComponentsBuilder url = UriComponentsBuilder.newInstance() + .scheme(httpServletRequest.getScheme()) + .host(httpServletRequest.getServerName()) + .path(path) + .query(httpServletRequest.getQueryString()); + apiResponse.getMetadata().getPaging().addLinks(url); + } + + private String getCorrectPath(HttpServletRequest httpServletRequest) { + if(httpServletRequest.getPathInfo() != null) { + return httpServletRequest.getPathInfo(); + } else { + return httpServletRequest.getServletPath(); + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java new file mode 100644 index 0000000..d76554f --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java @@ -0,0 +1,26 @@ +package net.cloudappi.apigen.archetypecore.interceptors.trace; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.interceptors.ApigenContext; +import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Slf4j +public class TraceIdInterceptor extends HandlerInterceptorAdapter { + + private final String traceHeader; + + public TraceIdInterceptor(String traceHeader) { + this.traceHeader = traceHeader; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String traceId = request.getHeader(traceHeader); + log.debug("Intercepted [{} {}] with traceId={}", request.getMethod(), request.getRequestURI(), traceId); + if (traceId != null) ApigenContext.setTraceId(traceId); + return true; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java new file mode 100644 index 0000000..ebec606 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java @@ -0,0 +1,28 @@ +package net.cloudappi.apigen.archetypecore.interceptors.update; + +import org.springframework.stereotype.Component; +import org.springframework.web.filter.GenericFilterBean; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@Component +public class CachingRequestBodyFilter extends GenericFilterBean { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest currentRequest = (HttpServletRequest) servletRequest; + if (currentRequest.getMethod().equals("PUT")) { + ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(currentRequest); + chain.doFilter(wrappedRequest, servletResponse); + } else { + chain.doFilter(servletRequest, servletResponse); + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java new file mode 100644 index 0000000..fb8e421 --- /dev/null +++ b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java @@ -0,0 +1,69 @@ +package net.cloudappi.apigen.archetypecore.interceptors.update; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import net.cloudappi.apigen.archetypecore.interceptors.ApigenContext; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +@ControllerAdvice +public class UpdateRequestBodyAdvice extends RequestBodyAdviceAdapter { + + private final ObjectMapper objectMapper; + private final Map> mappings = new HashMap<>(); + + public UpdateRequestBodyAdvice(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public boolean supports(MethodParameter methodParameter, Type type, Class> aClass) { + return true; + } + + @SneakyThrows + @Override + public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class> converterType) { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + if (request instanceof ContentCachingRequestWrapper) { + byte[] content = ((ContentCachingRequestWrapper) request).getContentAsByteArray(); + Set jsonFields = objectMapper.readValue(content, HashMap.class).keySet(); + Map mapping = getMapping(body.getClass()); + Set fields = jsonFields.stream().map(f -> mapping.getOrDefault(f, f)).collect(Collectors.toSet()); + ApigenContext.setRequestAttribute("updatedFields", fields); + } + return body; + } + + private Map getMapping(Class clazz) { + if (!mappings.containsKey(clazz)) { + Map mapping = new HashMap<>(); + for (Field field : clazz.getDeclaredFields()) { + String name = field.getName(); + if (field.isAnnotationPresent(JsonProperty.class)) { + String jsonName = field.getDeclaredAnnotation(JsonProperty.class).value(); + mapping.put(jsonName, name); + } else { + mapping.put(name, name); + } + } + mappings.put(clazz, mapping); + } + return mappings.get(clazz); + } +} diff --git a/archetype-core/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer b/archetype-core/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer new file mode 100644 index 0000000..b1653a2 --- /dev/null +++ b/archetype-core/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer @@ -0,0 +1 @@ +net.cloudappi.apigen.archetypecore.core.persistence.functions.ApigenFunctionsMetadataBuilderInitializer \ No newline at end of file diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/FakeApplication.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/FakeApplication.java new file mode 100644 index 0000000..d1193dd --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/FakeApplication.java @@ -0,0 +1,14 @@ +package net.cloudappi.apigen.archetypecore; + +import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenApplication; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@ApigenApplication +@SpringBootApplication +public class FakeApplication { + + public static void main(String[] args) { + SpringApplication.run(FakeApplication.class, args); + } +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudServiceTest.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudServiceTest.java new file mode 100644 index 0000000..cd37aaf --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudServiceTest.java @@ -0,0 +1,182 @@ +package net.cloudappi.apigen.archetypecore.core; + +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; +import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.AdditionalAnswers.returnsFirstArg; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +class AbstractCrudServiceTest { + + private EntityRepository repository; + private RelationsManager relationsManager; + private Mapper mapper; + private CrudService service; + + @BeforeEach + void beforeEach() { + repository = mock(EntityRepository.class); + relationsManager = mock(RelationsManager.class); + mapper = mock(Mapper.class); + service = new CrudService(repository, relationsManager, mapper); + } + + @Test + void givenNonPersistedEntity_whenSave_thenSuccess() { + Entity entity = new Entity(); + when(repository.save(any(Entity.class))).then(returnsFirstArg()); + Entity result = service.create(entity); + verify(relationsManager).createOrRetrieveRelations(any(Entity.class)); + assertSame(entity, result); + } + + @Test + void givenNonPersistedEntitySet_whenSave_thenSuccess() { + Set set = new HashSet<>(Arrays.asList(new Entity(), new Entity())); + when(repository.save(any(Entity.class))).then(returnsFirstArg()); + Set result = service.create(set); + verify(relationsManager, times(2)).createOrRetrieveRelations(any(Entity.class)); + assertEquals(set, result); + } + + @Test + void givenNonPersistedEntityList_whenSave_thenSuccess() { + List list = new ArrayList<>(Arrays.asList(new Entity(), new Entity())); + when(repository.save(any(Entity.class))).then(returnsFirstArg()); + List result = service.create(list); + verify(relationsManager, times(2)).createOrRetrieveRelations(any(Entity.class)); + assertEquals(list, result); + } + + @Test + void givenPersistedEntity_whenUpdateById_thenSuccess() { + Entity entity = new Entity(1L); + when(repository.findById(1L)).thenReturn(Optional.of(entity)); + when(repository.save(any(Entity.class))).then(returnsFirstArg()); + Entity result = service.update(1L, entity); + verify(relationsManager).updateRelations(any(Entity.class), any(Entity.class), any()); + verify(mapper).updateBasicData(any(Entity.class), any(Entity.class)); + verify(repository).save(entity); + assertEquals(entity, result); + } + + @Test + void givenPersistedEntities_whenUpdateList_thenSuccess() { + Entity one = new Entity(1L); + Entity two = new Entity(2L); + when(repository.findById(1L)).thenReturn(Optional.of(one)); + when(repository.findById(2L)).thenReturn(Optional.of(two)); + List list = Arrays.asList(one, two); + List result = service.update(list, list); + verify(relationsManager, times(2)).updateRelations(any(Entity.class), any(Entity.class), any()); + verify(mapper, times(2)).updateBasicData(any(Entity.class), any(Entity.class)); + verify(repository).save(one); + verify(repository).save(two); + assertEquals(list, result); + } + + @Test + void givenPersistedEntities_whenUpdateSet_thenSuccess() { + Entity one = new Entity(1L); + Entity two = new Entity(2L); + when(repository.findById(1L)).thenReturn(Optional.of(one)); + when(repository.findById(2L)).thenReturn(Optional.of(two)); + Set set = new HashSet<>(Arrays.asList(one, two)); + Set result = service.update(set, set); + verify(relationsManager, times(2)).updateRelations(any(Entity.class), any(Entity.class), any()); + verify(mapper, times(2)).updateBasicData(any(Entity.class), any(Entity.class)); + verify(repository).save(one); + verify(repository).save(two); + assertEquals(set, result); + } + + @Test + void givenPersistedEntity_whenDeleteById_thenSuccess() { + Entity entity = new Entity(1L); + when(repository.findById(1L)).thenReturn(Optional.of(entity)); + service.delete(1L); + verify(repository).delete(entity); + } + + @Test + void givenNonPersistedEntity_whenDeleteById_thenError() { + when(repository.findById(1L)).thenReturn(Optional.empty()); + assertThrows(EntityNotFoundException.class, () -> service.delete(1L)); + } + + @Test + void givenPersistedEntity_whenDelete_thenSuccess() { + Entity entity = new Entity(1L); + service.delete(entity); + verify(repository).delete(entity); + } + + @Test + void givenPersistedEntities_whenDeleteCollection_thenSuccess() { + Entity one = new Entity(1L); + Entity two = new Entity(2L); + Collection collection = Arrays.asList(one, two); + service.delete(collection); + verify(repository).delete(one); + verify(repository).delete(two); + } + + private static class Entity extends ApigenAbstractPersistable { + + public Entity() { + } + + public Entity(Long id) { + this.id = id; + } + + Long id; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + @Override + public boolean isReference() { + return getId() != null; + } + } + + private interface EntityRepository extends ApigenRepository { + } + + private abstract class RelationsManager extends AbstractRelationsManager { + + } + + private abstract class Mapper implements ApigenMapper { + + } + + private static class CrudService extends AbstractCrudService { + public CrudService(EntityRepository repository, AbstractRelationsManager relationsManager, ApigenMapper mapper) { + super(repository, relationsManager, mapper); + } + + @Override + protected void updateBasicDataPartially(Entity persistedEntity, Entity entity, Set fields) { + if (fields == null) mapper.updateBasicData(entity, persistedEntity); + else { + if (fields.contains("id")) persistedEntity.setId(entity.getId()); + } + } + } +} \ No newline at end of file diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractReadServiceTest.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractReadServiceTest.java new file mode 100644 index 0000000..bf41d8a --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractReadServiceTest.java @@ -0,0 +1,162 @@ +package net.cloudappi.apigen.archetypecore.core; + +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearch; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; + +class AbstractReadServiceTest { + + private static final List SELECT = new ArrayList<>(); + private static final List EXCLUDE = new ArrayList<>(); + private static final List EXPAND = new ArrayList<>(); + private static final Filter FILTER = new Filter(); + private static final List ORDER_BY = new ArrayList<>(); + private static final Integer INIT = 10; + private static final Integer LIMIT = 20; + private static final Boolean TOTAL = true; + + private EntityRepository repository; + private ReadService service; + + @BeforeEach + void beforeEach() { + repository = mock(EntityRepository.class); + service = new ReadService(repository); + } + + @Test + void givenMaybePersistedEntity_whenGetOneOptional_thenSuccess() { + Optional optional = Optional.empty(); + when(repository.findById(anyLong())).thenReturn(optional); + Optional result = service.getOne(1L); + assertSame(optional, result); + } + + @Test + void givenPersistedEntities_whenGetAll_thenSuccess() { + List entities = new ArrayList<>(); + when(repository.findAll()).thenReturn(entities); + List result = service.getAll(); + assertSame(entities, result); + } + + @Test + void givenPersistedEntities_whenGetAllPaged_thenSuccess() { + PageImpl page = new PageImpl<>(new ArrayList<>()); + when(repository.findAll(any(Pageable.class))).thenReturn(page); + Page result = service.getAll(mock(Pageable.class)); + assertSame(page, result); + } + + @Test + void givenPersistedEntities_whenGetAllSorted_thenSuccess() { + List entities = new ArrayList<>(); + when(repository.findAll(any(Sort.class))).thenReturn(entities); + List result = service.getAll(mock(Sort.class)); + assertSame(entities, result); + } + + @Test + void givenPersistedEntities_whenGetAllById_thenSuccess() { + List entities = new ArrayList<>(); + when(repository.findAllById(any(Iterable.class))).thenReturn(entities); + List result = service.getAll(new ArrayList<>()); + assertSame(entities, result); + } + + @Test + void givenPersistedEntities_whenSearchAll_thenSuccess() { + ApigenSearchResult searchResult = mock(ApigenSearchResult.class); + when(repository.search(any(ApigenSearch.class))).thenReturn(searchResult); + ArgumentCaptor captor = ArgumentCaptor.forClass(ApigenSearch.class); + ApigenSearchResult result = service.search(SELECT, EXCLUDE, EXPAND, FILTER, ORDER_BY, INIT, LIMIT, TOTAL); + verify(repository).search(captor.capture()); + assertSame(searchResult, result); + assertApigenSearch(SELECT, EXCLUDE, EXPAND, FILTER, ORDER_BY, INIT, LIMIT, TOTAL, captor.getValue()); + } + + @Test + void givenPersistedEntity_whenSearchOne_thenSuccess() { + Entity entity = new Entity(1L); + when(repository.searchById(anyLong(), any(ApigenSearch.class))).thenReturn(Optional.of(entity)); + ArgumentCaptor captor = ArgumentCaptor.forClass(ApigenSearch.class); + Entity result = service.search(1L, SELECT, EXCLUDE, EXPAND); + verify(repository).searchById(anyLong(), captor.capture()); + assertSame(entity, result); + assertApigenSearch(SELECT, EXCLUDE, EXPAND, null, null, null, null, null, captor.getValue()); + } + + @Test + void givenPersistedEntity_whenSearchOne_thenException() { + when(repository.searchById(anyLong(), any(ApigenSearch.class))).thenReturn(Optional.empty()); + assertThrows(EntityNotFoundException.class, () -> service.search(1L, SELECT, EXCLUDE, EXPAND)); + } + + private void assertApigenSearch(List select, List exclude, List expand, Filter filter, List orderBy, Integer init, Integer limit, Boolean total, ApigenSearch search) { + assertSame(select, search.getSelect()); + assertSame(exclude, search.getExclude()); + assertSame(expand, search.getExpand()); + assertSame(filter, search.getFilter()); + assertSame(orderBy, search.getOrderBy()); + if (init != null && limit != null) { + assertSame(init, search.getPagination().getInit()); + assertSame(limit, search.getPagination().getLimit()); + } + if (total != null) { + assertSame(total, search.getTotal()); + } + } + + private static class Entity extends ApigenAbstractPersistable { + + public Entity(Long id) { + this.id = id; + } + + Long id; + + @Override + public Long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + @Override + public boolean isReference() { + return getId() != null; + } + } + + private interface EntityRepository extends ApigenRepository { + } + + private static class ReadService extends AbstractReadService { + public ReadService(EntityRepository repository) { + super(repository); + } + } +} \ No newline at end of file diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java new file mode 100644 index 0000000..18d11da --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java @@ -0,0 +1,759 @@ +package net.cloudappi.apigen.archetypecore.core.advice; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; + +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest +@AutoConfigureMockMvc +class ApigenControllerAdviceTests { + + private static final LocalDate NOW = LocalDate.now(); + private static final String TODAY = NOW.toString(); + private static final String YESTERDAY = NOW.minusDays(1L).toString(); + private static final String TOMORROW = NOW.plusDays(1L).toString(); + + @Autowired + private MockMvc mvc; + + @Test + void givenEndpointWithPathVariable_whenPathVariableHasWrongType_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/path-variable/error") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1109))) + .andExpect(jsonPath("$.result.errors[0].message", is("Error parsing path variable 'id'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("id"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenMissingRequestParameter_whenInitDoesExist_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/missing-request-parameter?$limit=10") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1110))) + .andExpect(jsonPath("$.result.errors[0].message", is("Query parameter '$init' is required"))) + .andExpect(jsonPath("$.result.errors[0].element", is("$init"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenConstrainViolation_whenValueInitIsIncorrect_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/constrain-violation?$init=-1&$limit=10") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1003))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property '$init' must have a value greater or equal to '0'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("$init"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationNotNull_whenValueIsNotNull_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-not-null") + .contentType(MediaType.APPLICATION_JSON) + .content("{}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1000))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'name_property' must be not null"))) + .andExpect(jsonPath("$.result.errors[0].element", is("name_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationEmail_whenValueIsErrorNotEmail_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-email") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"email_property\": \"aaa\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1009))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'email_property' must be an email"))) + .andExpect(jsonPath("$.result.errors[0].element", is("email_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationNotEmpty_whenValueIsNotEmpty_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-not-empty") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"name_property\": \"\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1007))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'name_property' must not be empty"))) + .andExpect(jsonPath("$.result.errors[0].element", is("name_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationNotBlank_whenValueIsNotBlank_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-not-blank") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"name_property\": \" \"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1008))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'name_property' must not be blank"))) + .andExpect(jsonPath("$.result.errors[0].element", is("name_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationPositive_whenValueIsNotPositive_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-positive") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 0}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1002))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value greater than '0'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationPositiveOrZero_whenValueIsNotPositiveOrZero_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-positive-or-zero") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": -1}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1003))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value greater or equal to '0'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void giveValidationNegative_whenValueIsNotNegative_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-negative") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 0}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1004))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value less than '0'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationNegativeOrZero_whenValueIsNotNegativeOrZero_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-negative-or-zero") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 1}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1005))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value less or equal to '0'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationPast_whenValueIsNotPast_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-past") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"date_property\": \""+ TODAY +"\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1010))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'date_property' must be a past date"))) + .andExpect(jsonPath("$.result.errors[0].element", is("date_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationPastOrPresent_whenValueIsNotPastOrPresent_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-past-or-present") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"date_property\": \""+ TOMORROW +"\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1011))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'date_property' must be a past or present date"))) + .andExpect(jsonPath("$.result.errors[0].element", is("date_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationFuture_whenValueIsNotFuture_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-future") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"date_property\": \""+ TODAY +"\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1012))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'date_property' must be a future"))) + .andExpect(jsonPath("$.result.errors[0].element", is("date_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationFutureOrPresent_whenValueIsNotFutureOrPresent_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-future-or-present") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"date_property\": \""+ YESTERDAY +"\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1013))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'date_property' must be a future or present"))) + .andExpect(jsonPath("$.result.errors[0].element", is("date_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationMin_whenValueIsNotMin_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-min") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 9}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1003))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value greater or equal to '10'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationDecimalMinWithFalse_whenValueIsNotDecimalMin_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-decimal-min-with-false") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 10.25}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1002))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value greater than '10.25'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationDecimalMinWithTrue_whenValueIsNotDecimalMin_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-decimal-min-with-true") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 10.24}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1003))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value greater or equal to '10.25'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationMax_whenValueIsNotMax_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-max") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 21}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1005))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value less or equal to '20'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationDecimalMaxWithFalse_whenValueIsNotDecimalMax_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-decimal-max-with-false") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 20.25}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1004))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value less than '20.25'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationDecimalMaxWithTrue_whenValueIsNotDecimalMax_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-decimal-max-with-true") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 20.26}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1005))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must have a value less or equal to '20.25'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationSize_whenValueIsNotMinimumSize_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-size") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"name_property\": \"pepe\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1001))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'name_property' must have a length between '5' and '10'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("name_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationSize_whenValueIsNotMaximumSize_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-size") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"name_property\": \"Jose Francisco\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1001))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'name_property' must have a length between '5' and '10'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("name_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationDigits_whenValueIsSuperiorIntegerDigits_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-digits") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 10.99}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1014))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must be a decimal with '1' integer digits and '2' decimal digits"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationDigits_whenValueIsSuperiorDecimalDigits_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-digits") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"number_property\": 1.989}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1014))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'number_property' must be a decimal with '1' integer digits and '2' decimal digits"))) + .andExpect(jsonPath("$.result.errors[0].element", is("number_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationDigits_whenValueIsNotPattern_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-pattern") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"name_property\": \"pepe\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1006))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'name_property' must follow the regex '[A-Z]+'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("name_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationNotSupported_whenValueIsFalse_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"isName_property\": false}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1099))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'isName_property' must be valid"))) + .andExpect(jsonPath("$.result.errors[0].element", is("isName_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenEntityNotFound_whenEntityNotFound_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/entity-not-found") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1106))) + .andExpect(jsonPath("$.result.errors[0].message", is("Element with id 'id' of type 'String' not found"))) + .andExpect(jsonPath("$.result.errors[0].element", is("id"))) + .andReturn().getResponse(); + + // then + assertEquals(404, response.getStatus()); + } + + @Test + void givenEntityNotFound_whenRelatedEntityNotFound_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/related-entity-not-found") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1107))) + .andExpect(jsonPath("$.result.errors[0].message", is("Related element with id 'null' of type 'String' not found"))) + .andExpect(jsonPath("$.result.errors[0].element", is("null"))) + .andExpect(jsonPath("$.result.errors[1].code", is(1107))) + .andExpect(jsonPath("$.result.errors[1].message", is("Related element with id '1' of type 'String' not found"))) + .andExpect(jsonPath("$.result.errors[1].element", is("1"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenMethodNotSupported_whenGetNotSupported_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/method-not-supported") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1200))) + .andExpect(jsonPath("$.result.errors[0].message", is("Method 'GET' not implemented"))) + .andExpect(jsonPath("$.result.errors[0].element", is("GET"))) + .andReturn().getResponse(); + + // then + assertEquals(501, response.getStatus()); + } + + @Test + void givenHandlerNotFound_whenPathNotFound_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/not-defined-path") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1201))) + .andExpect(jsonPath("$.result.errors[0].message", is("Path '/not-defined-path' not implemented"))) + .andExpect(jsonPath("$.result.errors[0].element", is("/not-defined-path"))) + .andReturn().getResponse(); + + // then + assertEquals(404, response.getStatus()); + } + + @Test + void givenHandler_whenMethodNotImplemented_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/method-not-implemented") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1200))) + .andExpect(jsonPath("$.result.errors[0].message", is("Method 'GET /method-not-implemented' not implemented"))) + .andExpect(jsonPath("$.result.errors[0].element", is("GET /method-not-implemented"))) + .andReturn().getResponse(); + + // then + assertEquals(501, response.getStatus()); + } + + @Test + void givenException_whenNotKnowException_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/exception") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1300))) + .andExpect(jsonPath("$.result.errors[0].message", is("Unexpected error"))) + .andExpect(jsonPath("$.result.errors[0].element").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(500, response.getStatus()); + } + + @Test + void givenMediaType_whenBodyIsNotCorrect_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/media-type") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1203))) + .andExpect(jsonPath("$.result.errors[0].message", is("Format not supported, supported formats: '[text/xml]'"))) + .andExpect(jsonPath("$.result.errors[0].element").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenCustomApigenException_whenCustomApigenException_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/custom-apigen-exception") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(9999))) + .andExpect(jsonPath("$.result.errors[0].message", is("Parameter not found 'color'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("color"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenIncorrectEnum_whenBody_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/message-not-readable") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"levelTemperature_property\": \"MEDIUM\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1204))) + .andExpect(jsonPath("$.result.errors[0].message", is("Unsupported value 'MEDIUM', accepted values: 'HIGH, LOW'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("MEDIUM"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenEmptyBody_whenBodyIsNull_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-email") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1202))) + .andExpect(jsonPath("$.result.errors[0].message", is("Request body required"))) + .andExpect(jsonPath("$.result.errors[0].element").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenValidationDate_whenDateIsIncorrect_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-date") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"date_property\": \"ErrorDate\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1108))) + .andExpect(jsonPath("$.result.errors[0].message", is("Error parsing ISO date 'ErrorDate'"))) + .andExpect(jsonPath("$.result.errors[0].element", is("ErrorDate"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenTransalatorsError_whenTranslatorDoError_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/translator-errors") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1101))) + .andExpect(jsonPath("$.result.errors[0].message", is("Invalid property 'selected' in $select"))) + .andExpect(jsonPath("$.result.errors[0].element", is("selected"))) + .andExpect(jsonPath("$.result.errors[1].code", is(1102))) + .andExpect(jsonPath("$.result.errors[1].message", is("Invalid property 'exclude' in $exclude"))) + .andExpect(jsonPath("$.result.errors[1].element", is("exclude"))) + .andExpect(jsonPath("$.result.errors[2].code", is(1103))) + .andExpect(jsonPath("$.result.errors[2].message", is("Invalid property 'expand' in $expand"))) + .andExpect(jsonPath("$.result.errors[2].element", is("expand"))) + .andExpect(jsonPath("$.result.errors[3].code", is(1100))) + .andExpect(jsonPath("$.result.errors[3].message", is("Invalid property 'filter' in $filter"))) + .andExpect(jsonPath("$.result.errors[3].element", is("filter"))) + .andExpect(jsonPath("$.result.errors[4].code", is(1104))) + .andExpect(jsonPath("$.result.errors[4].message", is("Invalid property 'orderby' in $orderby"))) + .andExpect(jsonPath("$.result.errors[4].element", is("orderby"))) + .andExpect(jsonPath("$.result.errors[5].code", is(1105))) + .andExpect(jsonPath("$.result.errors[5].message", is("Invalid property 'OrderByToManyPath' in $orderby"))) + .andExpect(jsonPath("$.result.errors[5].element", is("OrderByToManyPath"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenListValidationNotNull_whenListValueIsNotNull_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/nested-error") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"nested_property\": [{}]}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1000))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'nested_property[0].name_property' must be not null"))) + // .andExpect(jsonPath("$.result.errors[0].element", is("name_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenInvalidRegex_whenEvaluated_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/regex-error") + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1111))) + .andExpect(jsonPath("$.result.errors[0].message", is("Invalid regex expression '*'"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java new file mode 100644 index 0000000..7a65315 --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java @@ -0,0 +1,440 @@ +package net.cloudappi.apigen.archetypecore.core.advice.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.archetypecore.exceptions.*; +import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.regex.Pattern; + +@Validated +@RestController +@RequestMapping("/advice") +public class FakeApiResponseErrorsController { + + @GetMapping("/path-variable/{id}") + public void pathVariable(@PathVariable("id") Integer identifier) { + } + + @Data + public static class NotNullBody{ + @NotNull + @JsonProperty("name_property") + private String name; + } + + @PostMapping(value = "/error-not-null") + public @ResponseBody + ApiResponse postNotNullExpection(@Valid @RequestBody NotNullBody body) { + throw new RuntimeException(); + } + + @Data + public static class Email{ + @javax.validation.constraints.Email + @JsonProperty("email_property") + private String email; + } + + @PostMapping(value = "/error-email") + public @ResponseBody + ApiResponse postEmailException(@Valid @RequestBody Email email) { + throw new RuntimeException(); + } + + @Data + public static class NotEmpty{ + @javax.validation.constraints.NotEmpty + @JsonProperty("name_property") + private String name; + } + + @PostMapping(value = "/error-not-empty") + public @ResponseBody + ApiResponse postNotEmptyException(@Valid @RequestBody NotEmpty body) { + throw new RuntimeException(); + } + + @Data + public static class NotBlank{ + @javax.validation.constraints.NotBlank + @JsonProperty("name_property") + private String name; + } + + @PostMapping(value = "/error-not-blank") + public @ResponseBody + ApiResponse postNotBlankException(@Valid @RequestBody NotBlank body) { + throw new RuntimeException(); + } + + @Data + public static class Positive{ + @javax.validation.constraints.Positive + @JsonProperty("number_property") + private Integer number; + } + + @PostMapping(value = "/error-positive") + public @ResponseBody + ApiResponse postPositiveException(@Valid @RequestBody Positive body) { + throw new RuntimeException(); + } + + @Data + public static class PositiveOrZero{ + @javax.validation.constraints.PositiveOrZero + @JsonProperty("number_property") + private Integer number; + } + + @PostMapping(value = "/error-positive-or-zero") + public @ResponseBody + ApiResponse postPositiveOrZeroException(@Valid @RequestBody PositiveOrZero body) { + throw new RuntimeException(); + } + + @Data + public static class Negative{ + @javax.validation.constraints.Negative + @JsonProperty("number_property") + private Integer number; + } + + @PostMapping(value = "/error-negative") + public @ResponseBody + ApiResponse postNegativeException(@Valid @RequestBody Negative body) { + throw new RuntimeException(); + } + + @Data + public static class NegativeOrZero{ + @javax.validation.constraints.NegativeOrZero + @JsonProperty("number_property") + private Integer number; + } + + @PostMapping(value = "/error-negative-or-zero") + public @ResponseBody + ApiResponse postNegativeOrZeroException(@Valid @RequestBody NegativeOrZero body) { + throw new RuntimeException(); + } + + @Data + public static class Past{ + @javax.validation.constraints.Past + @JsonProperty("date_property") + private LocalDate date; + } + + @PostMapping(value = "/error-past") + public @ResponseBody + ApiResponse postPastException(@Valid @RequestBody Past body) { + throw new RuntimeException(); + } + + @Data + public static class PastOrPresent{ + @javax.validation.constraints.PastOrPresent + @JsonProperty("date_property") + private LocalDate date; + } + + @PostMapping(value = "/error-past-or-present") + public @ResponseBody + ApiResponse postPastOrPresentException(@Valid @RequestBody PastOrPresent body) { + throw new RuntimeException(); + } + + @Data + public static class Future{ + @javax.validation.constraints.Future + @JsonProperty("date_property") + private LocalDate date; + } + + @PostMapping(value = "/error-future") + public @ResponseBody + ApiResponse postFutureException(@Valid @RequestBody Future body) { + throw new RuntimeException(); + } + + @Data + public static class FutureOrPresent{ + @javax.validation.constraints.FutureOrPresent + @JsonProperty("date_property") + private LocalDate date; + } + + @PostMapping(value = "/error-future-or-present") + public @ResponseBody + ApiResponse postFutureOrPresentException(@Valid @RequestBody FutureOrPresent body) { + throw new RuntimeException(); + } + + @Data + public static class Min{ + @javax.validation.constraints.Min(10) + @JsonProperty("number_property") + private Long number; + } + + @PostMapping(value = "/error-min") + public @ResponseBody + ApiResponse postMinException(@Valid @RequestBody Min body) { + throw new RuntimeException(); + } + + @Data + public static class DecimalMinWithFalse{ + @DecimalMin(value = "10.25", inclusive = false) + @JsonProperty("number_property") + private BigDecimal number; + } + + @PostMapping(value = "/error-decimal-min-with-false") + public @ResponseBody + ApiResponse postMinWithFalseException(@Valid @RequestBody DecimalMinWithFalse body) { + throw new RuntimeException(); + } + + @Data + public static class DecimalMinWithTrue{ + @DecimalMin(value = "10.25", inclusive = true) + @JsonProperty("number_property") + private BigDecimal number; + } + + @PostMapping(value = "/error-decimal-min-with-true") + public @ResponseBody + ApiResponse postMinWithTrueException(@Valid @RequestBody DecimalMinWithTrue body) { + throw new RuntimeException(); + } + + @Data + public static class Max{ + @javax.validation.constraints.Max(20) + @JsonProperty("number_property") + private Long number; + } + + @PostMapping(value = "/error-max") + public @ResponseBody + ApiResponse postMaxException(@Valid @RequestBody Max body) { + throw new RuntimeException(); + } + + @Data + public static class DecimalMaxWithFalse{ + @DecimalMax(value = "20.25", inclusive = false) + @JsonProperty("number_property") + private BigDecimal number; + } + + @PostMapping(value = "/error-decimal-max-with-false") + public @ResponseBody + ApiResponse postMaxWithFalseException(@Valid @RequestBody DecimalMaxWithFalse body) { + throw new RuntimeException(); + } + + @Data + public static class DecimalMaxWithTrue{ + @DecimalMax(value = "20.25", inclusive = true) + @JsonProperty("number_property") + private BigDecimal number; + } + + @PostMapping(value = "/error-decimal-max-with-true") + public @ResponseBody + ApiResponse postMaxWithTrueException(@Valid @RequestBody DecimalMaxWithTrue body) { + throw new RuntimeException(); + } + + @Data + public static class Size { + @javax.validation.constraints.Size( min = 5, max = 10) + @JsonProperty("name_property") + private String name; + } + + @PostMapping(value = "/error-size") + public @ResponseBody + ApiResponse postSizeException(@Valid @RequestBody Size body) { + throw new RuntimeException(); + } + + @Data + public static class Digits { + @javax.validation.constraints.Digits(integer=1, fraction=2) + @JsonProperty("number_property") + private BigDecimal number; + } + + @PostMapping(value = "/error-digits") + public @ResponseBody + ApiResponse postDigitsException(@Valid @RequestBody Digits body) { + throw new RuntimeException(); + } + + @Data + public static class PatternBody { + @javax.validation.constraints.Pattern(regexp = "[A-Z]+") + @JsonProperty("name_property") + private String name; + } + + @PostMapping(value = "/error-pattern") + public @ResponseBody + ApiResponse postPatternException(@Valid @RequestBody PatternBody body) { + throw new RuntimeException(); + } + + @Data + public static class Error{ + @AssertTrue + @JsonProperty("isName_property") + private Boolean isName; + } + + @PostMapping(value = "/error") + public @ResponseBody + ApiResponse postErrorException(@Valid @RequestBody Error body) { + throw new RuntimeException(); + } + + @GetMapping(value = "/entity-not-found") + public @ResponseBody ApiResponse getEntityNotFoundException() { + throw new EntityNotFoundException("id", String.class); + } + + @GetMapping(value = "/related-entity-not-found") + public @ResponseBody ApiResponse getRelatedEntityNotFoundException() { + RelationalErrors errors = new RelationalErrors(); + errors.register(String.class, 1); + errors.register(String.class, null); + throw new RelationalErrorsException(errors); + } + + @GetMapping(value = "/missing-request-parameter") + public @ResponseBody ApiResponse getMissingServletRequestParameterException(@RequestParam(value = "$init", required = true) Integer init, + @RequestParam(value = "$limit", required = true) Integer limit){ + throw new RuntimeException(); + } + + @GetMapping(value = "/constrain-violation") + public @ResponseBody ApiResponse getConstraintViolationException(@javax.validation.constraints.PositiveOrZero @RequestParam(value = "$init", required = true) Integer init, + @RequestParam(value = "$limit", required = true) Integer limit){ + throw new RuntimeException(); + } + + @PostMapping(value = "/method-not-supported") + public @ResponseBody ApiResponse getHttpRequestMethodNotSupportedException(){ + throw new RuntimeException(); + } + + @GetMapping(value = "/exception") + public @ResponseBody ApiResponse getException() { + throw new RuntimeException(); + } + + @GetMapping(value = "/media-type", produces = MediaType.TEXT_XML_VALUE) + public @ResponseBody ApiResponse getMediaTypeException() { + throw new RuntimeException(); + } + + @GetMapping(value = "/custom-apigen-exception") + public @ResponseBody ApiResponse getCustomApigenException() { + throw new CustomApigenException("CUSTOM_ERROR", "color"); + } + + @Data + public static class Temperature { + @JsonProperty("levelTemperature_property") + private Level level; + + public enum Level{ + LOW, + HIGH + } + } + + @PostMapping(value = "/message-not-readable") + public @ResponseBody + ApiResponse postMessageNotReadableException(@Valid @RequestBody Temperature body) { + throw new RuntimeException(); + } + + @Data + public static class ErrorDate{ + @JsonProperty("date_property") + private LocalDate date; + } + + @PostMapping(value = "/error-date") + public @ResponseBody + ApiResponse postPastException(@Valid @RequestBody ErrorDate body) { + throw new RuntimeException(); + } + + @Data + public static class FilterError{ + @JsonProperty("filter_property") + private Filter filter; + } + + @GetMapping(value = "/translator-errors") + public @ResponseBody + ApiResponse postTranslatorErrors() { + InvalidPropertyPath translatorErrors = new InvalidPropertyPath(); + translatorErrors.getInvalidExpandPath().add("expand"); + translatorErrors.getInvalidFilterPath().add("filter"); + translatorErrors.getInvalidExcludePath().add("exclude"); + translatorErrors.getInvalidSelectPath().add("selected"); + translatorErrors.getInvalidOrderByPath().add("orderby"); + translatorErrors.getInvalidOrderByToManyPath().add("OrderByToManyPath"); + throw translatorErrors; + } + + @Data + public static class Body{ + @Valid + @JsonProperty("nested_property") + List nested; + + @Data + public static class NestedBody { + @NotNull + @JsonProperty("name_property") + String name; + } + } + + @PostMapping(value = "/nested-error") + public @ResponseBody + ApiResponse postNestedError(@Valid @RequestBody Body body) { + throw new RuntimeException(); + } + + @GetMapping("/regex-error") + public void regexError() { + Pattern regexPattern = Pattern.compile("*"); + } + + @GetMapping("/method-not-implemented") + public void methodNotImplemented() { + throw new NotImplementedException("GET /method-not-implemented"); + } + +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java new file mode 100644 index 0000000..b69942f --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java @@ -0,0 +1,120 @@ +package net.cloudappi.apigen.archetypecore.core.persistence; + +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.PropType; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Value; +import net.cloudappi.apigen.archetypecore.core.persistence.pagination.Pagination; +import net.cloudappi.apigen.archetypecore.core.persistence.stubs.FakeEntityDates; +import net.cloudappi.apigen.archetypecore.core.persistence.stubs.FakeEntityDatesRepository; +import net.cloudappi.apigen.archetypecore.core.persistence.stubs.FakeEntityNode; +import net.cloudappi.apigen.archetypecore.core.persistence.stubs.FakeEntityNodeRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SpringBootTest +@TestPropertySource(properties = {"spring.datasource.initialization-mode=always"}) +class ApigenRepositoryTest { + + private static final List EMPTY = Collections.emptyList(); + + private static final Long TOTAL_DATES_ENTITIES = 2L; + private static final Long PAST_DATE_ENTITY_ID = 1L; + private static final Long FUTURE_DATE_ENTITY_ID = 2L; + + private static final Long CENTER_NODE_ENTITY_ID = 1L; + private static final Long NORTH_NODE_ENTITY_ID = 2L; + + @Autowired FakeEntityDatesRepository datesRepository; + @Autowired FakeEntityNodeRepository nodeRepository; + + @Test + void givenPersistedRecords_whenSearchedPaginated_thenSearchAll() { + Pagination pagination = new Pagination(0, 10); + ApigenSearch search = new ApigenSearch(EMPTY, EMPTY, EMPTY, null, EMPTY, pagination, true); + ApigenSearchResult result = datesRepository.search(search); + assertEquals(TOTAL_DATES_ENTITIES, result.getSearchResult().size()); + assertEquals(TOTAL_DATES_ENTITIES, result.getTotal()); + } + + @Test + void givenPersistedRecords_whenSearchedPaginatedSmall_thenSearchPage() { + Pagination pagination = new Pagination(0, 1); + List orderBy = Collections.singletonList("+oneId"); + ApigenSearch search = new ApigenSearch(EMPTY, EMPTY, EMPTY, null, orderBy, pagination, true); + ApigenSearchResult result = datesRepository.search(search); + assertEquals(1, result.getSearchResult().size()); + assertEquals(PAST_DATE_ENTITY_ID, result.getSearchResult().get(0).getId()); + assertEquals(TOTAL_DATES_ENTITIES, result.getTotal()); + } + + @Test + void givenPersistedRecords_whenSearchedDatetimeFilter_thenSuccess() { + Pagination pagination = new Pagination(0, 20); + Filter filter = new Filter(); + filter.setOperation(FilterOperation.GT); + Value value = new Value(); + value.setType(PropType.DATETIME); + value.setProperty("dateTime"); + value.setValue("2020-02-02T12:00:00.000+01:00"); + filter.setValues(Collections.singletonList(value)); + ApigenSearch search = new ApigenSearch(EMPTY, EMPTY, EMPTY, filter, EMPTY, pagination, false); + ApigenSearchResult result = datesRepository.search(search); + assertEquals(1, result.getSearchResult().size()); + assertEquals(FUTURE_DATE_ENTITY_ID, result.getSearchResult().get(0).getId()); + } + + @Test + void givenPersistedRecords_whenSearchedDateFilter_thenSuccess() { + Pagination pagination = new Pagination(0, 20); + Filter filter = new Filter(); + filter.setOperation(FilterOperation.LT); + Value value = new Value(); + value.setType(PropType.DATE); + value.setProperty("date"); + value.setValue("2020-02-02"); + filter.setValues(Collections.singletonList(value)); + ApigenSearch search = new ApigenSearch(EMPTY, EMPTY, EMPTY, filter, EMPTY, pagination, false); + ApigenSearchResult result = datesRepository.search(search); + assertEquals(1, result.getSearchResult().size()); + assertEquals(PAST_DATE_ENTITY_ID, result.getSearchResult().get(0).getId()); + } + + @Test + void givenPersistedRecords_whenExpand_thenSuccess() { + List expand = Arrays.asList("children", "children.children"); + ApigenSearch search = new ApigenSearch(EMPTY, EMPTY, expand); + FakeEntityNode center = nodeRepository.searchById(CENTER_NODE_ENTITY_ID, search).orElse(null); + assertNotNull(center); + assertNotNull(center.getChildren()); + assertEquals(3, center.getChildren().size()); + FakeEntityNode north = center.getChildren().stream().filter(n -> NORTH_NODE_ENTITY_ID.equals(n.getId())).findFirst().orElse(null); + assertNotNull(north); + assertNotNull(north.getChildren()); + assertEquals(1, north.getChildren().size()); + } + + @Test + void givenPersistedRecords_whenExpandAndSelect_thenSuccess() { + List expand = Arrays.asList("children", "children.children"); + List select = Arrays.asList("id", "children.id", "children.children.id"); + ApigenSearch search = new ApigenSearch(select, EMPTY, expand); + FakeEntityNode center = nodeRepository.searchById(CENTER_NODE_ENTITY_ID, search).orElse(null); + assertNotNull(center); + assertNotNull(center.getChildren()); + assertEquals(3, center.getChildren().size()); + FakeEntityNode north = center.getChildren().stream().filter(n -> NORTH_NODE_ENTITY_ID.equals(n.getId())).findFirst().orElse(null); + assertNotNull(north); + assertNotNull(north.getChildren()); + assertEquals(1, north.getChildren().size()); + } +} \ No newline at end of file diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java new file mode 100644 index 0000000..5e5b911 --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java @@ -0,0 +1,40 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.stubs; + +import lombok.Getter; +import lombok.Setter; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; + +import javax.persistence.*; +import java.time.LocalDate; +import java.time.OffsetDateTime; + +@Getter +@Setter +@Entity +@Table(name = "entity_dates") +public class FakeEntityDates extends ApigenAbstractPersistable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long oneId; + + private LocalDate date; + private OffsetDateTime dateTime; + + + + @Override + public Long getId() { + return oneId; + } + + @Override + public void setId(Long id) { + this.oneId = id; + } + + @Override + public boolean isReference() { + return getId() != null && date == null && dateTime == null; + } +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java new file mode 100644 index 0000000..2e5624f --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java @@ -0,0 +1,8 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.stubs; + +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FakeEntityDatesRepository extends ApigenRepository { +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java new file mode 100644 index 0000000..cf74858 --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java @@ -0,0 +1,47 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.stubs; + +import lombok.Getter; +import lombok.Setter; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; + +import javax.persistence.*; +import java.util.Set; + +@Getter +@Setter +@Entity +@Table(name = "entity_nodes") +public class FakeEntityNode extends ApigenAbstractPersistable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + @ManyToOne + @JoinColumn(name = "parent_id") + private FakeEntityNode parent; + @OneToMany(mappedBy = "parent") + private Set children; + @ManyToMany + @JoinTable( + name = "entity_nodes_neighbors", + joinColumns = { + @JoinColumn(name = "source_id", referencedColumnName = "id") + }, + inverseJoinColumns = { + @JoinColumn(name = "target_id", referencedColumnName = "id") + }, + uniqueConstraints = { + @UniqueConstraint(columnNames = {"source_id", "target_id"}) + } + ) + private Set neighbors; + @OneToOne + @JoinColumn(name="nearest_id") + private FakeEntityNode nearest; + + @Override + public boolean isReference() { + return getId() != null && name == null && parent == null && children == null && neighbors == null && nearest == null; + } +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java new file mode 100644 index 0000000..39af06d --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java @@ -0,0 +1,8 @@ +package net.cloudappi.apigen.archetypecore.core.persistence.stubs; + +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FakeEntityNodeRepository extends ApigenRepository { +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java new file mode 100644 index 0000000..adefd6f --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java @@ -0,0 +1,256 @@ +package net.cloudappi.apigen.archetypecore.core.resource; + +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.PropType; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Value; +import net.cloudappi.apigen.archetypecore.core.resource.stubs.FakeResourceColor; +import net.cloudappi.apigen.archetypecore.exceptions.InvalidPropertyPath; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ResourceNamingTranslatorByReflectionTests { + + private static final List SELECT = new ArrayList<>(); + private static final List EXCLUDE = new ArrayList<>(); + private static final List EXPAND = new ArrayList<>(); + private static final Filter FILTER = new Filter(); + private static final List ORDER_BY = new ArrayList<>(); + + @Test + void givenSelectWithoutNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List select = new ArrayList<>(Arrays.asList("json_id", "json_name")); + resourceNamingTranslatorByReflection.translate(select, EXCLUDE, EXPAND, FakeResourceColor.class); + assertEquals("id", select.get(0)); + assertEquals("name", select.get(1)); + } + + @Test + void givenSelectWithNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List select = new ArrayList<>(Arrays.asList("json_id", "json_name", "json_form.property_id")); + resourceNamingTranslatorByReflection.translate(select, EXCLUDE, EXPAND, FakeResourceColor.class); + assertEquals("id", select.get(0)); + assertEquals("name", select.get(1)); + assertEquals("form.id", select.get(2)); + } + + @Test + void givenSelectWithSetNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List select = new ArrayList<>(Arrays.asList("json_id", "json_name", "json_forms.property_id")); + resourceNamingTranslatorByReflection.translate(select, EXCLUDE, EXPAND, FakeResourceColor.class); + assertEquals("id", select.get(0)); + assertEquals("name", select.get(1)); + assertEquals("forms.id", select.get(2)); + } + + @Test + void givenIncorrectSelect_whenTranslate_thenHaveError() { + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List select = new ArrayList<>(Arrays.asList("property_id")); + InvalidPropertyPath thrown = assertThrows( + InvalidPropertyPath.class, + () -> resourceNamingTranslatorByReflection.translate(select, EXCLUDE, EXPAND, FakeResourceColor.class) + ); + + assertEquals("property_id", thrown.getInvalidSelectPath().get(0)); + } + + @Test + void givenIncorrectResourceNameInSelect_whenTranslate_thenHaveError() { + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List select = new ArrayList<>(Arrays.asList("json_id")); + InvalidPropertyPath thrown = assertThrows( + InvalidPropertyPath.class, + () -> resourceNamingTranslatorByReflection.translate(select, EXCLUDE, EXPAND, String.class) + ); + + assertEquals("json_id", thrown.getInvalidSelectPath().get(0)); + } + + @Test + void givenExcludeWithoutNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List exclude = new ArrayList<>(Arrays.asList("json_id", "json_name")); + resourceNamingTranslatorByReflection.translate(SELECT, exclude, EXPAND, FakeResourceColor.class); + assertEquals("id", exclude.get(0)); + assertEquals("name", exclude.get(1)); + } + + @Test + void givenExcludeWithNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List exclude = new ArrayList<>(Arrays.asList("json_id", "json_name", "json_form.property_id")); + resourceNamingTranslatorByReflection.translate(SELECT, exclude, EXPAND, FakeResourceColor.class); + assertEquals("id", exclude.get(0)); + assertEquals("name", exclude.get(1)); + assertEquals("form.id", exclude.get(2)); + } + + @Test + void givenExcludeWithSetNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List exclude = new ArrayList<>(Arrays.asList("json_id", "json_name", "json_forms.property_id")); + resourceNamingTranslatorByReflection.translate(SELECT, exclude, EXPAND, FakeResourceColor.class); + assertEquals("id", exclude.get(0)); + assertEquals("name", exclude.get(1)); + assertEquals("forms.id", exclude.get(2)); + } + + @Test + void givenExpandWithSetNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List expand = new ArrayList<>(Arrays.asList("json_forms")); + resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, expand, FakeResourceColor.class); + assertEquals("forms", expand.get(0)); + } + + @Test + void givenExpandWithNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List expand = new ArrayList<>(Arrays.asList("json_form")); + resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, expand, FakeResourceColor.class); + assertEquals("form", expand.get(0)); + } + + @Test + void givenOrderByPositiveWithoutNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List orderBy = new ArrayList<>(Arrays.asList("+json_id")); + resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, EXPAND, orderBy, FakeResourceColor.class); + assertEquals("+id", orderBy.get(0)); + } + + @Test + void givenOrderByNegativeWithoutNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List orderBy = new ArrayList<>(Arrays.asList("-json_id")); + resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, EXPAND, orderBy, FakeResourceColor.class); + assertEquals("-id", orderBy.get(0)); + } + + @Test + void givenOrderByPositiveWithNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List orderBy = new ArrayList<>(Arrays.asList("+json_id", "+json_form.property_id")); + resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, EXPAND, orderBy, FakeResourceColor.class); + assertEquals("+id", orderBy.get(0)); + assertEquals("+form.id", orderBy.get(1)); + } + + @Test + void givenOrderByNegativeeWithNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List orderBy = new ArrayList<>(Arrays.asList("-json_id", "-json_form.property_id")); + resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, EXPAND, orderBy, FakeResourceColor.class); + assertEquals("-id", orderBy.get(0)); + assertEquals("-form.id", orderBy.get(1)); + } + + @Test + void givenOrderByPositiveWithSetNestedId_whenTranslate_thenHaveError() { + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List orderBy = new ArrayList<>(Arrays.asList("+json_id", "+json_forms.property_id")); + InvalidPropertyPath thrown = assertThrows( + InvalidPropertyPath.class, + () -> resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, EXPAND, orderBy, FakeResourceColor.class) + ); + + assertEquals("+json_forms.property_id", thrown.getInvalidOrderByToManyPath().get(0)); + } + + @Test + void givenOrderByNegativeWithSetNestedId_whenTranslate_thenHaveError() { + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List orderBy = new ArrayList<>(Arrays.asList("-json_id", "-json_forms.property_id")); + InvalidPropertyPath thrown = assertThrows( + InvalidPropertyPath.class, + () -> resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, EXPAND, orderBy, FakeResourceColor.class) + ); + + assertEquals("-json_forms.property_id", thrown.getInvalidOrderByToManyPath().get(0)); + } + + @Test + void givenIncorrectOrderBy_whenTranslate_thenHaveError() { + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + List orderBy = new ArrayList<>(Arrays.asList("json_form")); + InvalidPropertyPath thrown = assertThrows( + InvalidPropertyPath.class, + () -> resourceNamingTranslatorByReflection.translate(SELECT, EXCLUDE, EXPAND, orderBy, FakeResourceColor.class) + ); + + assertEquals("[]", thrown.getInvalidExpandPath().toString()); + } + + @Test + void givenFilterWithoutNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + Filter filter = new Filter(); + filter.setOperation(FilterOperation.LT); + Value value = new Value(); + value.setType(PropType.INTEGER); + value.setProperty("json_id"); + value.setValue("10"); + filter.setValues(Collections.singletonList(value)); + resourceNamingTranslatorByReflection.translate(filter, FakeResourceColor.class); + assertEquals("id", filter.getValues().get(0).getProperty()); + } + + @Test + void givenFilterWithNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + Filter filter = new Filter(); + filter.setOperation(FilterOperation.LT); + Value value = new Value(); + value.setType(PropType.INTEGER); + value.setProperty("json_form.property_id"); + value.setValue("10"); + filter.setValues(Collections.singletonList(value)); + resourceNamingTranslatorByReflection.translate(filter, FakeResourceColor.class); + assertEquals("form.id", filter.getValues().get(0).getProperty()); + } + + @Test + void givenFilterWithSetNestedId_whenTranslate_thenSuccess(){ + String packageName = FakeResourceColor.class.getPackage().getName(); + ResourceNamingTranslatorByReflection resourceNamingTranslatorByReflection = new ResourceNamingTranslatorByReflection(packageName); + Filter filter = new Filter(); + filter.setOperation(FilterOperation.LT); + Value value = new Value(); + value.setType(PropType.INTEGER); + value.setProperty("json_forms.property_id"); + value.setValue("10"); + filter.setValues(Collections.singletonList(value)); + resourceNamingTranslatorByReflection.translate(filter, FakeResourceColor.class); + assertEquals("forms.id", filter.getValues().get(0).getProperty()); + } +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java new file mode 100644 index 0000000..9aa7145 --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java @@ -0,0 +1,23 @@ +package net.cloudappi.apigen.archetypecore.core.resource.stubs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.resource.ApigenEntityOutResource; + +import java.util.Set; + +@Data +@ApigenEntityOutResource +public class FakeResourceColor { + @JsonProperty("json_id") + private Long id; + + @JsonProperty("json_name") + private String name; + + @JsonProperty("json_forms") + private Set forms; + + @JsonProperty("json_form") + private FakeResourceForm form; +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java new file mode 100644 index 0000000..0ada4b6 --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java @@ -0,0 +1,15 @@ +package net.cloudappi.apigen.archetypecore.core.resource.stubs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.resource.ApigenEntityOutResource; + +@Data +@ApigenEntityOutResource +public class FakeResourceForm { + @JsonProperty("property_id") + private Long id; + + @JsonProperty("property_name") + private String name; +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponseObjectMother.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponseObjectMother.java new file mode 100644 index 0000000..c6ae7ee --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponseObjectMother.java @@ -0,0 +1,24 @@ +package net.cloudappi.apigen.archetypecore.core.responses; + +public class ApiResponseObjectMother { + + private ApiResponseObjectMother() { + // Intentional blank + } + + public static ApiResponse createApiResponseOnlyWithPaginationWithTotal(int init, int limit) { + ApiResponse apiResponse = new ApiResponse(); + apiResponse.withMetadataPagination(init, limit, 100L); + return apiResponse; + } + + public static ApiResponse createApiResponseOnlyWithPaginationWithoutTotal(int init, int limit) { + ApiResponse apiResponse = new ApiResponse(); + apiResponse.withMetadataPagination(init, limit, null); + return apiResponse; + } + + public static ApiResponse createEmptyApiResponse() { + return new ApiResponse(); + } +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java new file mode 100644 index 0000000..2241393 --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java @@ -0,0 +1,235 @@ +package net.cloudappi.apigen.archetypecore.interceptors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; + +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + + +@SpringBootTest +@AutoConfigureMockMvc +public class ApiResponseBodyAdviceTest { + + @Autowired + private MockMvc mvc; + + @Test + public void givenApiResponseWithParamsAndTotal_whenInitValue20LimitValue10_thenReturnAllLinks() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/pagination/with-total?name=AAA&$init=20&apes=bbb&$limit=10&$expand=boss") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.metadata.paging.links.self.href", is("/pagination/with-total?name=AAA&$init=20&apes=bbb&$limit=10&$expand=boss"))) + .andExpect(jsonPath("$.metadata.paging.links.previous.href", is("/pagination/with-total?name=AAA&apes=bbb&$limit=10&$expand=boss&$init=10"))) + .andExpect(jsonPath("$.metadata.paging.links.next.href", is("/pagination/with-total?name=AAA&apes=bbb&$limit=10&$expand=boss&$init=30"))) + .andExpect(jsonPath("$.metadata.paging.links.first.href", is("/pagination/with-total?name=AAA&apes=bbb&$limit=10&$expand=boss&$init=0"))) + .andExpect(jsonPath("$.metadata.paging.links.last.href", is("/pagination/with-total?name=AAA&apes=bbb&$limit=10&$expand=boss&$init=90"))) + .andReturn().getResponse(); + + // then + assertEquals(200, response.getStatus()); + } + + @Test + public void givenApiResponseWithParamsAndWithoutTotal_whenInitValue20LimitValue10_thenReturnAllLinksExceptedLastLink() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/pagination/without-total?name=AAA&$init=20&apes=bbb&$limit=10&$expand=boss") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.metadata.paging.links.self.href", is("/pagination/without-total?name=AAA&$init=20&apes=bbb&$limit=10&$expand=boss"))) + .andExpect(jsonPath("$.metadata.paging.links.previous.href", is("/pagination/without-total?name=AAA&apes=bbb&$limit=10&$expand=boss&$init=10"))) + .andExpect(jsonPath("$.metadata.paging.links.next.href", is("/pagination/without-total?name=AAA&apes=bbb&$limit=10&$expand=boss&$init=30"))) + .andExpect(jsonPath("$.metadata.paging.links.first.href", is("/pagination/without-total?name=AAA&apes=bbb&$limit=10&$expand=boss&$init=0"))) + .andExpect(jsonPath("$.metadata.paging.links.last").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(200, response.getStatus()); + } + + @Test + public void givenApiResponseWithoutPagination_whenNotPagination_thenNotReturnMetadata() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/pagination/without-pagination?name=AAA&apes=bbb&$expand=boss") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.metadata").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(200, response.getStatus()); + } + + @Test + public void givenApiResponseWithDefaultPagination_whenPaginationNotDefined_thenReturnOnlySelfNextAndFirstLinks() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/pagination") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.metadata.paging.links.self.href", is("/pagination"))) + .andExpect(jsonPath("$.metadata.paging.links.previous").doesNotExist()) + .andExpect(jsonPath("$.metadata.paging.links.next.href", is("/pagination?$init=25"))) + .andExpect(jsonPath("$.metadata.paging.links.first.href", is("/pagination?$init=0"))) + .andExpect(jsonPath("$.metadata.paging.links.last").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(200, response.getStatus()); + } + + @Test + public void givenApiResponseWithOutParams_whenNotReturnApiResponse_thenNotDoApiResponseBodyAdvice() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/pagination/no-response?$init=0&$limit=10") + .accept(MediaType.APPLICATION_JSON)) + .andReturn().getResponse(); + + // then + assertEquals(200, response.getStatus()); + } + + @Test + public void givenApiResponseWith200_whenHttpCode200_thenReturnsApiResultWithStatusTrueHttpCode200AndInfoOK() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/apiresult/with-200") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.status", is(true))) + .andExpect(jsonPath("$.result.http_code", is(200))) + .andExpect(jsonPath("$.result.info", is("OK"))) + .andExpect(jsonPath("$.result.errors").doesNotExist()) + .andExpect(jsonPath("$.result.trace_id").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(200, response.getStatus()); + } + + @Test + public void givenApiResponseWith301_whenHttpCode301_thenReturnsApiResultWithStatusTrueHttpCode301AndInfoOK() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/apiresult/with-301") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.status", is(true))) + .andExpect(jsonPath("$.result.http_code", is(301))) + .andExpect(jsonPath("$.result.info", is("OK"))) + .andExpect(jsonPath("$.result.errors").doesNotExist()) + .andExpect(jsonPath("$.result.trace_id").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(301, response.getStatus()); + } + + @Test + public void givenApiResponseWith404_whenHttpCode404_thenReturnsApiResultWithStatusFalseHttpCode404AndInfoERROR() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/apiresult/with-404") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.status", is(false))) + .andExpect(jsonPath("$.result.http_code", is(404))) + .andExpect(jsonPath("$.result.info", is("ERROR"))) + .andExpect(jsonPath("$.result.errors").doesNotExist()) + .andExpect(jsonPath("$.result.trace_id").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(404, response.getStatus()); + } + + @Test + public void givenApiResponseWith503_whenHttpCode503_thenReturnsApiResultWithStatusFalseHttpCode503AndInfoERROR() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/apiresult/with-503") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.status", is(false))) + .andExpect(jsonPath("$.result.http_code", is(503))) + .andExpect(jsonPath("$.result.info", is("ERROR"))) + .andExpect(jsonPath("$.result.errors").doesNotExist()) + .andExpect(jsonPath("$.result.trace_id").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(503, response.getStatus()); + } + + @Test + public void givenApiResponseWithMissingParameter_whenInitDoesExist_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/apiresult/missing-parameter-exception") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.status", is(false))) + .andExpect(jsonPath("$.result.http_code", is(400))) + .andExpect(jsonPath("$.result.info", is("ERROR"))) + .andExpect(jsonPath("$.result.errors[0].code", is(1110))) + .andExpect(jsonPath("$.result.errors[0].message", is("Query parameter '$init' is required"))) + .andExpect(jsonPath("$.result.errors[0].element", is("$init"))) + .andExpect(jsonPath("$.result.trace_id").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + public void givenApiResponseWithEntityNotFound_whenEntityNotFound_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/apiresult/entity-notfound-exception-apiresult") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.status", is(false))) + .andExpect(jsonPath("$.result.http_code", is(404))) + .andExpect(jsonPath("$.result.info", is("ERROR"))) + .andExpect(jsonPath("$.result.errors[0].code", is(1106))) + .andExpect(jsonPath("$.result.errors[0].message", is("Element with id 'id' of type 'String' not found"))) + .andExpect(jsonPath("$.result.errors[0].element", is("id"))) + .andExpect(jsonPath("$.result.trace_id").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(404, response.getStatus()); + } + + @Test + public void givenApiResponseWith200AndTraceId_whenHttpCode200AndTraceIDApiKeyHeader_thenReturnsApiResultWithStatusTrueHttpCode200InfoOKAndTraceIdApikeyvalue() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/apiresult/with-200") + .accept(MediaType.APPLICATION_JSON).header("api-key-header", "apikeyvalue")) + .andExpect(jsonPath("$.result.status", is(true))) + .andExpect(jsonPath("$.result.http_code", is(200))) + .andExpect(jsonPath("$.result.info", is("OK"))) + .andExpect(jsonPath("$.result.errors").doesNotExist()) + .andExpect(jsonPath("$.result.trace_id", is("apikeyvalue"))) + .andReturn().getResponse(); + + // then + assertEquals(200, response.getStatus()); + } + +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ExpandInterceptorTests.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ExpandInterceptorTests.java new file mode 100644 index 0000000..c62199d --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ExpandInterceptorTests.java @@ -0,0 +1,192 @@ +package net.cloudappi.apigen.archetypecore.interceptors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.util.AntPathMatcher; + +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@SpringBootTest +@AutoConfigureMockMvc +class ExpandInterceptorTests { + + @Autowired + private MockMvc mvc; + + @Test + void givenExplicitAllowedExpand_whenUseOtherExpand_thenError() throws Exception { + mvc.perform(get("/expand/allowed?$expand=parent,children,other")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("other"))) + .andExpect(status().is(400)); + } + + @Test + void givenExplicitAllowedExpand_whenUseAllowedExpand_thenSuccess() throws Exception { + mvc.perform(get("/expand/allowed?$expand=parent,children")) + .andExpect(status().is(200)); + } + + @Test + void givenExplicitExcludedExpand_whenUseExcludedExpand_thenError() throws Exception { + mvc.perform(get("/expand/excluded?$expand=invoice,details")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("invoice"))) + .andExpect(status().is(400)); + } + + @Test + void givenExplicitExcludedExpand_whenNotUseExcludedExpand_thenSuccess() throws Exception { + mvc.perform(get("/expand/excluded?$expand=details,other")) + .andExpect(status().is(200)); + } + + @Test + void givenExplicitExpandLevel_whenUseBiggerLevelExpand_thenError() throws Exception { + mvc.perform(get("/expand/level?$expand=details,other,other.other")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("other.other"))) + .andExpect(status().is(400)); + } + + @Test + void givenExplicitExpandLevel_whenUseAllowedLevelExpand_thenSuccess() throws Exception { + mvc.perform(get("/expand/level?$expand=details,other")) + .andExpect(status().is(200)); + } + + @Test + void givenGenericExpandLevel_whenUseBiggerLevelExpand_thenError() throws Exception { + mvc.perform(get("/expand/generic?$expand=details,other,other.other,parent.parent.parent")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("parent.parent.parent"))) + .andExpect(status().is(400)); + } + + @Test + void givenGenericExpandLevel_whenUseAllowedLevelExpand_thenSuccess() throws Exception { + mvc.perform(get("/expand/generic?$expand=details,other, parent.parent")) + .andExpect(status().is(200)); + } + + @Test + void givenExplicitExcludedExpand_whenUseExcludedExpandInNotSlashedUrl_thenError() throws Exception { + mvc.perform(get("/expand/trailing-slash?$expand=slash")) + .andExpect(status().is(400)); + } + + @Test + void givenExplicitExcludedExpand_whenUseExcludedExpandInSlashedUrl_thenNotFound() throws Exception { + mvc.perform(get("/expand/trailing-slash/?$expand=slash")) + .andExpect(status().is(404)); + } + + @Test + void givenRexegExplicitIncludedExpand_whenNotUseExcludedExpandInExplicitUrl_thenSuccess() throws Exception { + mvc.perform(get("/expand/expression/explicit?$expand=other")) + .andExpect(status().is(200)); + } + + @Test + void givenRexegExplicitIncludedExpand_whenUseExcludedExpandInExplicitUrl_thenError() throws Exception { + mvc.perform(get("/expand/expression/explicit?$expand=details")) + .andExpect(status().is(400)); + } + + @Test + void givenAnnotationWithAllowed_whenUseAllowedParams_thenSuccess() throws Exception { + mvc.perform(get("/expand/annotation/allowed?$expand=one,one.two,one.two.three")) + .andExpect(status().is(200)); + } + + @Test + void givenAnnotationWithAllowed_whenUseNonAllowedParams_thenError() throws Exception { + mvc.perform(get("/expand/annotation/allowed?$expand=one,other")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("other"))) + .andExpect(status().is(400)); + } + + @Test + void givenAnnotationWithExcluded_whenUseNonExcludedParams_thenSuccess() throws Exception { + mvc.perform(get("/expand/annotation/excluded?$expand=other,other.two")) + .andExpect(status().is(200)); + } + + @Test + void givenAnnotationWithExcluded_whenUseExcludedParams_thenSuccess() throws Exception { + mvc.perform(get("/expand/annotation/excluded?$expand=other,other.two,one")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("one"))) + .andExpect(status().is(400)); + } + + @Test + void givenAnnotationWithExcluded_whenUseNonExcludedParamsWithBiggerLevelThanDefault_thenError() throws Exception { + mvc.perform(get("/expand/annotation/excluded?$expand=other,other.two,other.two.three")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("other.two.three"))) + .andExpect(status().is(400)); + } + + @Test + void givenAnnotationWithExcludedAndLevel_whenUseNonExcludedParamsWithBiggerLevel_thenError() throws Exception { + mvc.perform(get("/expand/annotation/excluded-and-level?$expand=a,a.b.c,a.b.c.d")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("a.b.c.d"))) + .andExpect(status().is(400)); + } + + @Test + void givenAnnotationWithLevel_whenUseParamsWithBiggerLevel_thenError() throws Exception { + mvc.perform(get("/expand/annotation/level?$expand=a,a.b.c,a.b.c.d")) + .andExpect(jsonPath("$.result.errors.length()", is(1))) + .andExpect(jsonPath("$.result.errors[0].code", is(1103))) + .andExpect(jsonPath("$.result.errors[0].element", is("a.b.c.d"))) + .andExpect(status().is(400)); + } + + @Test + void documentedMatchers() { + AntPathMatcher m = new AntPathMatcher(); + assertTrue(m.match("/resources/*", "/resources/id")); + + assertFalse(m.match("/resources/*", "/resources/id/other")); + assertTrue(m.match("/resources/**", "/resources/id/other")); + + assertTrue(m.match("/resources", "/resources")); + assertFalse(m.match("/resources/", "/resources")); + + assertTrue(m.match("/resources/{identifier}", "/resources/id")); + + assertTrue(m.match("/resources/{path:^(?!other$).*}", "/resources/id")); + assertFalse(m.match("/resources/{path:^(?!id$).*}", "/resources/id")); + + assertTrue(m.match("/resources/{path:^(?!other$|id$).*}", "/resources/others")); + assertFalse(m.match("/resources/{path:^(?!other$|id$).*}", "/resources/id")); + + assertTrue(m.match("/resources/{:^(?!other$).*}", "/resources/id")); + assertFalse(m.match("/resources/{:^(?!id$).*}", "/resources/id")); + + assertTrue(m.match("/resources/{:^(?!other$|id$).*}", "/resources/others")); + assertFalse(m.match("/resources/{:^(?!other$|id$).*}", "/resources/id")); + } + +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java new file mode 100644 index 0000000..21cf439 --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java @@ -0,0 +1,33 @@ +package net.cloudappi.apigen.archetypecore.interceptors; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@SpringBootTest +@AutoConfigureMockMvc +class TraceIdInterceptorTests { + + @Autowired + private MockMvc mvc; + + @Test + void givenTraceIdConfigured_whenApiReceivesCallWithTraceId_thenApigenContextHasTheValue() throws Exception { + MvcResult result = mvc.perform(get("/trace") + .header("api-key-header", "test")) + .andExpect(status().is(200)) + .andReturn(); + + String response = result.getResponse().getContentAsString(); + assertEquals("test", response); + } + +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeApiResultController.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeApiResultController.java new file mode 100644 index 0000000..4dba8fa --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeApiResultController.java @@ -0,0 +1,51 @@ +package net.cloudappi.apigen.archetypecore.interceptors.controller; + +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponseObjectMother; +import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/apiresult") +public class FakeApiResultController { + + @GetMapping(value = "/with-200") + public @ResponseBody ApiResponse getNewApiResultWith200() { + return ApiResponseObjectMother.createEmptyApiResponse(); + } + + @GetMapping(value = "/with-301") + @ResponseStatus(HttpStatus.MOVED_PERMANENTLY) + public @ResponseBody ApiResponse getNewApiResultWith301() { + return ApiResponseObjectMother.createEmptyApiResponse(); + } + + @GetMapping(value = "/with-404") + @ResponseStatus(HttpStatus.NOT_FOUND) + public @ResponseBody ApiResponse getNewApiResultWith404() { + return ApiResponseObjectMother.createEmptyApiResponse(); + } + + @GetMapping(value = "/with-503") + @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) + public @ResponseBody ApiResponse getNewApiResultWith503() { + return ApiResponseObjectMother.createEmptyApiResponse(); + } + + @GetMapping(value = "/null-apiresult") + public @ResponseBody ApiResponse getNullApiResult() { + return null; + } + + @GetMapping(value = "/missing-parameter-exception") + public @ResponseBody ApiResponse getMissingParameterException() throws MissingServletRequestParameterException { + throw new MissingServletRequestParameterException("$init", "Integer"); + } + + @GetMapping(value = "/entity-notfound-exception-apiresult") + public @ResponseBody ApiResponse getEntityNotFoundException() { + throw new EntityNotFoundException("id", String.class); + } +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeExpandController.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeExpandController.java new file mode 100644 index 0000000..5d164db --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeExpandController.java @@ -0,0 +1,63 @@ +package net.cloudappi.apigen.archetypecore.interceptors.controller; + +import net.cloudappi.apigen.archetypecore.interceptors.expand.ApigenExpand; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/expand") +public class FakeExpandController { + + @GetMapping("/allowed") + public void getAllowed() { + } + + @GetMapping("/excluded") + public void getExcluded() { + } + + @GetMapping("/generic") + public void getGeneric() { + } + + @GetMapping("/level") + public void getLevel() { + } + + @GetMapping("/trailing-slash") + public void getTrailingSlash() { + } + + @GetMapping("/expression") + public void getExpression() { + } + + @GetMapping("/expression/explicit") + public void getExpressionExplicit() { + } + + @GetMapping("/expression/implicit") + public void getExpressionImplicit() { + } + + @ApigenExpand(allowed = {"one", "one.two", "one.two.three"}) + @GetMapping("/annotation/allowed") + public void getAnnotationAllowed() { + } + + @ApigenExpand(excluded = {"one", "one.two", "one.two.three"}) + @GetMapping("/annotation/excluded") + public void getAnnotationExcluded() { + } + + @ApigenExpand(excluded = {"one", "one.two", "one.two.three"}, maxLevel = 3) + @GetMapping("/annotation/excluded-and-level") + public void getAnnotationExcludedAndLevel() { + } + + @ApigenExpand(maxLevel = 3) + @GetMapping("/annotation/level") + public void getAnnotationLevel() { + } +} \ No newline at end of file diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakePaginationController.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakePaginationController.java new file mode 100644 index 0000000..59ddf8c --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakePaginationController.java @@ -0,0 +1,39 @@ +package net.cloudappi.apigen.archetypecore.interceptors.controller; + +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponseObjectMother; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/pagination") +public class FakePaginationController { + + @GetMapping(value = "/with-total") + public @ResponseBody ApiResponse getPaginationTotal(@RequestParam(value = "$init", required = true, defaultValue = "0") Integer init, + @RequestParam(value = "$limit", required = true, defaultValue = "25") Integer limit) { + return ApiResponseObjectMother.createApiResponseOnlyWithPaginationWithTotal(init, limit); + } + + @GetMapping(value = "/without-total") + public @ResponseBody ApiResponse getPagination(@RequestParam(value = "$init", required = true, defaultValue = "0") Integer init, + @RequestParam(value = "$limit", required = true, defaultValue = "25") Integer limit) { + return ApiResponseObjectMother.createApiResponseOnlyWithPaginationWithoutTotal(init, limit); + } + + @GetMapping(value = "/without-pagination") + public @ResponseBody ApiResponse get() { + return ApiResponseObjectMother.createEmptyApiResponse(); + } + + @GetMapping(value = "") + public @ResponseBody ApiResponse getDefault(@RequestParam(value = "$init", required = true, defaultValue = "0") Integer init, + @RequestParam(value = "$limit", required = true, defaultValue = "25") Integer limit) { + return ApiResponseObjectMother.createApiResponseOnlyWithPaginationWithoutTotal(init, limit); + } + + @GetMapping(value = "/no-response") + public @ResponseBody String getNoApiResponse(@RequestParam(value = "$init", required = true, defaultValue = "0") Integer init, + @RequestParam(value = "$limit", required = true, defaultValue = "25") Integer limit) { + return "hola"; + } +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java new file mode 100644 index 0000000..a915e6c --- /dev/null +++ b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java @@ -0,0 +1,19 @@ +package net.cloudappi.apigen.archetypecore.interceptors.controller; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.interceptors.ApigenContext; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequestMapping("/trace") +public class FakeTraceIdController { + + @GetMapping + public String trace() { + return ApigenContext.getTraceId(); + } + +} diff --git a/archetype-core/src/test/resources/application.properties b/archetype-core/src/test/resources/application.properties new file mode 100644 index 0000000..69f2598 --- /dev/null +++ b/archetype-core/src/test/resources/application.properties @@ -0,0 +1,25 @@ +logging.level.net.cloudappi.apigen=DEBUG +spring.jpa.show-sql=true +spring.jpa.hibernate.ddl-auto=create-drop + +spring.jackson.serialization.fail_on_empty_beans=false +spring.jackson.default-property-inclusion=NON_NULL +spring.mvc.throw-exception-if-no-handler-found=true +spring.resources.add-mappings=false + +spring.datasource.initialization-mode=never + +apigen.trace-header=api-key-header + +apigen.documentation.enabled=true + +apigen.errors.CUSTOM_ERROR.code=9999 +apigen.errors.CUSTOM_ERROR.message-template= Parameter not found ''{0}'' + +apigen.api.expand.level=2 +apigen.api.paths[/expand/allowed].expand.allowed=parent, children +apigen.api.paths[/expand/excluded].expand.excluded=invoice +apigen.api.paths[/expand/level].expand.level=1 +apigen.api.paths[/expand/trailing-slash].expand.excluded=slash +apigen.api.paths[/expand/expression/{\:^(?!explicit$|other$).*}].expand.allowed=details +apigen.api.paths[/expand/expression/explicit].expand.excluded=details \ No newline at end of file diff --git a/archetype-core/src/test/resources/data.sql b/archetype-core/src/test/resources/data.sql new file mode 100644 index 0000000..0f66882 --- /dev/null +++ b/archetype-core/src/test/resources/data.sql @@ -0,0 +1,13 @@ +insert into entity_dates(one_id, date, date_time) values (1, DATE '2010-06-06', TIMESTAMP '2010-01-01 10:00:00+01'); +insert into entity_dates(one_id, date, date_time) values (2, DATE '2100-06-06', TIMESTAMP '2100-01-01 10:00:00+01'); + +insert into entity_nodes(id, name, parent_id, nearest_id) values (1, 'Center', null, null); +insert into entity_nodes(id, name, parent_id, nearest_id) values (2, 'North', 1, 1); +insert into entity_nodes(id, name, parent_id, nearest_id) values (3, 'North+', 2, 2); +insert into entity_nodes(id, name, parent_id, nearest_id) values (4, 'Est', 1, 1); +insert into entity_nodes(id, name, parent_id, nearest_id) values (5, 'West', 1, 1); + +insert into entity_nodes_neighbors(source_id, target_id) values (1, 2); +insert into entity_nodes_neighbors(source_id, target_id) values (1, 4); +insert into entity_nodes_neighbors(source_id, target_id) values (1, 5); +insert into entity_nodes_neighbors(source_id, target_id) values (2, 3); \ No newline at end of file diff --git a/archetype-parent-spring-boot/pom.xml b/archetype-parent-spring-boot/pom.xml new file mode 100644 index 0000000..4b855b9 --- /dev/null +++ b/archetype-parent-spring-boot/pom.xml @@ -0,0 +1,146 @@ + + + + + org.springframework.boot + spring-boot-starter-parent + 2.3.3.RELEASE + + + + 4.0.0 + + net.cloudappi.apigen + archetype-parent-spring-boot + 0.0.1-SNAPSHOT + pom + + + + 0.0.1-SNAPSHOT + + 1.8 + 1.8 + 1.8 + + UTF-8 + UTF-8 + + 0.0.1-SNAPSHOT + 1.3.1.Final + 2.9.2 + + + + + net.cloudappi.apigen + archetype-core + ${apigen.version} + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-validation + + + org.projectlombok + lombok + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + io.springfox + springfox-swagger2 + ${springfox.version} + + + + mapstruct + org.mapstruct + + + + + io.springfox + springfox-swagger-ui + ${springfox.version} + + + + com.h2database + h2 + test + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.projectlombok + lombok + ${lombok.version} + + + + + + + + + + + false + + + true + + cloudappi-maven-snapshots + https://nexus.cloudappi.net/repository/maven-snapshots/ + + + + \ No newline at end of file diff --git a/generator-core/pom.xml b/generator-core/pom.xml new file mode 100644 index 0000000..71a6ca4 --- /dev/null +++ b/generator-core/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + + apigen + net.cloudappi.apigen + 0.0.1-SNAPSHOT + ../pom.xml + + + + generator-core + generator-core + ${revision} + + + + + com.squareup + javapoet + + + org.apache.maven + maven-model + + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + + io.swagger.parser.v3 + swagger-parser + + + + net.cloudappi.apigen + archetype-core + + + org.springframework + spring-web + + + org.springframework.boot + spring-boot-test + + + org.springframework.boot + spring-boot-starter-validation + + + io.springfox + springfox-swagger2 + + + org.mapstruct + mapstruct + + + + org.junit.jupiter + junit-jupiter + + + + org.mockito + mockito-core + test + + + \ No newline at end of file diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/Configuration.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/Configuration.java new file mode 100644 index 0000000..43a41a2 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/Configuration.java @@ -0,0 +1,43 @@ +package net.cloudappi.apigen.generatorcore.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Data +public class Configuration { + + @NotBlank + @JsonProperty("name") + private String name; + @JsonProperty("description") + private String description; + @NotBlank + @JsonProperty("group") + private String group; + @NotBlank + @JsonProperty("artifact") + private String artifact; + @NotBlank + @JsonProperty("version") + private String version; + @NotNull + @JsonProperty("partial") + private Boolean partial; + @NotNull + @JsonProperty("entities") + private List entities; + @NotEmpty + @JsonProperty("controllers") + private List controllers; + + public String getBasePackage() { + return group + "." + artifact; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Attribute.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Attribute.java new file mode 100644 index 0000000..2ee0304 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Attribute.java @@ -0,0 +1,27 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import net.cloudappi.apigen.generatorcore.config.validation.Validation; + +import java.util.List; + +@Data +public class Attribute { + @JsonProperty("name") + protected String name; + @JsonProperty("validations") + List validations; + @JsonProperty("attributes") + List attributes; + @JsonProperty("type") + protected String type; + @JsonProperty("format") + protected String format; + @JsonProperty("is_collection") + protected boolean isCollection; + @JsonProperty("related_entity") + private String relatedEntity; + @JsonProperty("entity_field_name") + private String entityFieldName; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Controller.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Controller.java new file mode 100644 index 0000000..ee6712a --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Controller.java @@ -0,0 +1,16 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class Controller { + @JsonProperty("entity") + private String entity; + @JsonProperty("mapping") + private String mapping; + @JsonProperty("endpoints") + private List endpoints; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Endpoint.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Endpoint.java new file mode 100644 index 0000000..373d1b7 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Endpoint.java @@ -0,0 +1,33 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class Endpoint { + @JsonProperty("method") + private Method method; + @JsonProperty("mapping") + private String mapping; + @JsonProperty("name") + private String name; + @JsonProperty("parameters") + private List parameters; + @JsonProperty("response") + private Response response; + @JsonProperty("request") + private Request request; + @JsonProperty("related_entity") + private String relatedEntity; + + public enum Method { + GET("Find"), POST("Create"), PUT("Update"), DELETE("Delete"); + public final String prefix; + + Method(String prefix) { + this.prefix = prefix; + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Parameter.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Parameter.java new file mode 100644 index 0000000..0af9a03 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Parameter.java @@ -0,0 +1,14 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class Parameter extends Attribute { + @JsonProperty("required") + protected Boolean required; + @JsonProperty("in") + private String in; + @JsonProperty("default_value") + private Object defaultValue; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Request.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Request.java new file mode 100644 index 0000000..ca156c4 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Request.java @@ -0,0 +1,15 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import net.cloudappi.apigen.generatorcore.config.validation.Validation; + +import java.util.List; + +@Data +public class Request { + @JsonProperty("related_entity") + private String relatedEntity; + @JsonProperty("attributes") + private List attributes; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Response.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Response.java new file mode 100644 index 0000000..af91822 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Response.java @@ -0,0 +1,22 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class Response { + @JsonProperty("is_standard") + private Boolean isStandard; + @JsonProperty("is_collection") + private Boolean isCollection; + @JsonProperty("collection_name") + private String collectionName; + @JsonProperty("related_entity") + private String relatedEntity; + @JsonProperty("attributes") + private List attributes; + @JsonProperty("default_status_code") + private Integer defaultStatusCode; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Attribute.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Attribute.java new file mode 100644 index 0000000..6f55166 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Attribute.java @@ -0,0 +1,38 @@ +package net.cloudappi.apigen.generatorcore.config.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import net.cloudappi.apigen.generatorcore.config.validation.Validation; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class Attribute { + + @JsonProperty("name") + private String name; + @JsonProperty("type") + private String type; + @JsonProperty("columns") + private List columns = new ArrayList<>(); + @JsonProperty("foreign_columns") + private List foreignColumns = new ArrayList<>(); + @JsonProperty("relation") + private Relation relation; + @JsonProperty("validations") + private List validations = new ArrayList<>(); + @JsonProperty("attributes") + private List attributes = new ArrayList<>(); + @JsonProperty("is_collection") + private Boolean isCollection = false; + + public Attribute() { + // Required for jackson + } + + public Attribute(String name, String type) { + this.name = name; + this.type = type; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Column.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Column.java new file mode 100644 index 0000000..d071768 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Column.java @@ -0,0 +1,45 @@ +package net.cloudappi.apigen.generatorcore.config.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class Column { + @JsonProperty("name") + private String name; + @JsonProperty("primary_key") + private Boolean primaryKey = false; + @JsonProperty("autogenerated") + private Boolean autogenerated = false; + @JsonProperty("sequence") + private String sequence; + @JsonProperty("unique") + private Boolean unique = false; // FIXME analyze only used in DDL creation + @JsonProperty("reference_column") + private String referenceColumn; + + public Column() { + } + + public Column(String name) { + this.name = name; + } + + public Column(String name, Boolean unique) { + this.name = name; + this.unique = unique; + } + + public Column(String name, Boolean unique, String referenceColumn) { + this.name = name; + this.unique = unique; + this.referenceColumn = referenceColumn; + } + + public Column(String name, Boolean unique, Boolean primaryKey, String sequence) { + this.name = name; + this.unique = unique; + this.primaryKey = primaryKey; + this.sequence = sequence; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Entity.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Entity.java new file mode 100644 index 0000000..5fc37d7 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Entity.java @@ -0,0 +1,26 @@ +package net.cloudappi.apigen.generatorcore.config.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class Entity { + @JsonProperty("name") + private String name; + @JsonProperty("table") + private String table; + @JsonProperty("attributes") + private List attributes; + + public Entity() { + // Required for jackson + } + + public Entity(String name, String table, List attributes) { + this.name = name; + this.table = table; + this.attributes = attributes; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Relation.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Relation.java new file mode 100644 index 0000000..9448a82 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Relation.java @@ -0,0 +1,50 @@ +package net.cloudappi.apigen.generatorcore.config.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class Relation { + @JsonProperty("type") + private RelationType type; + @JsonProperty("related_entity") + private String relatedEntity; + @JsonProperty("intermediate_table") + private String intermediateTable; + @JsonProperty("owner") + private Boolean owner = false; + @JsonProperty("columns") + private List columns = new ArrayList<>(); + @JsonProperty("reverse_columns") + private List reverseColumns = new ArrayList<>(); + + public Relation() { + } + + public Relation(String relatedEntity) { + this.relatedEntity = relatedEntity; + } + + public Relation(String relatedEntity, Boolean owner) { + this.relatedEntity = relatedEntity; + this.owner = owner; + } + + public Relation(String relatedEntity, List columns, List reverseColumns, String intermediateTable) { + this.relatedEntity = relatedEntity; + this.columns = columns; + this.reverseColumns = reverseColumns; + this.intermediateTable = intermediateTable; + } + + public Relation(String relatedEntity, List columns, List reverseColumns, String intermediateTable, Boolean owner) { + this.relatedEntity = relatedEntity; + this.columns = columns; + this.reverseColumns = reverseColumns; + this.intermediateTable = intermediateTable; + this.owner = owner; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/RelationType.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/RelationType.java new file mode 100644 index 0000000..15392e1 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/RelationType.java @@ -0,0 +1,10 @@ +package net.cloudappi.apigen.generatorcore.config.entity; + +public enum RelationType { + ONE_TO_ONE, + ONE_TO_ONE_OWNER, + ONE_TO_MANY, + MANY_TO_ONE, + MANY_TO_MANY, + MANY_TO_MANY_OWNER; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AbstractExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AbstractExtractor.java new file mode 100644 index 0000000..5cb10cd --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AbstractExtractor.java @@ -0,0 +1,11 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import net.cloudappi.apigen.generatorcore.config.extractors.context.ExtractorContext; + +public abstract class AbstractExtractor { + protected final ExtractorContext context; + + public AbstractExtractor(ExtractorContext context) { + this.context = context; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AttributesExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AttributesExtractor.java new file mode 100644 index 0000000..33ca29d --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AttributesExtractor.java @@ -0,0 +1,133 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; +import net.cloudappi.apigen.generatorcore.config.controller.Attribute; +import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; +import net.cloudappi.apigen.generatorcore.utils.CustomStringUtils; + +import java.util.*; + +import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.*; + +public class AttributesExtractor { + + private static final int MAX_DEPTH_LEVEL = 10; + + private Map schemas; + private final ValidationsExtractor validationsExtractor; + + public AttributesExtractor(Map schemas) { + this.schemas = schemas; + this.validationsExtractor = new ValidationsExtractor(); + } + + + @SuppressWarnings("unchecked") + public List getFirstLvlAttributes(Schema schema) { + List attributes = new ArrayList<>(); + for (Map.Entry property : (Set>) schema.getProperties().entrySet()) { + String propName = property.getKey(); + Schema propSchema = property.getValue(); + + Attribute attribute = new Attribute(); + attribute.setName(propName); + + boolean isCollection = propSchema instanceof ArraySchema; + attribute.setCollection(isCollection); + + if (isCollection) { + propSchema = ((ArraySchema) propSchema).getItems(); + } + + propSchema = solveSchema(propSchema); + String entityFieldName = getEntityFieldName(propSchema, propName); + attribute.setEntityFieldName(entityFieldName); + + if (!isCollection) { + attribute.setType(propSchema.getType()); + attribute.setFormat(propSchema.getFormat()); + } + + boolean isObject = Openapi2JavapoetType.TYPE_OBJECT.equalsIgnoreCase(attribute.getType()); + if (isObject || isCollection) { + String relatedEntity = getMappingEntity(propSchema); + attribute.setRelatedEntity(relatedEntity); + } + + attributes.add(attribute); + } + return attributes; + } + + public List getAttributes(Schema schema) { + return getAttributes(schema, 0); + } + + @SuppressWarnings("unchecked") + private List getAttributes(Schema schema, int level) { + + List attributes = new ArrayList<>(); + if (schema.getProperties() == null || level > MAX_DEPTH_LEVEL) return attributes; + List required = schema.getRequired(); + if (required == null) required = Collections.emptyList(); + + for (Map.Entry property : (Set>) schema.getProperties().entrySet()) { + String propName = property.getKey(); + Schema propSchema = property.getValue(); + + Attribute attribute = new Attribute(); + attribute.setName(propName); + + boolean isCollection = propSchema instanceof ArraySchema; + attribute.setCollection(isCollection); + + if (isCollection) { + attribute.setValidations(validationsExtractor.getValidations(propSchema, required.contains(propName))); + propSchema = ((ArraySchema) propSchema).getItems(); + } else { + attribute.setValidations(validationsExtractor.getValidations(propSchema, required.contains(propName))); + } + + propSchema = solveSchema(propSchema); + String entityFieldName = getEntityFieldName(propSchema, propName); + attribute.setEntityFieldName(entityFieldName); + String relatedEntity = getMappingEntity(propSchema); + attribute.setRelatedEntity(relatedEntity); + + attribute.setType(propSchema.getType()); + attribute.setFormat(propSchema.getFormat()); + + attribute.setAttributes(getAttributes(propSchema, level++)); + attributes.add(attribute); + } + return attributes; + } + + private String getMappingEntity(Schema schema) { + Map apigenExtension = getMappingExtension(schema); + if (apigenExtension == null) return null; + return (String) apigenExtension.get(MAPPING_MODEL); + } + + @SuppressWarnings("unchecked") + private Map getMappingExtension(Schema schema) { + Map extensions = schema.getExtensions(); + if (extensions == null) return null; + return (Map) extensions.get(MAPPING); + } + + private String getEntityFieldName(Schema schema, String defaultName) { + Map apigenExtension = getMappingExtension(schema); + defaultName = CustomStringUtils.snakeCaseToCamelCase(defaultName); + if (apigenExtension == null) return defaultName; + return (String) apigenExtension.getOrDefault(MAPPING_FIELD, defaultName); + } + + private Schema solveSchema(Schema schema) { + if (schema.get$ref() == null) return schema; + String schemaName = schema.get$ref(); + schemaName = schemaName.substring(schemaName.lastIndexOf('/') + 1); + return schemas.get(schemaName); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractor.java new file mode 100644 index 0000000..39e0983 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractor.java @@ -0,0 +1,56 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.config.extractors.context.ExtractorContext; +import net.cloudappi.apigen.generatorcore.exceptions.DefinitionException; +import net.cloudappi.apigen.generatorcore.exceptions.ExtractorException; +import net.cloudappi.apigen.generatorcore.spec.OpenAPIExtended; +import net.cloudappi.apigen.generatorcore.spec.components.ApigenProject; + +@Slf4j +public class ConfigurationExtractor extends AbstractExtractor { + + private final OpenAPIExtended openAPIExtended; + private final EntitiesExtractor entitiesExtractor; + private final ControllersExtractor controllersExtractor; + + public ConfigurationExtractor(OpenAPIExtended openAPIExtended) { + super(new ExtractorContext()); + this.openAPIExtended = openAPIExtended; + this.entitiesExtractor = new EntitiesExtractor(context); + this.controllersExtractor = new ControllersExtractor(openAPIExtended.getSchemas()); + } + + public Configuration extract() { + try { + Configuration configuration = extractProject(openAPIExtended.getProject()); + configuration.setEntities(entitiesExtractor.getEntities(openAPIExtended.getModels())); + configuration.setControllers( + controllersExtractor.getControllers( + openAPIExtended.getPaths(), + openAPIExtended.getPathBindings(), + configuration.getEntities() + ) + ); + context.validate(); + return configuration; + } catch (ExtractorException e) { + throw e; + } catch (Exception e) { + log.error("Error: ", e); + throw new DefinitionException(); + } + } + + private Configuration extractProject(ApigenProject project) { + Configuration configuration = new Configuration(); + configuration.setName(project.getName()); + configuration.setDescription(project.getDescription()); + configuration.setGroup(project.getJavaProperties().getGroupId()); + configuration.setArtifact(project.getJavaProperties().getArtifactId()); + configuration.setVersion(project.getVersion()); + configuration.setPartial(project.getPartial()); + return configuration; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ControllersExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ControllersExtractor.java new file mode 100644 index 0000000..4c357da --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ControllersExtractor.java @@ -0,0 +1,147 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.media.Schema; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.spec.components.ApigenBinding; +import net.cloudappi.apigen.generatorcore.utils.Mapping; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +public class ControllersExtractor { + + private final ParametersExtractor parametersExtractor; + private final RequestExtractor requestExtractor; + private final ResponseExtractor responseExtractor; + + public ControllersExtractor(Map schemas) { + this.parametersExtractor = new ParametersExtractor(); + this.requestExtractor = new RequestExtractor(schemas); + this.responseExtractor = new ResponseExtractor(schemas); + } + + public List getControllers(Map paths, Map bindings, List rawEntities) { + List controllers = getEntityControllers(paths, bindings, rawEntities); + List otherControllers = getOtherControllers(paths, bindings); + controllers.addAll(otherControllers); + return controllers; + } + + private List getEntityControllers(Map paths, Map bindings, List rawEntities) { + Map entities = rawEntities.stream().collect(Collectors.toMap(Entity::getName, e -> e)); + Map> pathsByEntity = new HashMap<>(); + for (Map.Entry path : paths.entrySet()) { + ApigenBinding binding = bindings.get(path.getValue()); + if (binding == null) continue; + Entity entity = entities.get(binding.getModel()); + if (entity == null) { + log.warn("Model not found {} , path {} ignored", binding.getModel(), path.getKey()); + } else { + String entityName = entity.getName(); + pathsByEntity.putIfAbsent(entityName, new HashMap<>()); + pathsByEntity.get(entityName).put(path.getKey(), path.getValue()); + } + } + return pathsByEntity.entrySet().stream() + .map(entry -> createController(entry.getValue(), entities.get(entry.getKey()))) + .collect(Collectors.toList()); + } + + private List getOtherControllers(Map paths, Map bindings) { + Map otherPaths = new HashMap<>(); + for (Map.Entry path : paths.entrySet()) { + ApigenBinding binding = bindings.get(path.getValue()); + if (binding == null) { + otherPaths.put(path.getKey(), path.getValue()); + } + } + return group(otherPaths).stream() + .map(this::createController) + .collect(Collectors.toList()); + } + + private List> group(Map paths) { + Map> groups = new HashMap<>(); + paths.forEach((k, v) -> { + String key = k.split("/")[1]; + if (!groups.containsKey(key)) { + groups.put(key, new HashMap<>()); + } + groups.get(key).put(k, v); + }); + return new ArrayList<>(groups.values()); + } + + private Controller createController(Map paths) { + return createController(paths, null); + } + + private Controller createController(Map paths, Entity entity) { + String entityName = entity == null ? null : entity.getName(); + Controller controller = new Controller(); + controller.setEntity(entityName); + String requestMapping = paths.keySet().iterator().next(); + requestMapping = requestMapping.split("/")[1]; + controller.setMapping(requestMapping); + List endpoints = getEndpoints(paths); + endpoints.forEach(e -> e.setRelatedEntity(entityName)); + controller.setEndpoints(endpoints); + return controller; + } + + private List getEndpoints(Map paths) { + List endpoints = new ArrayList<>(); + for (Map.Entry pathData : paths.entrySet()) { + endpoints.addAll(getEndpoints(pathData.getKey(), pathData.getValue())); + } + return endpoints; + } + + private List getEndpoints(String pathMapping, PathItem pathItem) { + List endpoints = new ArrayList<>(); + String[] pathParts = pathMapping.split("/", 3); + String idPathPart = isCollectionPath(pathParts) ? null : pathParts[2]; + if (pathItem.getGet() != null) + endpoints.add(getEndpoint(pathItem.getGet(), Endpoint.Method.GET, idPathPart)); + if (pathItem.getPost() != null) + endpoints.add(getEndpoint(pathItem.getPost(), Endpoint.Method.POST, idPathPart)); + if (pathItem.getPut() != null) + endpoints.add(getEndpoint(pathItem.getPut(), Endpoint.Method.PUT, idPathPart)); + if (pathItem.getDelete() != null) + endpoints.add(getEndpoint(pathItem.getDelete(), Endpoint.Method.DELETE, idPathPart)); + return endpoints; + } + + private boolean isCollectionPath(String[] pathParts) { + return pathParts.length == 2; + } + + private Endpoint getEndpoint( + Operation operation, + Endpoint.Method method, + String pathPart + ) { + Endpoint endpoint = new Endpoint(); + endpoint.setMethod(method); + endpoint.setName(operation.getOperationId()); + endpoint.setMapping(pathPart); + Mapping mapping = new Mapping(pathPart); + endpoint.setParameters(parametersExtractor.readParameters(operation.getParameters())); + if (!mapping.isSearch()) { + endpoint.setRequest(requestExtractor.getRequest(operation)); + } + endpoint.setResponse(responseExtractor.getResponse(operation)); + return endpoint; + } + + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/EntitiesExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/EntitiesExtractor.java new file mode 100644 index 0000000..9e4f1f7 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/EntitiesExtractor.java @@ -0,0 +1,194 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import net.cloudappi.apigen.generatorcore.config.entity.Attribute; +import net.cloudappi.apigen.generatorcore.config.entity.Column; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.Relation; +import net.cloudappi.apigen.generatorcore.config.extractors.context.ExtractorContext; +import net.cloudappi.apigen.generatorcore.spec.components.ApigenModel; + +import java.util.*; +import java.util.stream.Collectors; + +import static net.cloudappi.apigen.generatorcore.exceptions.GeneratorErrors.*; +import static net.cloudappi.apigen.generatorcore.generator.common.ApigenExt2JavapoetType.isComposedID; + +public class EntitiesExtractor extends AbstractExtractor { + + private final ValidationsExtractor validationsExtractor; + + public EntitiesExtractor(ExtractorContext context) { + super(context); + this.validationsExtractor = new ValidationsExtractor(); + } + + public List getEntities(Map models) { + ArrayList entities = new ArrayList<>(); + if (models != null) { + models.forEach((key, value) -> { + Entity entity = toEntity(key, value); + entities.add(entity); + }); + } + return entities; + } + + private Entity toEntity(String modelName, ApigenModel model) { + Entity entity = new Entity(); + entity.setName(modelName); + if (model.getRelationalPersistence() == null) { + context.registerError(MODEL_WITHOUT_RELATIONAL_PERSISTENCE, modelName); + } else { + String table = model.getRelationalPersistence().getTable(); + if (table != null) { + entity.setTable(table); + } + + if (model.getAttributes() == null) { + context.registerError(MODEL_WITHOUT_ATTRIBUTES, modelName); + } else { + entity.setAttributes(model.getAttributes().stream().map(a -> toAttribute(modelName, a)).collect(Collectors.toList())); + } + } + return entity; + } + + private Attribute toAttribute(String modelName, ApigenModel.ApigenModelAttribute modelAttribute) { + + Attribute attribute = new Attribute(); + + String attributeName = modelAttribute.getName(); + if (attributeName == null) { + context.registerError(MODEL_ATTRIBUTE_WITHOUT_NAME, modelName); + } else { + attribute.setName(attributeName); + } + + String type = modelAttribute.getItemsType() == null ? + modelAttribute.getType() : modelAttribute.getItemsType(); + if (type == null) { + context.registerError(MODEL_ATTRIBUTE_WITHOUT_TYPE, modelName, attributeName); + } else { + attribute.setType(type); + } + + ApigenModel.AttributeRelationalPersistence relationalPersistence = modelAttribute.getRelationalPersistence(); + if (relationalPersistence == null) { + context.registerError(MODEL_ATTRIBUTE_WITHOUT_RELATIONAL_PERSISTENCE, modelName, attributeName); + } else { + setColumns(attribute, relationalPersistence); + setForeignColumns(attribute, relationalPersistence); + } + + attribute.setIsCollection(false); + setAttributeRelation(attribute, modelAttribute.getType(), modelAttribute); + attribute.setValidations(validationsExtractor.getValidations(modelAttribute)); + attribute.setAttributes(modelAttribute.getAttributes().stream().map(a -> toAttribute(attributeName, a)).collect(Collectors.toList())); + + return attribute; + } + + private void setColumns(Attribute attribute, ApigenModel.AttributeRelationalPersistence relationalPersistence) { + attribute.setColumns(getColumns(relationalPersistence)); + } + + private List getColumns(ApigenModel.AttributeRelationalPersistence relationalPersistence) { + List columns = new ArrayList<>(); + if (relationalPersistence.getColumns() != null) { + for (Map.Entry col : relationalPersistence.getColumns().entrySet()) { + Column column = new Column(); + column.setName(col.getKey()); + column.setReferenceColumn(col.getValue()); + column.setPrimaryKey(false); + columns.add(column); + } + } else { + String columnName = relationalPersistence.getColumn(); + Boolean pk = relationalPersistence.isPrimaryKey(); + Boolean autogenerated = relationalPersistence.isAutogenerated(); + String sequence = relationalPersistence.getSequence(); + if (columnName != null || pk) { + Column column = new Column(); + column.setName(columnName); + column.setPrimaryKey(pk); + column.setAutogenerated(autogenerated); + column.setSequence(sequence); + columns.add(column); + } + } + return columns; + } + + private void setForeignColumns(Attribute attribute, ApigenModel.AttributeRelationalPersistence relationalPersistence) { + attribute.setForeignColumns(getForeignColumns(relationalPersistence)); + } + + private List getForeignColumns(ApigenModel.AttributeRelationalPersistence relationalPersistence) { + List foreignColumns = new ArrayList<>(); + String foreignColumnName = relationalPersistence.getForeignColumn(); + if (foreignColumnName != null) { + Column foreignColumn = new Column(); + foreignColumn.setName(foreignColumnName); + foreignColumns.add(foreignColumn); + } else if (relationalPersistence.getForeignColumns() != null) { + for (Map.Entry col : relationalPersistence.getForeignColumns().entrySet()) { + Column column = new Column(); + column.setName(col.getKey()); + column.setReferenceColumn(col.getValue()); + foreignColumns.add(column); + } + } + return foreignColumns; + } + + private void setAttributeRelation(Attribute attribute, String type, ApigenModel.ApigenModelAttribute modelAttribute) { + if (!isRelation(type)) return; + Relation relation = new Relation(); + relation.setColumns(new ArrayList<>()); + relation.setReverseColumns(new ArrayList<>()); + + if (isCollectionType(type)) { + String itemsType = modelAttribute.getItemsType(); + attribute.setIsCollection(true); + relation.setRelatedEntity(itemsType); + setIntermediateTable(attribute, relation, modelAttribute); + } else { + relation.setRelatedEntity(type); + } + attribute.setRelation(relation); + } + + private void setIntermediateTable(Attribute attribute, Relation relation, ApigenModel.ApigenModelAttribute modelAttribute) { + ApigenModel.AttributeRelationalPersistence relationalPersistence = modelAttribute.getRelationalPersistence(); + if (relationalPersistence == null) { + context.registerError(MODEL_ATTRIBUTE_WITHOUT_RELATIONAL_PERSISTENCE, attribute.getName()); + return; + } + String intermediateTable = relationalPersistence.getIntermediateTable(); + if (intermediateTable != null) { + + attribute.setType(relation.getRelatedEntity()); + attribute.setColumns(new ArrayList<>()); + attribute.setForeignColumns(new ArrayList<>()); + + relation.setIntermediateTable(intermediateTable); + relation.setOwner(relationalPersistence.isOwner()); + + relation.setColumns(getColumns(relationalPersistence)); + relation.setReverseColumns(getForeignColumns(relationalPersistence)); + } + } + + private boolean isRelation(String type) { + return (isCollectionType(type) || !(isBasicType(type) || isComposedID(type))); + } + + private boolean isCollectionType(String type) { + return "Array".equals(type); + } + + private boolean isBasicType(String type) { + Set basic = new HashSet<>(Arrays.asList("Boolean", "String", "Long", "Integer", "Float", "Double", "LocalDate", "OffsetDateTime")); + return basic.contains(type); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ParametersExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ParametersExtractor.java new file mode 100644 index 0000000..5207b91 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ParametersExtractor.java @@ -0,0 +1,44 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; + +import java.util.ArrayList; +import java.util.List; + +public class ParametersExtractor { + + private final ValidationsExtractor validationsExtractor; + + public ParametersExtractor() { + this.validationsExtractor = new ValidationsExtractor(); + } + + public List readParameters(List parameters) { + List params = new ArrayList<>(); + if (parameters == null) return params; + for (io.swagger.v3.oas.models.parameters.Parameter parameter : parameters) { + Parameter param = new Parameter(); + param.setName(parameter.getName()); + Schema itemSchema = parameter.getSchema(); + + param.setDefaultValue(itemSchema.getDefault()); + param.setValidations(validationsExtractor.getValidations(itemSchema, false)); + + boolean isCollection = itemSchema instanceof ArraySchema; + param.setCollection(isCollection); + if (isCollection) itemSchema = ((ArraySchema) itemSchema).getItems(); + + Boolean required = parameter.getRequired(); + if (required == null) required = false; + param.setRequired(required); + + param.setType(itemSchema.getType()); + param.setFormat(itemSchema.getFormat()); + param.setIn(parameter.getIn()); + params.add(param); + } + return params; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/RequestExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/RequestExtractor.java new file mode 100644 index 0000000..8e77571 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/RequestExtractor.java @@ -0,0 +1,43 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.parameters.RequestBody; +import net.cloudappi.apigen.generatorcore.config.controller.Request; + +import java.util.Map; + +import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.MAPPING; +import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.MAPPING_MODEL; + +public class RequestExtractor { + + private final AttributesExtractor attributesExtractor; + + public RequestExtractor(Map schemas) { + this.attributesExtractor = new AttributesExtractor(schemas); + } + + public Request getRequest(Operation operation) { + RequestBody requestBody = operation.getRequestBody(); + if (requestBody == null || requestBody.getContent().get("application/json") == null) return null; + Schema bodySchema = requestBody.getContent().get("application/json").getSchema(); + Request request = new Request(); + request.setAttributes(attributesExtractor.getAttributes(bodySchema)); + request.setRelatedEntity(getMappingEntity(bodySchema)); + return request; + } + + private String getMappingEntity(Schema schema) { + Map apigenExtension = getMappingExtension(schema); + if (apigenExtension == null) return null; + return (String) apigenExtension.get(MAPPING_MODEL); + } + + @SuppressWarnings("unchecked") + private Map getMappingExtension(Schema schema) { + Map extensions = schema.getExtensions(); + if (extensions == null) return null; + return (Map) extensions.get(MAPPING); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ResponseExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ResponseExtractor.java new file mode 100644 index 0000000..f0168d2 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ResponseExtractor.java @@ -0,0 +1,99 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import io.swagger.v3.oas.models.Operation; +import io.swagger.v3.oas.models.media.ArraySchema; +import io.swagger.v3.oas.models.media.Schema; +import io.swagger.v3.oas.models.responses.ApiResponse; +import net.cloudappi.apigen.generatorcore.config.controller.Response; + +import java.util.Map; + +import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.MAPPING; +import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.MAPPING_MODEL; + +public class ResponseExtractor { + + private final AttributesExtractor attributesExtractor; + + public ResponseExtractor(Map schemas) { + this.attributesExtractor = new AttributesExtractor(schemas); + } + + public Response getResponse(Operation operation) { + int status = operation.getResponses().keySet().stream() + .filter(s -> !"default".equals(s)).mapToInt(Integer::parseInt) + .filter(n -> n >= 200 && n <= 400) + .sorted() + .findFirst().orElse(0); + ApiResponse response = operation.getResponses().get(status == 0 ? "default" : String.valueOf(status)); + + Response endpointResponse = new Response(); + endpointResponse.setDefaultStatusCode(status == 0 ? 200 : status); + endpointResponse.setIsStandard(false); + + if (response == null || response.getContent() == null || response.getContent().get("application/json") == null) { + return endpointResponse; + } + + Schema schema = response.getContent().get("application/json").getSchema(); + + if (isStandardResponse(schema)) { + endpointResponse.setIsStandard(true); + Schema dataSchema = getDataSchema(schema); + boolean isCollection = isNamedCollectionSchema(dataSchema); + endpointResponse.setIsCollection(isCollection); + if (isCollection) { + String collectionName = getNamedCollectionName(dataSchema); + endpointResponse.setCollectionName(collectionName); + dataSchema = ((ArraySchema) dataSchema.getProperties().get(collectionName)).getItems(); + } + String relatedEntity = getMappingEntity(dataSchema); + if (relatedEntity == null) { + endpointResponse.setAttributes(attributesExtractor.getAttributes(dataSchema)); + } else { + endpointResponse.setRelatedEntity(relatedEntity); + endpointResponse.setAttributes(attributesExtractor.getFirstLvlAttributes(dataSchema)); + } + } else { + if (schema instanceof ArraySchema) { + endpointResponse.setIsCollection(true); + schema = ((ArraySchema) schema).getItems(); + } else { + endpointResponse.setIsCollection(false); + } + endpointResponse.setAttributes(attributesExtractor.getAttributes(schema)); + } + return endpointResponse; + } + + private boolean isStandardResponse(Schema schema) { + return schema.getProperties() != null && schema.getProperties().containsKey("data") && schema.getProperties().containsKey("result"); + } + + private Schema getDataSchema(Schema schema) { + return (Schema) schema.getProperties().get("data"); + } + + private boolean isNamedCollectionSchema(Schema dataSchema) { + if (dataSchema.getProperties().size() != 1) return false; + String key = getNamedCollectionName(dataSchema); + return dataSchema.getProperties().get(key) instanceof ArraySchema; + } + + private String getNamedCollectionName(Schema dataSchema) { + return (String) dataSchema.getProperties().keySet().iterator().next(); + } + + private String getMappingEntity(Schema schema) { + Map apigenExtension = getMappingExtension(schema); + if (apigenExtension == null) return null; + return (String) apigenExtension.get(MAPPING_MODEL); + } + + @SuppressWarnings("unchecked") + private Map getMappingExtension(Schema schema) { + Map extensions = schema.getExtensions(); + if (extensions == null) return null; + return (Map) extensions.get(MAPPING); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ValidationsExtractor.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ValidationsExtractor.java new file mode 100644 index 0000000..8f2142d --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ValidationsExtractor.java @@ -0,0 +1,133 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import io.swagger.v3.oas.models.media.Schema; +import net.cloudappi.apigen.generatorcore.config.validation.Validation; +import net.cloudappi.apigen.generatorcore.config.validation.ValidationType; +import net.cloudappi.apigen.generatorcore.spec.components.ApigenModel; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; + +public class ValidationsExtractor { + + public List getValidations(ApigenModel.ApigenModelAttribute modelAttribute) { + return modelAttribute.getValidations().stream().map(this::toValidation).collect(Collectors.toList()); + } + + private Validation toValidation(ApigenModel.AttributeValidation modelValidation) { + String type = modelValidation.getType(); + switch (type) { + case "NotNull": + return new Validation(ValidationType.NOT_NULL); + case "Size": + return new Validation(ValidationType.SIZE, modelValidation.getMin(), modelValidation.getMax()); + case "Min": + return new Validation(ValidationType.MIN, Long.parseLong(modelValidation.getValue())); + case "Max": + return new Validation(ValidationType.MAX, Long.parseLong(modelValidation.getValue())); + case "Email": + return new Validation(ValidationType.EMAIL); + case "NotEmpty": + return new Validation(ValidationType.NOT_EMPTY); + case "NotBlank": + return new Validation(ValidationType.NOT_BLANK); + case "Positive": + return new Validation(ValidationType.POSITIVE); + case "PositiveOrZero": + return new Validation(ValidationType.POSITIVE_OR_ZERO); + case "Negative": + return new Validation(ValidationType.NEGATIVE); + case "NegativeOrZero": + return new Validation(ValidationType.NEGATIVE_OR_ZERO); + case "Past": + return new Validation(ValidationType.PAST); + case "PastOrPresent": + return new Validation(ValidationType.PAST_OR_PRESENT); + case "Future": + return new Validation(ValidationType.FUTURE); + case "FutureOrPresent": + return new Validation(ValidationType.FUTURE_OR_PRESENT); + case "Pattern": + return new Validation(ValidationType.PATTERN, modelValidation.getRegex()); + case "Digits": + return new Validation(ValidationType.DIGITS, modelValidation.getInteger(), modelValidation.getFraction()); + case "DecimalMin": + return new Validation(ValidationType.DECIMAL_MIN, new BigDecimal(modelValidation.getValue()), modelValidation.isInclusive()); + case "DecimalMax": + return new Validation(ValidationType.DECIMAL_MAX, new BigDecimal(modelValidation.getValue()), modelValidation.isInclusive()); + default: + throw new IllegalArgumentException("Model validation type " + type + " not supported"); + } + } + + public List getValidations(Schema schema, boolean required) { + List validations = new ArrayList<>(); + addRequiredValidation(required, validations); + addNumberValidations(schema, validations); + addSizeValidation(schema, validations); + addPatternValidation(schema, validations); + addEmailValidation(schema, validations); + return validations; + } + + private void addRequiredValidation(boolean required, List validations) { + if (!required) return; + validations.add(new Validation(ValidationType.NOT_NULL)); + } + + private void addNumberValidations(Schema schema, List validations) { + boolean isDecimalNumber = "number".equalsIgnoreCase(schema.getType()); + addNumberMinimumValidations(schema, isDecimalNumber, validations); + addNumberMaximumValidations(schema, isDecimalNumber, validations); + } + + private void addNumberMinimumValidations(Schema schema, boolean isDecimal, List validations) { + if (isNull(schema.getMinimum())) return; + BigDecimal minimum = schema.getMinimum(); + boolean inclusive = schema.getExclusiveMinimum() == null || !schema.getExclusiveMinimum(); + if (isDecimal) { + validations.add(new Validation(ValidationType.DECIMAL_MIN, minimum, inclusive)); + } else { + long minimumLong = minimum.longValue(); + if (!inclusive) minimumLong += 1; + validations.add(new Validation(ValidationType.MIN, minimumLong)); + } + } + + private void addNumberMaximumValidations(Schema schema, boolean isDecimal, List validations) { + if (isNull(schema.getMaximum())) return; + BigDecimal maximum = schema.getMaximum(); + boolean inclusive = schema.getExclusiveMaximum() == null || !schema.getExclusiveMaximum(); + if (isDecimal) { + validations.add(new Validation(ValidationType.DECIMAL_MAX, maximum, inclusive)); + } else { + long maximumLong = maximum.longValue(); + if (!inclusive) maximumLong -= 1; + validations.add(new Validation(ValidationType.MAX, maximumLong)); + } + } + + private void addSizeValidation(Schema schema, List validations) { + if (nonNull(schema.getMinLength()) || nonNull(schema.getMaxLength())) { + validations.add(new Validation(ValidationType.SIZE, schema.getMinLength(), schema.getMaxLength())); + } + if (nonNull(schema.getMinItems()) || nonNull(schema.getMaxItems())) { + validations.add(new Validation(ValidationType.SIZE, schema.getMinItems(), schema.getMaxItems())); + } + } + + private void addPatternValidation(Schema schema, List validations) { + if (isNull(schema.getPattern())) return; + validations.add(new Validation(ValidationType.PATTERN, schema.getPattern())); + } + + private void addEmailValidation(Schema schema, List validations) { + if (!"string".equals(schema.getType()) || !"email".equals(schema.getFormat())) return; + validations.add(new Validation(ValidationType.EMAIL)); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/context/ExtractorContext.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/context/ExtractorContext.java new file mode 100644 index 0000000..a54765f --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/context/ExtractorContext.java @@ -0,0 +1,39 @@ +package net.cloudappi.apigen.generatorcore.config.extractors.context; + +import lombok.AllArgsConstructor; +import net.cloudappi.apigen.generatorcore.exceptions.ExtractorException; +import net.cloudappi.apigen.generatorcore.exceptions.GeneratorErrors; + +import java.util.ArrayList; +import java.util.List; + +public class ExtractorContext { + + private List errors = new ArrayList<>(); + + public void registerError(GeneratorErrors generatorError, String... data) { + errors.add(new ExtractorError(generatorError, data)); + } + + public void validate() { + if (!errors.isEmpty()) throw new ExtractorException(errors); + } + + @AllArgsConstructor + public static class ExtractorError { + private GeneratorErrors error; + private String[] data; + + public int getCode() { + return error.code; + } + + public String getMessage() { + return String.format(error.message, data); + } + + public String getReference() { + return String.join(".", data); + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/Validation.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/Validation.java new file mode 100644 index 0000000..b5670d0 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/Validation.java @@ -0,0 +1,64 @@ +package net.cloudappi.apigen.generatorcore.config.validation; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.ParameterSpec; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class Validation { + @JsonProperty("type") + private ValidationType type; + @JsonProperty("integer_value_one") + private Integer integerValueOne; + @JsonProperty("integer_value_two") + private Integer integerValueTwo; + @JsonProperty("decimal_value") + private BigDecimal decimalValue; + @JsonProperty("long_value") + private Long longValue; + @JsonProperty("string_value") + private String stringValue; + @JsonProperty("inclusive") + private boolean inclusive; + + private Validation() { + // Required for jackson + } + + public Validation(ValidationType type) { + this.type = type; + } + + public Validation(ValidationType type, String value) { + this.type = type; + this.stringValue = value; + } + + public Validation(ValidationType type, Integer integerValueOne, Integer integerValueTwo) { + this.type = type; + this.integerValueOne = integerValueOne; + this.integerValueTwo = integerValueTwo; + } + + public Validation(ValidationType type, BigDecimal value, boolean inclusive) { + this.type = type; + this.decimalValue = value; + this.inclusive = inclusive; + } + + public Validation(ValidationType type, Long value) { + this.type = type; + this.longValue = value; + } + + public void apply(FieldSpec.Builder builder) { + type.apply(this, builder); + } + + public void apply(ParameterSpec.Builder builder) { + type.apply(this, builder); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/ValidationType.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/ValidationType.java new file mode 100644 index 0000000..f0e755a --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/ValidationType.java @@ -0,0 +1,157 @@ +package net.cloudappi.apigen.generatorcore.config.validation; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.ParameterSpec; +import net.cloudappi.apigen.generatorcore.generator.common.Members; + +import javax.validation.constraints.*; +import java.math.BigDecimal; + +import static java.util.Objects.nonNull; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; + +public enum ValidationType { + + NOT_NULL(NotNull.class), + EMAIL(Email.class), + NOT_EMPTY(NotEmpty.class), + NOT_BLANK(NotBlank.class), + POSITIVE(Positive.class), + POSITIVE_OR_ZERO(PositiveOrZero.class), + NEGATIVE(Negative.class), + NEGATIVE_OR_ZERO(NegativeOrZero.class), + PAST(Past.class), + PAST_OR_PRESENT(PastOrPresent.class), + FUTURE(Future.class), + FUTURE_OR_PRESENT(FutureOrPresent.class), + MIN(Min.class) { + @Override + public void apply(Validation validation, FieldSpec.Builder builder) { + builder.addAnnotation(getLongAnnotation(this.enumClass, validation.getLongValue())); + } + + @Override + public void apply(Validation validation, ParameterSpec.Builder builder) { + builder.addAnnotation(getLongAnnotation(this.enumClass, validation.getLongValue())); + } + }, + MAX(Max.class) { + @Override + public void apply(Validation validation, FieldSpec.Builder builder) { + builder.addAnnotation(getLongAnnotation(this.enumClass, validation.getLongValue())); + } + + @Override + public void apply(Validation validation, ParameterSpec.Builder builder) { + builder.addAnnotation(getLongAnnotation(this.enumClass, validation.getLongValue())); + } + }, + DECIMAL_MIN(DecimalMin.class) { + @Override + public void apply(Validation validation, FieldSpec.Builder builder) { + builder.addAnnotation(getDecimalAnnotation(this.enumClass, validation.getDecimalValue(), validation.isInclusive())); + } + + @Override + public void apply(Validation validation, ParameterSpec.Builder builder) { + builder.addAnnotation(getDecimalAnnotation(this.enumClass, validation.getDecimalValue(), validation.isInclusive())); + } + }, + DECIMAL_MAX(DecimalMax.class) { + @Override + public void apply(Validation validation, FieldSpec.Builder builder) { + builder.addAnnotation(getDecimalAnnotation(this.enumClass, validation.getDecimalValue(), validation.isInclusive())); + } + + @Override + public void apply(Validation validation, ParameterSpec.Builder builder) { + builder.addAnnotation(getDecimalAnnotation(this.enumClass, validation.getDecimalValue(), validation.isInclusive())); + } + }, + SIZE(Size.class) { + @Override + public void apply(Validation validation, FieldSpec.Builder builder) { + builder.addAnnotation(getSizeAnnotation(validation.getIntegerValueOne(), validation.getIntegerValueTwo())); + } + + @Override + public void apply(Validation validation, ParameterSpec.Builder builder) { + builder.addAnnotation(getSizeAnnotation(validation.getIntegerValueOne(), validation.getIntegerValueTwo())); + } + }, + DIGITS(Digits.class) { + @Override + public void apply(Validation validation, FieldSpec.Builder builder) { + builder.addAnnotation(getDigitsAnnotation(validation.getIntegerValueOne(), validation.getIntegerValueTwo())); + } + + @Override + public void apply(Validation validation, ParameterSpec.Builder builder) { + builder.addAnnotation(getDigitsAnnotation(validation.getIntegerValueOne(), validation.getIntegerValueTwo())); + } + }, + PATTERN(Pattern.class) { + @Override + public void apply(Validation validation, FieldSpec.Builder builder) { + builder.addAnnotation(getPatternAnnotation(validation.getStringValue())); + } + + @Override + public void apply(Validation validation, ParameterSpec.Builder builder) { + builder.addAnnotation(getPatternAnnotation(validation.getStringValue())); + } + }; + + protected Class enumClass; + + ValidationType(Class enumClass) { + this.enumClass = enumClass; + } + + private static AnnotationSpec getDefaultAnnotation(Class enumClass, String value) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(enumClass); + if (value != null) builder.addMember(Members.VALUE, value); + return builder.build(); + } + + private static AnnotationSpec getLongAnnotation(Class enumClass, Long value) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(enumClass); + if (value != null) builder.addMember(Members.VALUE, LITERAL, value); + return builder.build(); + } + + private static AnnotationSpec getDecimalAnnotation(Class enumClass, BigDecimal value, boolean inclusive) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(enumClass); + if (value != null) + builder.addMember(Members.VALUE, STRING, value.toString()).addMember(Members.INCLUSIVE, LITERAL, inclusive); + return builder.build(); + } + + private static AnnotationSpec getSizeAnnotation(Integer min, Integer max) { + AnnotationSpec.Builder annotationSpec = AnnotationSpec.builder(Size.class); + if (nonNull(min)) annotationSpec.addMember(Members.MIN, LITERAL, min); + if (nonNull(max)) annotationSpec.addMember(Members.MAX, LITERAL, max); + return annotationSpec.build(); + } + + private static AnnotationSpec getDigitsAnnotation(int integer, int fraction) { + return AnnotationSpec.builder(Digits.class) + .addMember(Members.INTEGER, LITERAL, integer) + .addMember(Members.FRACTION, LITERAL, fraction) + .build(); + } + + private static AnnotationSpec getPatternAnnotation(String regex) { + return AnnotationSpec.builder(Pattern.class).addMember(Members.REGEXP, STRING, regex).build(); + } + + public void apply(Validation validation, FieldSpec.Builder builder) { + builder.addAnnotation(getDefaultAnnotation(enumClass, validation.getStringValue())); + } + + public void apply(Validation validation, ParameterSpec.Builder builder) { + builder.addAnnotation(getDefaultAnnotation(enumClass, validation.getStringValue())); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/DefinitionException.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/DefinitionException.java new file mode 100644 index 0000000..7cc3c37 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/DefinitionException.java @@ -0,0 +1,4 @@ +package net.cloudappi.apigen.generatorcore.exceptions; + +public class DefinitionException extends RuntimeException { +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/ExtractorException.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/ExtractorException.java new file mode 100644 index 0000000..91305ce --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/ExtractorException.java @@ -0,0 +1,15 @@ +package net.cloudappi.apigen.generatorcore.exceptions; + +import lombok.Getter; +import net.cloudappi.apigen.generatorcore.config.extractors.context.ExtractorContext; + +import java.util.List; + +@Getter +public class ExtractorException extends RuntimeException { + private final List errors; + + public ExtractorException(List errors) { + this.errors = errors; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/GeneratorErrors.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/GeneratorErrors.java new file mode 100644 index 0000000..aca708c --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/GeneratorErrors.java @@ -0,0 +1,21 @@ +package net.cloudappi.apigen.generatorcore.exceptions; + +public enum GeneratorErrors { + MODEL_WITHOUT_RELATIONAL_PERSISTENCE(1, "Model %s without relational-persistence"), + MODEL_WITHOUT_ATTRIBUTES(2, "Model %s without attributes"), + MODEL_RELATIONAL_PERSISTENCE_WITHOUT_TABLE(3, "Model %s without table in relational-persistence"), + MODEL_ATTRIBUTE_WITHOUT_NAME(4, "Model %s with attribute without name"), + MODEL_ATTRIBUTE_WITHOUT_TYPE(5, "Model %s attribute %s without type"), + MODEL_ATTRIBUTE_WITHOUT_RELATIONAL_PERSISTENCE(6, "Model attribute %s without relational-persistence"), + EXTRACTOR_ERROR(99, "Extractor error"), + CONFIGURATION_NOT_VALID(100, "Configuration not valid") + ; + + public final int code; + public final String message; + + GeneratorErrors(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/InvalidValuesException.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/InvalidValuesException.java new file mode 100644 index 0000000..d690b38 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/InvalidValuesException.java @@ -0,0 +1,15 @@ +package net.cloudappi.apigen.generatorcore.exceptions; + +import lombok.AllArgsConstructor; + +import javax.validation.ConstraintViolation; +import java.util.Set; + +@AllArgsConstructor +public class InvalidValuesException extends RuntimeException { + private transient Set> violations; + + public Set> getViolations() { + return violations; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/ApigenProjectGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/ApigenProjectGenerator.java new file mode 100644 index 0000000..8ea3390 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/ApigenProjectGenerator.java @@ -0,0 +1,138 @@ +package net.cloudappi.apigen.generatorcore.generator; + +import io.swagger.parser.OpenAPIParser; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.parser.core.models.ParseOptions; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.config.extractors.ConfigurationExtractor; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.generator.base.PomGenerator; +import net.cloudappi.apigen.generatorcore.generator.base.ProjectStructureGenerator; +import net.cloudappi.apigen.generatorcore.generator.base.SpringBootBaseGenerator; +import net.cloudappi.apigen.generatorcore.generator.common.Validator; +import net.cloudappi.apigen.generatorcore.generator.mapper.MappersGenerator; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesGenerator; +import net.cloudappi.apigen.generatorcore.generator.persistence.repository.RepositoriesGenerator; +import net.cloudappi.apigen.generatorcore.generator.service.RelationManagersGenerator; +import net.cloudappi.apigen.generatorcore.generator.service.ServicesGenerator; +import net.cloudappi.apigen.generatorcore.generator.web.controller.ControllersGenerator; +import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourcesData; +import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourcesGenerator; +import net.cloudappi.apigen.generatorcore.generator.web.response.ResponsesGenerator; +import net.cloudappi.apigen.generatorcore.spec.OpenAPIExtended; +import net.cloudappi.apigen.generatorcore.utils.ZipUtils; +import org.apache.commons.io.FileUtils; +import org.springframework.core.io.ByteArrayResource; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + + +@Slf4j +public class ApigenProjectGenerator { + + private static final String JAVA_DIR = "src/main/java"; + + private final String parentGroup; + private final String parentArtifact; + private final String parentVersion; + + public ApigenProjectGenerator(String parentGroup, String parentArtifact, String parentVersion) { + this.parentGroup = parentGroup; + this.parentArtifact = parentArtifact; + this.parentVersion = parentVersion; + } + + public Configuration getConfiguration(Path location) { + return getConfiguration(location.toString()); + } + + public Configuration getConfiguration(String location) { + OpenAPI openAPI = new OpenAPIParser().readLocation(location, null, getParseOptions()).getOpenAPI(); + return getConfiguration(openAPI); + } + + public Configuration getConfiguration(byte[] file) { + OpenAPI openAPI = new OpenAPIParser().readContents(new String(file), null, getParseOptions()).getOpenAPI(); + return getConfiguration(openAPI); + } + + private Configuration getConfiguration(OpenAPI openAPI) { + return new ConfigurationExtractor(new OpenAPIExtended(openAPI)).extract(); + } + + private ParseOptions getParseOptions() { + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolveFully(true); + return parseOptions; + } + + public Project generate(Path location) throws IOException { + return generate(location.toString()); + } + + public Project generate(String location) throws IOException { + return generate(getConfiguration(location)); + } + + public Project generate(byte[] file) throws IOException { + return generate(getConfiguration(file)); + } + + public Project generate(Configuration config) throws IOException { + + Validator.validate(config); + + ByteArrayResource response; + File projectFolder = null; + try { + projectFolder = ProjectStructureGenerator.generate(config); + + boolean partial = config.getPartial(); + generateCode(projectFolder, config.getBasePackage(), config.getEntities(), config.getControllers()); + if (!partial) { + generateCoreCode(projectFolder, config); + } + + Path zipPath = ZipUtils.zip(Paths.get(projectFolder.toURI())); + response = new ByteArrayResource(Files.readAllBytes(zipPath)); + Files.delete(zipPath); + + return new Project(config.getName() + ".zip", response); + } finally { + if (projectFolder != null) FileUtils.deleteDirectory(projectFolder); + } + } + + private void generateCode(File projectFolder, String basePackage, List entities, List controllers) { + Path filesRootPath = Paths.get(projectFolder.getPath(), JAVA_DIR); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(entities, basePackage); + EntitiesData entitiesData = entitiesGenerator.getEntitiesData(); + entitiesGenerator.generate(filesRootPath); + + new RepositoriesGenerator(entities, entitiesData, basePackage).generate(filesRootPath); + new ServicesGenerator(entities, entitiesData, basePackage).generate(filesRootPath); + new RelationManagersGenerator(entities, entitiesData, basePackage).generate(filesRootPath); + + ResourcesGenerator resourcesGenerator = new ResourcesGenerator(controllers, basePackage); + ResourcesData resourcesData = resourcesGenerator.getResourcesData(); + resourcesGenerator.generate(filesRootPath); + + new MappersGenerator(entities, entitiesData, resourcesData, basePackage).generate(filesRootPath); + new ResponsesGenerator(controllers, basePackage).generate(filesRootPath); + new ControllersGenerator(controllers, entitiesData, basePackage).generate(filesRootPath); + } + + private void generateCoreCode(File projectFolder, Configuration config) throws IOException { + PomGenerator.generate(config, parentGroup, parentArtifact, parentVersion, projectFolder); + SpringBootBaseGenerator.generate(config, projectFolder); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/Project.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/Project.java new file mode 100644 index 0000000..97d009d --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/Project.java @@ -0,0 +1,12 @@ +package net.cloudappi.apigen.generatorcore.generator; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.core.io.ByteArrayResource; + +@Getter +@AllArgsConstructor +public class Project { + String name; + ByteArrayResource content; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationBuilder.java new file mode 100644 index 0000000..8b32fcb --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationBuilder.java @@ -0,0 +1,65 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import com.squareup.javapoet.*; +import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenApplication; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.ApplicationPidFileWriter; + +import javax.annotation.PostConstruct; +import javax.lang.model.element.Modifier; +import java.util.TimeZone; + +public class ApplicationBuilder extends AbstractClassBuilder { + + private String basePackage; + + public ApplicationBuilder(String basePackage) { + this.basePackage = basePackage; + } + + @Override + public String getPackage() { + return basePackage; + } + + @Override + protected void initialize() { + initializeBuilder(); + addMainMethod(); + initializeTimezone(); + } + + private void initializeBuilder() { + builder = TypeSpec + .classBuilder("Application") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(ApigenApplication.class) + .addAnnotation(SpringBootApplication.class); + } + + private void addMainMethod() { + MethodSpec methodSpec = MethodSpec + .methodBuilder("main") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(ParameterSpec.builder(TypeName.get(String[].class), "args").build()) + .returns(TypeName.VOID) + .addStatement(CodeBlock.of("$T springApplication = new SpringApplication(Application.class)", SpringApplication.class)) + .addStatement(CodeBlock.of("springApplication.addListeners(new $T())", ApplicationPidFileWriter.class)) + .addStatement(CodeBlock.of("$T.run(Application.class, args)", SpringApplication.class)) + .build(); + builder.addMethod(methodSpec); + } + + private void initializeTimezone() { + MethodSpec methodSpec = MethodSpec + .methodBuilder("init") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(PostConstruct.class) + .returns(TypeName.VOID) + .addStatement("$1T.setDefault($1T.getTimeZone($2S))", TimeZone.class, "UTC") + .build(); + builder.addMethod(methodSpec); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationGenerator.java new file mode 100644 index 0000000..eef86f3 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationGenerator.java @@ -0,0 +1,22 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; + +import java.util.Collection; +import java.util.Collections; + + +public class ApplicationGenerator extends AbstractGenerator { + + private ApplicationBuilder builder; + + public ApplicationGenerator(String basePackage) { + builder = new ApplicationBuilder(basePackage); + } + + @Override + protected Collection getBuilders() { + return Collections.singletonList(builder); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PomGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PomGenerator.java new file mode 100644 index 0000000..b797182 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PomGenerator.java @@ -0,0 +1,84 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import net.cloudappi.apigen.generatorcore.config.Configuration; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.Model; +import org.apache.maven.model.Parent; +import org.apache.maven.model.Profile; +import org.apache.maven.model.io.xpp3.MavenXpp3Writer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; + +public class PomGenerator { + + private PomGenerator() { + // Intentional blank + } + + public static void generate(Configuration configuration, String parentGroup, String parentArtifact, String parentVersion, File projectFolder) throws IOException { + Model model = buildModel(configuration); + addParent(model, parentGroup, parentArtifact, parentVersion); + addProperties(model); + addDependencies(model); + addProfiles(model); + writeFile(model, projectFolder); + } + + private static Model buildModel(Configuration configuration) { + Model model = new Model(); + model.setModelVersion("4.0.0"); + model.setArtifactId(configuration.getArtifact()); + model.setGroupId(configuration.getGroup()); + model.setVersion(configuration.getVersion()); + model.setName(configuration.getName()); + model.setDescription(configuration.getDescription()); + return model; + } + + private static void addParent(Model model, String group, String artifact, String version) { + Parent parent = new Parent(); + parent.setGroupId(group); + parent.setArtifactId(artifact); + parent.setVersion(version); + model.setParent(parent); + } + + private static void addProperties(Model model) { + model.addProperty("java.version", "1.8"); + model.addProperty("maven.compiler.source", "1.8"); + model.addProperty("maven.compiler.target", "1.8"); + model.addProperty("project.build.sourceEncoding", "UTF-8"); + model.addProperty("project.reporting.outputEncoding", "UTF-8"); + model.addProperty("h2.scope", "test"); + } + + private static void addDependencies(Model model) { + Dependency h2Dependency = new Dependency(); + h2Dependency.setGroupId("com.h2database"); + h2Dependency.setArtifactId("h2"); + h2Dependency.setScope("${h2.scope}"); + model.addDependency(h2Dependency); + } + + private static void addProfiles(Model model) { + Profile h2Profile = new Profile(); + h2Profile.setId("h2"); + Properties properties = new Properties(); + properties.setProperty("h2.scope", "runtime"); + h2Profile.setProperties(properties); + model.addProfile(h2Profile); + } + + private static void writeFile(Model model, File projectFolder) throws IOException { + Path pomPath = Paths.get(projectFolder.getPath(), "/pom.xml"); + File pomFile = pomPath.toFile(); + try (FileOutputStream fos = new FileOutputStream(pomFile)) { + new MavenXpp3Writer().write(fos, model); + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGenerator.java new file mode 100644 index 0000000..8eba273 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGenerator.java @@ -0,0 +1,26 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import net.cloudappi.apigen.generatorcore.config.Configuration; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ProjectStructureGenerator { + + private ProjectStructureGenerator() { + // Intentional blank + } + + public static File generate(Configuration configuration) throws IOException { + Path basePath = Files.createTempDirectory("apigen-project-folder-" + System.nanoTime()); + Path rootFolder = Paths.get(basePath.toString(), configuration.getName()); + String appFolder = configuration.getBasePackage().replace(".", "/"); + Files.createDirectories(Paths.get(rootFolder.toString(), "/src/main/resources")); + Files.createDirectories(Paths.get(rootFolder.toString(), "/src/main/java/", appFolder)); + Files.createDirectories(Paths.get(rootFolder.toString(), "/src/test/java/", appFolder)); + return rootFolder.toFile(); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PropertiesGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PropertiesGenerator.java new file mode 100644 index 0000000..ae848fd --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PropertiesGenerator.java @@ -0,0 +1,73 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.Configuration; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Slf4j +public class PropertiesGenerator { + + private PropertiesGenerator() { + // Intentional blank + } + + public static void generate(Path propertiesDir, Configuration configuration) throws IOException { + + String properties = generateProperties(); + String devProperties = generateDevProperties(configuration); + Files.createDirectories(propertiesDir); + write("application.properties", properties, propertiesDir); + write("application-dev.properties", devProperties, propertiesDir); + write("application-pre.properties", propertiesDir); + write("application-pro.properties", propertiesDir); + write("application-test.properties", propertiesDir); + } + + private static String generateProperties() { + StringBuilder p = new StringBuilder(); + add("spring.application.name", "@name@", p); + add("spring.profiles.active", "dev", p); + add("logging.level.root", "info", p); + add("spring.jackson.serialization.fail_on_empty_beans", "false", p); + add("spring.jackson.default-property-inclusion", "NON_NULL", p); + add("spring.mvc.throw-exception-if-no-handler-found", "true", p); + add("spring.resources.add-mappings", "false", p); + add("management.endpoints.enabled-by-default", "false", p); + add("management.endpoint.health.enabled", "true", p); + return p.toString(); + } + + private static String generateDevProperties(Configuration configuration) { + StringBuilder p = new StringBuilder(); + add("logging.level." + configuration.getBasePackage(), "debug", p); + add("apigen.documentation.enabled", "true", p); + add("spring.jpa.show-sql", "true", p); + return p.toString(); + } + + private static void add(String property, String value, StringBuilder sb) { + sb.append(property); + sb.append("="); + sb.append(value); + sb.append("\n"); + } + + private static void write(String fileName, Path propertiesDir) { + write(fileName, "", propertiesDir); + } + + private static void write(String fileName, String content, Path propertiesDir) { + File file = Paths.get(propertiesDir.toString(), fileName).toFile(); + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(content.getBytes()); + } catch (IOException e) { + log.error(e.getMessage()); + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java new file mode 100644 index 0000000..60fb924 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java @@ -0,0 +1,26 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.Configuration; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Slf4j +public class SpringBootBaseGenerator { + + private SpringBootBaseGenerator() { + // Intentional blank + } + + public static void generate(Configuration configuration, File projectFolder) throws IOException { + Path propertiesDir = Paths.get(projectFolder.getPath(), "/src/main/resources"); + Path javaDir = Paths.get(projectFolder.getPath(), "src/main/java"); + PropertiesGenerator.generate(propertiesDir, configuration); + new ApplicationGenerator(configuration.getBasePackage()).generate(javaDir); + Path javaTestDir = Paths.get(projectFolder.getPath(), "src/test/java"); + SpringBootContextTestGenerator.generate(configuration.getBasePackage()).writeTo(javaTestDir); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java new file mode 100644 index 0000000..5c03ee8 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java @@ -0,0 +1,46 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import javax.lang.model.element.Modifier; + +public class SpringBootContextTestGenerator { + + private SpringBootContextTestGenerator() { + // Intentional blank + } + + public static JavaFile generate(String basePackage) { + TypeSpec.Builder applicationBuilder = getTestBuilder(); + applicationBuilder.addMethod(buildTestMethod()); + return toJavaFile(basePackage, applicationBuilder); + } + + private static TypeSpec.Builder getTestBuilder() { + return TypeSpec + .classBuilder("ApplicationTests") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(SpringBootTest.class); + } + + private static MethodSpec buildTestMethod() { + return MethodSpec + .methodBuilder("thatContextLoads") + .returns(TypeName.VOID) + .addAnnotation(Test.class) + .addComment("Intentional blank") + .build(); + } + + private static JavaFile toJavaFile(String basePackage, TypeSpec.Builder builder) { + return JavaFile.builder(basePackage, builder.build()) + .indent(" ") + .build(); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractClassBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractClassBuilder.java new file mode 100644 index 0000000..bc2310e --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractClassBuilder.java @@ -0,0 +1,58 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import javax.lang.model.element.Modifier; + +public abstract class AbstractClassBuilder { + + protected TypeSpec.Builder builder; + + protected static String concatPackage(String basePackage, String newPackage) { + return String.format("%s.%s", basePackage, newPackage.toLowerCase()); + } + + protected static String concatPackage(String basePackage, String newPackage, String otherPackage) { + return String.format("%s.%s.%s", basePackage, newPackage.toLowerCase(), otherPackage); + } + + protected static TypeSpec.Builder getClass(String name) { + return TypeSpec.classBuilder(name) + .addModifiers(Modifier.PUBLIC); + } + + protected static TypeSpec.Builder getInnerClass(String name) { + return TypeSpec.classBuilder(name) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC); + } + + protected static TypeSpec.Builder getPublicInnerClass(String name) { + return TypeSpec.classBuilder(name) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC); + } + + protected static TypeSpec.Builder getInterface(String name) { + return TypeSpec.interfaceBuilder(name) + .addModifiers(Modifier.PUBLIC); + } + + protected static AnnotationSpec.Builder getAnnotation(Class clazz) { + return AnnotationSpec.builder(clazz); + } + + protected static FieldSpec.Builder getField(TypeName type, String name) { + return FieldSpec.builder(type, name, Modifier.PRIVATE); + } + + public abstract String getPackage(); + + protected abstract void initialize(); + + public TypeSpec build() { + if (this.builder == null) initialize(); + return builder.build(); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractGenerator.java new file mode 100644 index 0000000..27e9744 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractGenerator.java @@ -0,0 +1,34 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeSpec; +import lombok.extern.slf4j.Slf4j; + +import java.nio.file.Path; +import java.util.Collection; + +@Slf4j +public abstract class AbstractGenerator { + + private static final String INDENT = " "; + + protected abstract Collection getBuilders(); + + public boolean generate(Path path) { + return getBuilders().stream().allMatch(builder -> write(builder.getPackage(), builder.build(), path)); + } + + private boolean write(String packageName, TypeSpec spec, Path path) { + try { + JavaFile javaFile = JavaFile.builder(packageName, spec) + .indent(INDENT) + .skipJavaLangImports(true) + .build(); + javaFile.writeTo(path); + return true; + } catch (Exception e) { + log.error("Error writing class", e); + return false; + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java new file mode 100644 index 0000000..0297cbe --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java @@ -0,0 +1,62 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; + +import java.time.LocalDate; +import java.time.OffsetDateTime; + +public class ApigenExt2JavapoetType { + private static final String TYPE_STRING = "String"; + private static final String TYPE_BOOLEAN = "Boolean"; + private static final String TYPE_FLOAT = "Float"; + private static final String TYPE_DOUBLE = "Double"; + private static final String TYPE_INTEGER = "Integer"; + private static final String TYPE_LONG = "Long"; + private static final String TYPE_LOCAL_DATE = "LocalDate"; + private static final String TYPE_OFFSET_DATE_TIME = "OffsetDateTime"; + private static final String TYPE_COMPOSED_ID = "ComposedID"; + + private ApigenExt2JavapoetType() { + // Intentional blank + } + + public static boolean isComposedID(String type) { + return TYPE_COMPOSED_ID.equals(type); + } + + public static boolean isString(String type) { + return TYPE_STRING.equals(type); + } + + public static boolean isNumeric(String type) { + return TYPE_INTEGER.equals(type) || TYPE_LONG.equals(type); + } + + public static TypeName transformSimpleType(String type) { + switch (type) { + case TYPE_STRING: + return getTypeName(String.class); + case TYPE_BOOLEAN: + return getTypeName(Boolean.class); + case TYPE_FLOAT: + return getTypeName(Float.class); + case TYPE_DOUBLE: + return getTypeName(Double.class); + case TYPE_INTEGER: + return getTypeName(Integer.class); + case TYPE_LONG: + return getTypeName(Long.class); + case TYPE_LOCAL_DATE: + return getTypeName(LocalDate.class); + case TYPE_OFFSET_DATE_TIME: + return getTypeName(OffsetDateTime.class); + default: + throw new IllegalArgumentException("Type " + type + " not supported"); + } + } + + private static TypeName getTypeName(Class clazz) { + return ClassName.get(clazz); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Formats.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Formats.java new file mode 100644 index 0000000..28331e7 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Formats.java @@ -0,0 +1,11 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +public class Formats { + + public static final String STRING = "$S"; + public static final String LITERAL = "$L"; + public static final String ENUM_VALUE = "$T.$L"; + private Formats() { + // Intentional blank + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Members.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Members.java new file mode 100644 index 0000000..28163d0 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Members.java @@ -0,0 +1,38 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +public class Members { + + public static final String VALUE = "value"; + public static final String NAME = "name"; + public static final String MAPPED_BY = "mappedBy"; + public static final String UNIQUE = "unique"; + public static final String SEQUENCE_NAME = "sequenceName"; + public static final String STRATEGY = "strategy"; + public static final String GENERATOR = "generator"; + public static final String CODE = "code"; + public static final String MIN = "min"; + public static final String MAX = "max"; + public static final String REGEXP = "regexp"; + public static final String INTEGER = "integer"; + public static final String FRACTION = "fraction"; + public static final String TAGS = "tags"; + public static final String COMPONENT_MODEL = "componentModel"; + public static final String USES = "uses"; + public static final String MODE = "mode"; + public static final String IGNORE_BY_DEFAULT = "ignoreByDefault"; + public static final String SOURCE = "source"; + public static final String TARGET = "target"; + public static final String PROPAGATION = "propagation"; + public static final String INCLUSIVE = "inclusive"; + public static final String REQUIRED = "required"; + public static final String DEFAULT_VALUE = "defaultValue"; + public static final String FOREIGN_KEY = "foreignKey"; + public static final String COLUMN_NAMES = "columnNames"; + public static final String JOIN_COLUMNS = "joinColumns"; + public static final String INVERSE_JOIN_COLUMNS = "inverseJoinColumns"; + public static final String UNIQUE_CONSTRAINTS = "uniqueConstraints"; + public static final String REFERENCED_COLUMN_NAME = "referencedColumnName"; + private Members() { + // Intentional blank + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetType.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetType.java new file mode 100644 index 0000000..5d8173b --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetType.java @@ -0,0 +1,98 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; + +import java.time.LocalDate; +import java.time.OffsetDateTime; + +public class Openapi2JavapoetType { + + public static final String TYPE_STRING = "string"; + public static final String TYPE_NUMBER = "number"; + public static final String TYPE_INTEGER = "integer"; + public static final String TYPE_BOOLEAN = "boolean"; + public static final String TYPE_ARRAY = "array"; + public static final String TYPE_OBJECT = "object"; + + private Openapi2JavapoetType() { + // intentional blank + } + + public static boolean isSimpleType(String type) { + return TYPE_STRING.equals(type) || TYPE_NUMBER.equals(type) || TYPE_INTEGER.equals(type) || TYPE_BOOLEAN.equals(type); + } + + public static TypeName transformSimpleType(String type, String format) { + Class clazz = getClass(type, format); + if (clazz.equals(byte[].class)) { + return ArrayTypeName.of(byte.class); + } + return ClassName.get(clazz); + } + + private static Class getClass(String type, String format) { + if (type == null) throw new IllegalArgumentException("Type is required"); + type = low(type); + format = low(format); + switch (type) { + case TYPE_STRING: + return getStringClass(format); + case TYPE_NUMBER: + return getNumberClass(format); + case TYPE_INTEGER: + return getIntegerClass(format); + case TYPE_BOOLEAN: + return getBooleanClass(); + default: + throw new IllegalArgumentException("Type " + type + " with format " + format + " not supported"); + } + } + + private static Class getStringClass(String format) { + if (format == null) return String.class; + switch (format) { + case "date": + return LocalDate.class; + case "date-time": + return OffsetDateTime.class; + case "binary": + return byte[].class; + case "byte": + default: + return String.class; + } + } + + private static Class getNumberClass(String format) { + if (format == null) return Double.class; + switch (format) { + case "float": + return Float.class; + case "double": + default: + return Double.class; + } + + } + + private static Class getIntegerClass(String format) { + if (format == null) return Long.class; + switch (format) { + case "int32": + return Integer.class; + case "int64": + default: + return Long.class; + } + } + + private static Class getBooleanClass() { + return Boolean.class; + } + + private static String low(String value) { + return value != null ? value.toLowerCase() : null; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Validator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Validator.java new file mode 100644 index 0000000..3e689fe --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Validator.java @@ -0,0 +1,23 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +import net.cloudappi.apigen.generatorcore.exceptions.InvalidValuesException; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import java.util.Set; + +public class Validator { + + private static final javax.validation.Validator javaValidator = Validation.buildDefaultValidatorFactory().getValidator(); + + private Validator() { + // Intentional blank + } + + public static void validate(Object object) { + Set> violations = javaValidator.validate(object); + if (!violations.isEmpty()) { + throw new InvalidValuesException(violations); + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilder.java new file mode 100644 index 0000000..8e06975 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilder.java @@ -0,0 +1,171 @@ +package net.cloudappi.apigen.generatorcore.generator.mapper; + +import com.squareup.javapoet.*; +import net.cloudappi.apigen.archetypecore.core.ApigenMapper; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.mapstruct.*; + +import javax.lang.model.element.Modifier; +import java.util.List; +import java.util.Set; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; + +public class MapperBuilder extends AbstractClassBuilder { + + private static final String TO_RESOURCE = "toResource"; + private static final String TO_ENTITY = "toEntity"; + private static final String UPDATE_BASIC_DATA = "updateBasicData"; + + private String basePackage; + private String entityName; + private Set basicAttributes; + private Set relatedEntitiesName; + private Set resourcesToEntity; + + private TypeName entityType; + private TypeName composedIdType; + private TypeName resourceType; + + public MapperBuilder(String entityName, String basePackage, Set basicAttributes, Set relatedEntitiesName, Set resourcesToEntity, TypeName composedIdType) { + this.basePackage = basePackage; + this.entityName = entityName; + this.relatedEntitiesName = relatedEntitiesName; + this.basicAttributes = basicAttributes; + this.resourcesToEntity = resourcesToEntity; + this.entityType = EntityBuilder.getTypeName(entityName, basePackage); + this.composedIdType = composedIdType; + this.resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), getName(entityName)); + } + + private static String getName(String entityName) { + return entityName + "Mapper"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName); + } + + @Override + public String getPackage() { + return getPackage(entityName, basePackage); + } + + @Override + protected void initialize() { + initializeBuilder(); + addMapperAnnotation(); + addEntityToResource(); + addEntityListToResource(); + addEntitySetToResource(); + addResourcesToEntity(); + addUpdateBasicData(); + addComposedIDMapping(); + } + + private void initializeBuilder() { + builder = getInterface(getName(entityName)) + .addSuperinterface(ParameterizedTypeName.get(ClassName.get(ApigenMapper.class), entityType)); + } + + private void addMapperAnnotation() { + AnnotationSpec annotationSpec = AnnotationSpec.builder(Mapper.class) + .addMember(COMPONENT_MODEL, STRING, "spring") + .addMember(USES, LITERAL, getRelatedEntitiesCodeBlock()) + .build(); + builder.addAnnotation(annotationSpec); + } + + private CodeBlock getRelatedEntitiesCodeBlock() { + return relatedEntitiesName.stream() + .sorted() + .map(name -> MapperBuilder.getTypeName(name, basePackage)) + .map(type -> CodeBlock.of("$T.class", type)) + .collect(CodeBlock.joining(",", "{", "}")); + } + + private void addEntityToResource() { + MethodSpec methodSpec = MethodSpec.methodBuilder(TO_RESOURCE) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(entityType, "entity") + .returns(resourceType) + .build(); + builder.addMethod(methodSpec); + } + + private void addEntityListToResource() { + MethodSpec methodSpec = MethodSpec.methodBuilder(TO_RESOURCE) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(ParameterizedTypeName.get(ClassName.get(List.class), entityType), "entities") + .returns(ParameterizedTypeName.get(ClassName.get(List.class), resourceType)) + .build(); + builder.addMethod(methodSpec); + } + + private void addEntitySetToResource() { + MethodSpec methodSpec = MethodSpec.methodBuilder(TO_RESOURCE) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(ParameterizedTypeName.get(ClassName.get(Set.class), entityType), "entities") + .returns(ParameterizedTypeName.get(ClassName.get(Set.class), resourceType)) + .build(); + builder.addMethod(methodSpec); + + } + + private void addResourcesToEntity() { + for (TypeName type : resourcesToEntity) { + MethodSpec methodSpec = MethodSpec.methodBuilder(TO_ENTITY) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addParameter(type, "resource") + .returns(entityType) + .build(); + builder.addMethod(methodSpec); + } + } + + private void addUpdateBasicData() { + MethodSpec methodSpecBuilder = MethodSpec.methodBuilder(UPDATE_BASIC_DATA) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addAnnotation(Override.class) + .addAnnotation(getAnnotation(BeanMapping.class).addMember(IGNORE_BY_DEFAULT, LITERAL, true).build()) + .addAnnotation(getMappingsAnnotation()) + .addParameter(entityType, "source") + .addParameter(ParameterSpec.builder(entityType, "target").addAnnotation(MappingTarget.class).build()) + .build(); + builder.addMethod(methodSpecBuilder); + } + + private AnnotationSpec getMappingsAnnotation() { + AnnotationSpec.Builder annotationBuilder = getAnnotation(Mappings.class); + basicAttributes.stream().sorted() + .map(attribute -> getAnnotation(Mapping.class).addMember(SOURCE, STRING, attribute).addMember(TARGET, STRING, attribute).build()) + .forEach(annotationSpec -> annotationBuilder.addMember(VALUE, LITERAL, annotationSpec)); + return annotationBuilder.build(); + } + + private void addComposedIDMapping() { + if (composedIdType == null) return; + MethodSpec mapIDToString = MethodSpec.methodBuilder("map") + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) + .addParameter(composedIdType, "id") + .returns(String.class) + .addStatement("return id.toString()") + .build(); + builder.addMethod(mapIDToString); + MethodSpec mapStringToID = MethodSpec.methodBuilder("map") + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) + .addParameter(String.class, "id") + .returns(composedIdType) + .addStatement("return $T.from(id)", composedIdType) + .build(); + builder.addMethod(mapStringToID); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MappersGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MappersGenerator.java new file mode 100644 index 0000000..8bfcef4 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MappersGenerator.java @@ -0,0 +1,35 @@ +package net.cloudappi.apigen.generatorcore.generator.mapper; + +import com.squareup.javapoet.TypeName; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourcesData; + +import java.util.*; + + +@Slf4j +public class MappersGenerator extends AbstractGenerator { + + private Map builders = new HashMap<>(); + + public MappersGenerator(Collection entities, EntitiesData entitiesData, ResourcesData resourcesData, String basePackage) { + entities.stream().filter(e -> !builders.containsKey(e.getName())). + forEach(e -> { + String name = e.getName(); + Set basicAttributes = entitiesData.getBasicAttributes(name); + Set relateEntities = entitiesData.getRelatedEntities(name); + Set inputResources = resourcesData.getInputResources(name); + TypeName composedIdType = entitiesData.getComposedIDType(name); + builders.put(name, new MapperBuilder(name, basePackage, basicAttributes, relateEntities, inputResources, composedIdType)); + }); + } + + @Override + protected Collection getBuilders() { + return new ArrayList<>(builders.values()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java new file mode 100644 index 0000000..fa42e82 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java @@ -0,0 +1,169 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence; + +import com.squareup.javapoet.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.entity.Column; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.*; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.ApigenExt2JavapoetType; +import net.cloudappi.apigen.generatorcore.utils.CustomStringUtils; + +import javax.lang.model.element.Modifier; +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Objects.isNull; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.*; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; + +@Slf4j +public class ComposedIdBuilder extends AbstractClassBuilder { + + private String entityName; + private List idAttributes; + private String basePackage; + + private TypeName typeName; + + public ComposedIdBuilder(Entity entity, String basePackage) { + this.entityName = entity.getName(); + this.idAttributes = entity.getAttributes().stream() + .filter(a -> ApigenExt2JavapoetType.isComposedID(a.getType())) + .findFirst().orElseThrow(IllegalArgumentException::new) + .getAttributes(); + this.basePackage = basePackage; + typeName = getTypeName(entityName, basePackage); + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), getName(entityName)); + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName); + } + + private static String getName(String entityName) { + return entityName + "ID"; + } + + @Override + public String getPackage() { + return getPackage(entityName, basePackage); + } + + @Override + protected void initialize() { + initializeBuilder(); + addIdAttributes(); + addFromMethod(); + addToStringMethod(); + addCompareToMethod(); + } + + private void initializeBuilder() { + builder = getClass(getName(entityName)) + .addSuperinterface(Serializable.class) + .addSuperinterface(getComparableInterfaceClass()) + .addAnnotation(Getter.class).addAnnotation(Setter.class).addAnnotation(Embeddable.class) + .addAnnotation(NoArgsConstructor.class).addAnnotation(AllArgsConstructor.class); + } + + private ParameterizedTypeName getComparableInterfaceClass() { + return ParameterizedTypeName.get(ClassName.get(Comparable.class), typeName); + } + + private void addIdAttributes() { + idAttributes.forEach(this::addAttribute); + } + + private void addAttribute(Attribute attribute) { + log.debug("Parsing attribute '{}' of type '{}' in entity composed ID '{}' | {}", attribute.getName(), attribute.getType(), entityName, attribute); + FieldSpec.Builder fieldBuilder = getBasicFieldBuilder(attribute.getType(), attribute.getName()); + Column column = attribute.getColumns().isEmpty() ? new Column() : attribute.getColumns().get(0); + addColumnAnnotation(attribute.getName(), column, fieldBuilder); + attribute.getValidations().forEach(validation -> validation.apply(fieldBuilder)); + builder.addField(fieldBuilder.build()); + } + + private FieldSpec.Builder getBasicFieldBuilder(String type, String attributeName) { + TypeName attributeType = ApigenExt2JavapoetType.transformSimpleType(type); + return FieldSpec.builder(attributeType, attributeName, Modifier.PRIVATE); + } + + private void addColumnAnnotation(String javaName, Column column, FieldSpec.Builder fieldBuilder) { + if (isNull(column) || isNull(fieldBuilder)) return; + String columnName = column.getName(); + if (columnName == null) columnName = CustomStringUtils.camelCaseToSnakeCase(javaName); + Boolean isUnique = column.getUnique(); + AnnotationSpec.Builder annotationBuilder = getAnnotation(javax.persistence.Column.class) + .addMember(NAME, STRING, columnName); + + if (Boolean.TRUE.equals(isUnique)) { + annotationBuilder.addMember(UNIQUE, LITERAL, true); + } + + fieldBuilder.addAnnotation(annotationBuilder.build()); + } + + private void addFromMethod() { + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("from") + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(String.class, "str") + .returns(typeName) + .addStatement("if (str == null) return null") + .addStatement("$T[] parts = str.split(\"_\")", String.class) + .addStatement("if (parts.length != $L) throw new $T()", idAttributes.size(), IllegalArgumentException.class); + List args = new ArrayList<>(); + args.add(typeName); + StringBuilder params = new StringBuilder(); + for (int i = 0; i < idAttributes.size(); i++) { + TypeName attTypeName = ApigenExt2JavapoetType.transformSimpleType(idAttributes.get(i).getType()); + if (attTypeName.equals(TypeName.get(String.class))) { + params.append("parts[").append(i).append("],"); + } else if (attTypeName.equals(TypeName.get(LocalDate.class)) || attTypeName.equals(TypeName.get(LocalDateTime.class))) { + args.add(attTypeName); + params.append("$T.parse(parts[").append(i).append("])"); + } else { + args.add(attTypeName); + params.append("$T.valueOf(parts[").append(i).append("])"); + } + } + methodBuilder.addStatement("return new $T(" + params.toString() + ")", args.toArray()); + builder.addMethod(methodBuilder.build()); + } + + private void addToStringMethod() { + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("toString") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(String.class) + .addStatement("return " + idAttributes.stream().map(Attribute::getName).collect(Collectors.joining(" + \"_\" + "))); + builder.addMethod(methodBuilder.build()); + } + + private void addCompareToMethod() { + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("compareTo") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addParameter(typeName, "o") + .returns(int.class) + .addStatement("int c"); + for (Attribute a : idAttributes) { + methodBuilder.addStatement("c = $1L.compareTo(o.$1L)", a.getName()); + methodBuilder.addStatement("if (c != 0) return c"); + } + methodBuilder.addStatement("return c"); + builder.addMethod(methodBuilder.build()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesData.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesData.java new file mode 100644 index 0000000..2e7aa50 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesData.java @@ -0,0 +1,100 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence; + +import com.squareup.javapoet.*; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import javax.persistence.*; +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.Objects.isNull; + +public class EntitiesData { + + private static final Set TO_ONE_RELATION = new HashSet<>(Arrays.asList(TypeName.get(OneToOne.class), TypeName.get(ManyToOne.class))); + private static final Set TO_MANY_RELATION = new HashSet<>(Arrays.asList(TypeName.get(OneToMany.class), TypeName.get(ManyToMany.class))); + private static final TypeName ID = TypeName.get(Id.class); + private static final TypeName ID_E = TypeName.get(EmbeddedId.class); + private Map> entitiesAttributes = new HashMap<>(); + + public EntitiesData(Collection spec) { + for (TypeSpec s : spec) { + String entityName = s.name; + Map attributes = new HashMap<>(); + for (FieldSpec f : s.fieldSpecs) { + String attributeName = f.name; + attributes.put(attributeName, getRelationalAnnotation(f)); + } + entitiesAttributes.put(entityName, attributes); + } + } + + private AttributeData getRelationalAnnotation(FieldSpec fieldSpec) { + boolean isCollection = false; + boolean isOwner = false; + String relatedEntity = null; + boolean isId = false; + for (AnnotationSpec annotationSpec : fieldSpec.annotations) { + TypeName annotationType = annotationSpec.type; + if (TO_ONE_RELATION.contains(annotationType)) { + isCollection = false; + isOwner = !annotationSpec.members.containsKey("mappedBy"); + relatedEntity = getClassName(fieldSpec.type); + } else if (TO_MANY_RELATION.contains(annotationType)) { + isCollection = true; + isOwner = !annotationSpec.members.containsKey("mappedBy"); + ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) fieldSpec.type; + relatedEntity = getClassName(parameterizedTypeName.typeArguments.get(0)); + } else if (ID.equals(annotationType) || ID_E.equals(annotationType)) { + isId = true; + } + } + return new AttributeData(isCollection, relatedEntity, isOwner, isId, fieldSpec.type); + } + + private String getClassName(TypeName typeName) { + return ((ClassName) typeName).simpleName(); + } + + public Set getRelatedEntities(String entityName) { + return entitiesAttributes.get(entityName).values().stream() + .map(attributeData -> attributeData.relatedEntity) + .filter(Objects::nonNull) + .filter(name -> !name.equals(entityName)) + .collect(Collectors.toSet()); + } + + public Set getBasicAttributes(String entityName) { + return entitiesAttributes.get(entityName).entrySet().stream() + .filter(e -> isNull(e.getValue().relatedEntity)) + .filter(e -> !e.getValue().isId()) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + + public TypeName getIDType(String entityName) { + return entitiesAttributes.get(entityName).values().stream() + .filter(AttributeData::isId).map(AttributeData::getTypeName).findFirst().orElse(null); + } + + public TypeName getComposedIDType(String entityName) { + TypeName typeName = getIDType(entityName); + if (typeName.toString().startsWith("java")) return null; + return typeName; + } + + public Map getAttributes(String entityName) { + return entitiesAttributes.get(entityName); + } + + @Getter + @AllArgsConstructor + public static class AttributeData { + private boolean isCollection; + private String relatedEntity; + private boolean isOwned; + private boolean isId; + private TypeName typeName; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGenerator.java new file mode 100644 index 0000000..d920b25 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGenerator.java @@ -0,0 +1,40 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class EntitiesGenerator extends AbstractGenerator { + + private List builders = new ArrayList<>(); + private List idBuilders = new ArrayList<>(); + private EntitiesData entitiesData; + + public EntitiesGenerator(List entities, String basePackage) { + EntityRelationManager entityRelationManager = new EntityRelationManager(entities); + entities.forEach(e -> { + EntityBuilder entityBuilder = new EntityBuilder(e, entityRelationManager, basePackage); + builders.add(entityBuilder); + if (entityBuilder.hasComposedID()) idBuilders.add(new ComposedIdBuilder(e, basePackage)); + }); + entitiesData = new EntitiesData(builders.stream().map(AbstractClassBuilder::build).collect(Collectors.toList())); + } + + @Override + protected Collection getBuilders() { + List allBuilders = new ArrayList<>(builders); + allBuilders.addAll(idBuilders); + return allBuilders; + } + + public EntitiesData getEntitiesData() { + return entitiesData; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityBuilder.java new file mode 100644 index 0000000..ddf6449 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityBuilder.java @@ -0,0 +1,299 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence; + +import com.squareup.javapoet.*; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import net.cloudappi.apigen.generatorcore.config.entity.Column; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.*; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.ApigenExt2JavapoetType; +import net.cloudappi.apigen.generatorcore.utils.CustomStringUtils; +import org.hibernate.annotations.GenericGenerator; + +import javax.lang.model.element.Modifier; +import javax.persistence.*; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static java.util.Objects.isNull; +import static java.util.Objects.nonNull; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.*; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; + +@Slf4j +public class EntityBuilder extends AbstractClassBuilder { + + private Entity entity; + private EntityRelationManager relationManager; + private String basePackage; + + public EntityBuilder(Entity entity, EntityRelationManager relationManager, String basePackage) { + this.entity = entity; + this.relationManager = relationManager; + this.basePackage = basePackage; + } + + public boolean hasComposedID() { + return hasComposedID(entity); + } + + private static boolean hasComposedID(Entity entity) { + return getComposedIDAttribute(entity).isPresent(); + } + + private static Optional getComposedIDAttribute(Entity entity) { + return entity.getAttributes().stream() + .filter(EntityBuilder::isComposedPrimaryKey) + .findFirst(); + } + + private static boolean isComposedPrimaryKey(Attribute attribute) { + return ApigenExt2JavapoetType.isComposedID(attribute.getType()); + } + + private static Optional getSimpleIDAttribute(Entity entity) { + return entity.getAttributes().stream().filter(EntityBuilder::isPrimaryKey).findFirst(); + } + + private static boolean isPrimaryKey(Attribute attribute) { + return attribute.getColumns().size() == 1 && attribute.getColumns().get(0).getPrimaryKey(); + } + + private static String getIDName(Entity entity) { + Attribute idAttribute = getComposedIDAttribute(entity).orElse(null); + if (idAttribute == null) idAttribute = getSimpleIDAttribute(entity).orElse(null); + if (idAttribute == null) return null; + else return idAttribute.getName(); + } + + public static TypeName getIDTypeName(Entity entity, String basePackage) { + if (hasComposedID(entity)) return ComposedIdBuilder.getTypeName(entity.getName(), basePackage); + Optional idAttribute = getSimpleIDAttribute(entity); + return idAttribute.map(attribute -> ApigenExt2JavapoetType.transformSimpleType(attribute.getType())) + .orElseGet(() -> ClassName.get(Long.class)); + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), entityName); + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName); + } + + @Override + public String getPackage() { + return getPackage(entity.getName(), basePackage); + } + + @Override + protected void initialize() { + initializeBuilder(); + addSetAndGetIdMethods(); + addAttributes(); + addIsReferenceMethod(); + } + + private void initializeBuilder() { + builder = getClass(entity.getName()) + .superclass(getParentClass()) + .addAnnotation(Getter.class).addAnnotation(Setter.class) + .addAnnotation(getAnnotation(javax.persistence.Entity.class).build()) + .addAnnotation(getAnnotation(Table.class).addMember(NAME, STRING, getTable()).build()); + } + + private String getTable() { + if (entity.getTable() == null) return CustomStringUtils.camelCaseToSnakeCase(entity.getName()); + return entity.getTable(); + } + + private ParameterizedTypeName getParentClass() { + return ParameterizedTypeName.get( + ClassName.get(ApigenAbstractPersistable.class), + EntityBuilder.getIDTypeName(entity, basePackage) + ); + } + + private void addSetAndGetIdMethods() { + String attributeName = getIDName(entity); + if (attributeName == null) return; + TypeName identifierType = getIDTypeName(entity, basePackage); + + builder.addMethod(MethodSpec.methodBuilder("getId") + .addAnnotation(AnnotationSpec.builder(Override.class).build()) + .addModifiers(Modifier.PUBLIC) + .returns(identifierType) + .addStatement("return this.$N", attributeName) + .build()); + + builder.addMethod(MethodSpec.methodBuilder("setId") + .addAnnotation(AnnotationSpec.builder(Override.class).build()) + .addModifiers(Modifier.PUBLIC) + .returns(void.class) + .addParameter(identifierType, attributeName) + .addStatement("this.$N = $N", attributeName, attributeName) + .build()); + } + + private void addAttributes() { + List attributes = entity.getAttributes(); + if (nonNull(attributes)) { + attributes.forEach(this::addAttribute); + } + } + + private void addAttribute(Attribute attribute) { + log.debug("Parsing attribute '{}' of type '{}' in entity '{}' | {}", attribute.getName(), attribute.getType(), entity.getName(), attribute); + FieldSpec.Builder fieldBuilder = getBasicFieldBuilder(attribute); + if (nonNull(attribute.getRelation())) { + relationManager.applyRelation(entity.getName(), attribute, fieldBuilder); + } else if (isPrimaryKey(attribute)) { + Column column = attribute.getColumns().get(0); + addIdAnnotation(attribute.getType(), column, fieldBuilder); + addColumnAnnotation(attribute.getName(), column, fieldBuilder); + } else if (isComposedPrimaryKey(attribute)) { + addComposedIdAnnotation(fieldBuilder); + } else { + Column column = attribute.getColumns().isEmpty() ? new Column() : attribute.getColumns().get(0); + addColumnAnnotation(attribute.getName(), column, fieldBuilder); + } + attribute.getValidations().forEach(validation -> validation.apply(fieldBuilder)); + builder.addField(fieldBuilder.build()); + } + + private FieldSpec.Builder getBasicFieldBuilder(Attribute attribute) { + TypeName attributeType = getType(attribute); + return FieldSpec.builder(attributeType, attribute.getName(), Modifier.PRIVATE); + } + + private TypeName getType(Attribute attribute) { + String type = attribute.getType(); + Relation relation = attribute.getRelation(); + if (isComposedPrimaryKey(attribute)) return ComposedIdBuilder.getTypeName(entity.getName(), basePackage); + if (isNull(relation)) return ApigenExt2JavapoetType.transformSimpleType(type); + String relatedEntityName = relation.getRelatedEntity(); + TypeName relatedEntityType = EntityBuilder.getTypeName(relatedEntityName, basePackage); + if (isToMany(relation)) { + return ParameterizedTypeName.get(ClassName.get(Set.class), relatedEntityType); + } else if (isToOne(relation)) { + return relatedEntityType; + } else { + throw new IllegalArgumentException("Relation type " + relation.getType() + " not supported"); + } + } + + private boolean isToOne(Relation relation) { + if (isNull(relation)) return false; + RelationType type = relation.getType(); + return RelationType.MANY_TO_ONE.equals(type) || RelationType.ONE_TO_ONE.equals(type) || RelationType.ONE_TO_ONE_OWNER.equals(type); + } + + private boolean isToMany(Relation relation) { + if (isNull(relation)) return false; + RelationType type = relation.getType(); + return RelationType.ONE_TO_MANY.equals(type) || RelationType.MANY_TO_MANY.equals(type) || RelationType.MANY_TO_MANY_OWNER.equals(type); + } + + private void addColumnAnnotation(String javaName, Column column, FieldSpec.Builder fieldBuilder) { + if (isNull(column) || isNull(fieldBuilder)) return; + String columnName = column.getName(); + if (columnName == null) columnName = CustomStringUtils.camelCaseToSnakeCase(javaName); + Boolean isUnique = column.getUnique(); + + AnnotationSpec.Builder annotationBuilder = getAnnotation(javax.persistence.Column.class) + .addMember(NAME, STRING, columnName); + + if (nonNull(isUnique) && isUnique) { + annotationBuilder.addMember(UNIQUE, LITERAL, true); + } + + fieldBuilder.addAnnotation(annotationBuilder.build()); + } + + private void addComposedIdAnnotation(FieldSpec.Builder fieldBuilder) { + fieldBuilder.addAnnotation(EmbeddedId.class); + } + + private void addIdAnnotation(String type, Column column, FieldSpec.Builder fieldBuilder) { + + fieldBuilder.addAnnotation(Id.class); + + boolean isString = ApigenExt2JavapoetType.isString(type); + boolean isNumeric = ApigenExt2JavapoetType.isNumeric(type); + + boolean isAutogenerated = column.getAutogenerated(); + boolean isSequence = nonNull(column.getSequence()); + + isAutogenerated = isAutogenerated && (isString || isNumeric); + + if (isAutogenerated) { + if (isString) { + addUUIDIdGenerationAnnotations(fieldBuilder); + } else { + if (isSequence) { + addSequenceIdGenerationAnnotations(column.getSequence(), fieldBuilder); + } else { + addNativeIdGenerationAnnotations(fieldBuilder); + } + } + } + } + + private void addSequenceIdGenerationAnnotations(String sequence, FieldSpec.Builder fieldBuilder) { + // @GeneratedValue(generator="sequence_name") + // @SequenceGenerator(name="sequence_name", sequenceName="sequence") + AnnotationSpec.Builder generatedValueAnnotation = getAnnotation(GeneratedValue.class) + .addMember(GENERATOR, STRING, sequence + "_name"); + fieldBuilder.addAnnotation(generatedValueAnnotation.build()); + AnnotationSpec.Builder sequenceGeneratorAnnotation = getAnnotation(SequenceGenerator.class) + .addMember(NAME, STRING, sequence + "_name") + .addMember(SEQUENCE_NAME, STRING, sequence); + fieldBuilder.addAnnotation(sequenceGeneratorAnnotation.build()); + } + + private void addUUIDIdGenerationAnnotations(FieldSpec.Builder fieldBuilder) { + // @GeneratedValue(generator="uuid") + // @GenericGenerator(name = "uuid", strategy = "uuid2") + AnnotationSpec.Builder generatedValueAnnotation = getAnnotation(GeneratedValue.class) + .addMember(GENERATOR, STRING, "uuid"); + fieldBuilder.addAnnotation(generatedValueAnnotation.build()); + AnnotationSpec.Builder genericGeneratorAnnotationUUID = getAnnotation(GenericGenerator.class) + .addMember(NAME, STRING, "uuid") + .addMember(STRATEGY, STRING, "uuid2"); + fieldBuilder.addAnnotation(genericGeneratorAnnotationUUID.build()); + } + + private void addNativeIdGenerationAnnotations(FieldSpec.Builder fieldBuilder) { + // @GeneratedValue(generator="native") + // @GenericGenerator(name = "native", strategy = "native") + AnnotationSpec.Builder generatedValueAnnotation = getAnnotation(GeneratedValue.class) + .addMember(GENERATOR, STRING, "native"); + fieldBuilder.addAnnotation(generatedValueAnnotation.build()); + AnnotationSpec.Builder genericGeneratorAnnotationNative = getAnnotation(GenericGenerator.class) + .addMember(NAME, STRING, "native") + .addMember(STRATEGY, STRING, "native"); + fieldBuilder.addAnnotation(genericGeneratorAnnotationNative.build()); + } + + private void addIsReferenceMethod() { + String idAttributeName = getIDName(entity); + List otherAttributes = entity.getAttributes().stream() + .map(Attribute::getName).filter(n -> !n.equals(idAttributeName)).collect(Collectors.toList()); + + StringBuilder statement = new StringBuilder(); + statement.append("return getId() != null"); + otherAttributes.forEach(a -> statement.append(" && ").append(a).append(" == null")); + + builder.addMethod(MethodSpec.methodBuilder("isReference") + .addAnnotation(AnnotationSpec.builder(Override.class).build()) + .addModifiers(Modifier.PUBLIC) + .returns(boolean.class) + .addStatement(statement.toString()) + .build()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityRelationManager.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityRelationManager.java new file mode 100644 index 0000000..a801473 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityRelationManager.java @@ -0,0 +1,181 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence; + +import com.squareup.javapoet.FieldSpec; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.entity.*; +import net.cloudappi.apigen.generatorcore.generator.persistence.relations.*; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +@Slf4j +public class EntityRelationManager { + + /* > */ + private Map> manyToManyMappedEntities = new ConcurrentHashMap<>(); + + private Map> manyToOneMappedEntities = new ConcurrentHashMap<>(); + + /* > */ + private Map> oneToManyMappedEntities = new ConcurrentHashMap<>(); + + /* > */ + private Map> oneToOneMappedEntities = new ConcurrentHashMap<>(); + + /* > */ + private Map> remainingAttributes = new HashMap<>(); + + public EntityRelationManager(Collection entities) { + detectRelations(entities); + } + + private void detectRelations(Collection entities) { + for (Entity entity : entities) { + for (Attribute attribute : entity.getAttributes()) { + detectRelation(entity, attribute); + } + } + checkRemainingAttributes(); + } + + private void detectRelation(Entity entity, Attribute attribute) { + if (attribute.getRelation() == null) return; + + if (attribute.getIsCollection()) { + identifyToMany(attribute, entity.getName()); + } else { + identifyToOne(attribute, entity.getName()); + } + } + + private void identifyToMany(Attribute attribute, String entityName) { + Relation relation = attribute.getRelation(); + if (relation.getIntermediateTable() == null) { + relation.setType(RelationType.ONE_TO_MANY); + registerOneToMany(attribute, entityName); + } else { + if (relation.getOwner()) { + relation.setType(RelationType.MANY_TO_MANY_OWNER); + } else { + relation.setType(RelationType.MANY_TO_MANY); + } + registerManyToMany(attribute, entityName); + } + } + + private void identifyToOne(Attribute attribute, String entityName) { + if (!attribute.getForeignColumns().isEmpty()) { + attribute.getRelation().setType(RelationType.ONE_TO_ONE); + registerOneToOne(attribute, entityName); + } else { + remainingAttributes.putIfAbsent(entityName, new HashMap<>()); + remainingAttributes.get(entityName).put(attribute.getName(), attribute); + } + } + + private void checkRemainingAttributes() { + for (String entityName : remainingAttributes.keySet()) { + checkIfEntityContainsManyToOneAttributes(entityName); + checkIfEntityContainsOneToOneOwnerAttributes(entityName); + } + } + + private void checkIfEntityContainsManyToOneAttributes(String entityName) { + if (!oneToManyMappedEntities.containsKey(entityName)) { + for (Attribute attribute : remainingAttributes.get(entityName).values()) { + attribute.getRelation().setType(RelationType.MANY_TO_ONE); + registerManyToOne(attribute, entityName); + } + } else { + for (Attribute attribute : remainingAttributes.get(entityName).values()) { + for (Attribute foreignAttribute : oneToManyMappedEntities.get(entityName).values()) { + if (columnsMatch(attribute, foreignAttribute)) { + attribute.getRelation().setType(RelationType.MANY_TO_ONE); + registerManyToOne(attribute, entityName); + break; + } + } + } + } + } + + private void checkIfEntityContainsOneToOneOwnerAttributes(String entityName) { + if (!oneToOneMappedEntities.containsKey(entityName)) return; + for (Attribute attribute : remainingAttributes.get(entityName).values()) { + for (Attribute foreignAttribute : oneToOneMappedEntities.get(entityName).values()) { + if (columnsMatch(attribute, foreignAttribute)) { + attribute.getRelation().setType(RelationType.ONE_TO_ONE_OWNER); + registerOneToOne(attribute, entityName); + break; + } + } + } + } + + private boolean columnsMatch(Attribute attribute, Attribute foreignAttribute) { + Set columns = attribute.getColumns().stream().map(Column::getName).collect(Collectors.toSet()); + Set foreignColumns = foreignAttribute.getForeignColumns().stream().map(Column::getName).collect(Collectors.toSet()); + return !foreignColumns.isEmpty() && foreignColumns.equals(columns); + } + + private RelatedFieldBuilder generateRelation(String entityName, Attribute attribute) { + String foreignClassName = attribute.getRelation().getRelatedEntity(); + RelationType relationType = attribute.getRelation().getType(); + + switch (relationType) { + case ONE_TO_MANY: + String foreignAttributeName = manyToOneMappedEntities.get(entityName) != null ? manyToOneMappedEntities.get(entityName).get(foreignClassName).getName() : ""; + return new OneToManyBuilder(foreignAttributeName); + case MANY_TO_ONE: + return new ManyToOneBuilder(map(attribute.getColumns())); + case MANY_TO_MANY_OWNER: + Relation relation = attribute.getRelation(); + String intermediateTable = relation.getIntermediateTable(); + return new ManyToManyOwnerBuilder(intermediateTable, map(relation.getColumns()), map(relation.getReverseColumns())); + case MANY_TO_MANY: + String mappedByFieldNameManyToMany = manyToManyMappedEntities.get(entityName).get(foreignClassName).getName(); + return new ManyToManyBuilder(mappedByFieldNameManyToMany); + case ONE_TO_ONE_OWNER: + return new OneToOneOwnerBuilder(map(attribute.getColumns())); + case ONE_TO_ONE: + String mappedByFieldName = oneToOneMappedEntities.get(entityName).get(foreignClassName).getName(); + return new OneToOneBuilder(mappedByFieldName); + default: + throw new IllegalArgumentException(String.format("Relation type %s not supported", relationType)); + } + } + + private List map(List columns) { + return columns.stream().map(c -> new ColumnRelation(c.getName(), c.getReferenceColumn())).collect(Collectors.toList()); + } + + public void applyRelation(String entityName, Attribute attribute, FieldSpec.Builder fieldBuilder) { + generateRelation(entityName, attribute).apply(fieldBuilder); + } + + private void registerManyToMany(Attribute attribute, String entityName) { + Relation relation = attribute.getRelation(); + manyToManyMappedEntities.putIfAbsent(relation.getRelatedEntity(), new HashMap<>()); + manyToManyMappedEntities.get(relation.getRelatedEntity()).put(entityName, attribute); + } + + private void registerManyToOne(Attribute attribute, String entityName) { + Relation relation = attribute.getRelation(); + manyToOneMappedEntities.putIfAbsent(relation.getRelatedEntity(), new HashMap<>()); + manyToOneMappedEntities.get(relation.getRelatedEntity()).put(entityName, attribute); + } + + private void registerOneToMany(Attribute attribute, String entityName) { + Relation relation = attribute.getRelation(); + oneToManyMappedEntities.putIfAbsent(relation.getRelatedEntity(), new HashMap<>()); + oneToManyMappedEntities.get(relation.getRelatedEntity()).put(entityName, attribute); + } + + private void registerOneToOne(Attribute attribute, String entityName) { + Relation relation = attribute.getRelation(); + oneToOneMappedEntities.putIfAbsent(relation.getRelatedEntity(), new HashMap<>()); + oneToOneMappedEntities.get(relation.getRelatedEntity()).put(entityName, attribute); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java new file mode 100644 index 0000000..ce0783a --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java @@ -0,0 +1,11 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.relations; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ColumnRelation { + private String name; + private String referencedName; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java new file mode 100644 index 0000000..37c36ed --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java @@ -0,0 +1,25 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.relations; + +import com.squareup.javapoet.AnnotationSpec; + +import javax.persistence.ManyToMany; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.MAPPED_BY; + +public class ManyToManyBuilder extends RelatedFieldBuilder { + + private String mappedByFieldName; + + public ManyToManyBuilder(String mappedByFieldName) { + this.mappedByFieldName = mappedByFieldName; + } + + @Override + protected void initialize() { + AnnotationSpec relationAnnotation = AnnotationSpec.builder(ManyToMany.class) + .addMember(MAPPED_BY, STRING, mappedByFieldName) + .build(); + annotations.add(relationAnnotation); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java new file mode 100644 index 0000000..4286c37 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java @@ -0,0 +1,55 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.relations; + +import com.squareup.javapoet.AnnotationSpec; + +import javax.persistence.*; + +import java.util.ArrayList; +import java.util.List; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; + +public class ManyToManyOwnerBuilder extends RelatedFieldBuilder { + + private String intermediateTable; + private List columns; + private List inverseColumns; + + public ManyToManyOwnerBuilder(String intermediateTable, List columns, List inverseColumns) { + this.intermediateTable = intermediateTable; + this.columns = columns; + this.inverseColumns = inverseColumns; + } + + @Override + protected void initialize() { + AnnotationSpec relationAnnotation = AnnotationSpec.builder(ManyToMany.class).build(); + annotations.add(relationAnnotation); + AnnotationSpec joinTable = joinTable(intermediateTable, columns, inverseColumns); + annotations.add(joinTable); + } + + private AnnotationSpec joinTable(String intermediateTable, List columns, List inverseColumns) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(JoinTable.class).addMember(NAME, STRING, intermediateTable); + List columnNames = new ArrayList<>(); + for (ColumnRelation cr : columns) { + columnNames.add(cr.getName()); + builder.addMember(JOIN_COLUMNS, LITERAL, joinColumn(cr.getName(), cr.getReferencedName())); + } + for (ColumnRelation cr : inverseColumns) { + columnNames.add(cr.getName()); + builder.addMember(INVERSE_JOIN_COLUMNS, LITERAL, joinColumn(cr.getName(), cr.getReferencedName())); + } + return builder.addMember(UNIQUE_CONSTRAINTS, LITERAL, uniqueConstraint(columnNames)).build(); + } + + private AnnotationSpec uniqueConstraint(List constrains) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(UniqueConstraint.class); + for (String constrain : constrains) { + builder.addMember(COLUMN_NAMES, STRING, constrain); + } + return builder.build(); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java new file mode 100644 index 0000000..e83fe97 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java @@ -0,0 +1,26 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.relations; + +import com.squareup.javapoet.AnnotationSpec; +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.ManyToOne; + +import java.util.List; + +@Slf4j +public class ManyToOneBuilder extends RelatedFieldBuilder { + + private List columns; + + public ManyToOneBuilder(List columns) { + this.columns = columns; + } + + @Override + protected void initialize() { + annotations.add(AnnotationSpec.builder(ManyToOne.class).build()); + for (ColumnRelation cr : columns) { + annotations.add(joinColumn(cr.getName(), cr.getReferencedName())); + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java new file mode 100644 index 0000000..a4637d8 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java @@ -0,0 +1,27 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.relations; + +import com.squareup.javapoet.AnnotationSpec; +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.OneToMany; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.MAPPED_BY; + +@Slf4j +public class OneToManyBuilder extends RelatedFieldBuilder { + + private String mappedByFieldName; + + public OneToManyBuilder(String mappedByFieldName) { + this.mappedByFieldName = mappedByFieldName; + } + + @Override + protected void initialize() { + AnnotationSpec relationAnnotation = AnnotationSpec.builder(OneToMany.class) + .addMember(MAPPED_BY, STRING, mappedByFieldName) + .build(); + annotations.add(relationAnnotation); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java new file mode 100644 index 0000000..2e63e7c --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java @@ -0,0 +1,25 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.relations; + +import com.squareup.javapoet.AnnotationSpec; + +import javax.persistence.OneToOne; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.MAPPED_BY; + +public class OneToOneBuilder extends RelatedFieldBuilder { + + private String mappedByFieldName; + + public OneToOneBuilder(String mappedByFieldName) { + this.mappedByFieldName = mappedByFieldName; + } + + @Override + protected void initialize() { + AnnotationSpec relationAnnotation = AnnotationSpec.builder(OneToOne.class) + .addMember(MAPPED_BY, STRING, mappedByFieldName) + .build(); + annotations.add(relationAnnotation); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java new file mode 100644 index 0000000..067600c --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java @@ -0,0 +1,24 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.relations; + +import com.squareup.javapoet.AnnotationSpec; + +import javax.persistence.OneToOne; + +import java.util.List; + +public class OneToOneOwnerBuilder extends RelatedFieldBuilder { + + private List columns; + + public OneToOneOwnerBuilder(List columns) { + this.columns = columns; + } + + @Override + protected void initialize() { + annotations.add(AnnotationSpec.builder(OneToOne.class).build()); + for (ColumnRelation cr : columns) { + annotations.add(joinColumn(cr.getName(), cr.getReferencedName())); + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java new file mode 100644 index 0000000..24ec597 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java @@ -0,0 +1,33 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.relations; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.FieldSpec; +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.JoinColumn; +import java.util.ArrayList; +import java.util.List; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.NAME; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.REFERENCED_COLUMN_NAME; + +@Slf4j +public abstract class RelatedFieldBuilder { + + protected List annotations = new ArrayList<>(); + + protected abstract void initialize(); + + public void apply(FieldSpec.Builder fieldBuilder) { + if (annotations.isEmpty()) initialize(); + annotations.forEach(fieldBuilder::addAnnotation); + } + + protected AnnotationSpec joinColumn(String name, String referencedColumn) { + AnnotationSpec.Builder builder = AnnotationSpec.builder(JoinColumn.class).addMember(NAME, STRING, name); + if (referencedColumn != null) builder.addMember(REFERENCED_COLUMN_NAME, STRING, referencedColumn); + return builder.build(); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java new file mode 100644 index 0000000..defd022 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java @@ -0,0 +1,33 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.repository; + +import com.squareup.javapoet.TypeName; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class RepositoriesGenerator extends AbstractGenerator { + + private Map builders = new HashMap<>(); + + public RepositoriesGenerator(Collection entities, EntitiesData entitiesData, String basePackage) { + entities.forEach(entity -> { + String name = entity.getName(); + TypeName idTypeName = entitiesData.getIDType(name); + builders.put(name, new RepositoryBuilder(name, basePackage, idTypeName)); + }); + } + + @Override + protected Collection getBuilders() { + return new ArrayList<>(builders.values()); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java new file mode 100644 index 0000000..058ab68 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java @@ -0,0 +1,69 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.repository; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.springframework.stereotype.Repository; + +public class RepositoryBuilder extends AbstractClassBuilder { + + private String entityName; + private String basePackage; + + private TypeName identifierType; + private TypeName entityType; + + public RepositoryBuilder(String entityName, String basePackage, TypeName identifierType) { + this.entityName = entityName; + this.basePackage = basePackage; + this.entityType = EntityBuilder.getTypeName(entityName, basePackage); + this.identifierType = identifierType; + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), getName(entityName)); + } + + public static String getName(String entityName) { + return entityName + "Repository"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName); + } + + @Override + public String getPackage() { + return getPackage(entityName, basePackage); + } + + @Override + protected void initialize() { + initializeBuilder(); + addSuperinterface(); + addAnnotations(); + } + + private void initializeBuilder() { + builder = getInterface(getName(entityName)); + } + + private void addSuperinterface() { + builder.addSuperinterface(getInterface()); + } + + private void addAnnotations() { + builder.addAnnotation(Repository.class); + } + + private ParameterizedTypeName getInterface() { + return ParameterizedTypeName.get( + ClassName.get(ApigenRepository.class), + entityType, + identifierType + ); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilder.java new file mode 100644 index 0000000..69760fd --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilder.java @@ -0,0 +1,204 @@ +package net.cloudappi.apigen.generatorcore.generator.service; + +import com.squareup.javapoet.*; +import net.cloudappi.apigen.archetypecore.core.AbstractRelationsManager; +import net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors; +import net.cloudappi.apigen.archetypecore.exceptions.RelationalErrorsException; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData.AttributeData; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import javax.lang.model.element.Modifier; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.ENUM_VALUE; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.PROPAGATION; + +public class RelationManagerBuilder extends AbstractClassBuilder { + + private String basePackage; + private String entityName; + private List sortedRelatedEntities; + private Map attributes; + + public RelationManagerBuilder(String entityName, String basePackage, Map attributes) { + this.entityName = entityName; + this.basePackage = basePackage; + this.attributes = attributes; + this.sortedRelatedEntities = attributes.values().stream() + .filter(AttributeData::isOwned) + .map(AttributeData::getRelatedEntity) + .distinct().sorted().collect(Collectors.toList()); + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), getName(entityName)); + } + + private static String getName(String entityName) { + return entityName + "RelationManager"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName); + } + + @Override + public String getPackage() { + return getPackage(this.entityName, this.basePackage); + } + + @Override + protected void initialize() { + initializeBuilder(); + addServiceFields(); + addCreateOrRetrieveRelationsMethod(); + addUpdateRelationsMethod(); + addCreateOrRetrieveMethods(); + addRetrieveMethods(); + } + + private void initializeBuilder() { + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + builder = getClass(getName(entityName)).superclass( + ParameterizedTypeName.get(ClassName.get(AbstractRelationsManager.class), entityType)) + .addAnnotation(Component.class); + } + + private void addServiceFields() { + sortedRelatedEntities.forEach(relatedEntityName -> { + TypeName serviceType = ServiceBuilder.getTypeName(relatedEntityName, basePackage); + String serviceName = StringUtils.uncapitalize(ServiceBuilder.getName(relatedEntityName)); + FieldSpec fieldSpec = FieldSpec.builder(serviceType, serviceName, Modifier.PRIVATE) + .addAnnotation(Autowired.class).build(); + builder.addField(fieldSpec); + }); + } + + private void addCreateOrRetrieveRelationsMethod() { + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + String paramName = StringUtils.uncapitalize(entityName); + List sortedAttributes = attributes.keySet().stream().sorted().collect(Collectors.toList()); + MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("createOrRetrieveRelations") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addAnnotation(getAnnotation(Transactional.class).addMember(PROPAGATION, ENUM_VALUE, Propagation.class, Propagation.MANDATORY.name()).build()) + .addParameter(entityType, paramName); + methodSpecBuilder.addStatement("$1T errors = new $1T()", RelationalErrors.class); + for (String attribute : sortedAttributes) { + AttributeData attributeData = attributes.get(attribute); + if (!attributeData.isOwned()) continue; + String capAttribute = StringUtils.capitalize(attribute); + + methodSpecBuilder.beginControlFlow("if ($L.get$L() != null)", paramName, capAttribute); + if (attributeData.isCollection()) { + methodSpecBuilder.addStatement("$1L.set$2L($1L.get$2L().stream().map(e -> createOrRetrieve(e, errors)).collect($3T.toSet()))", + paramName, capAttribute, Collectors.class); + } else { + methodSpecBuilder.addStatement("$1L.set$2L(createOrRetrieve($1L.get$2L(), errors))", paramName, capAttribute); + } + methodSpecBuilder.endControlFlow(); + } + methodSpecBuilder.beginControlFlow("if (!errors.isEmpty())"); + methodSpecBuilder.addStatement("throw new $T(errors)", RelationalErrorsException.class); + methodSpecBuilder.endControlFlow(); + + builder.addMethod(methodSpecBuilder.build()); + } + + private void addUpdateRelationsMethod() { + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + String persistedParamName = "persisted" + entityName; + String paramName = StringUtils.uncapitalize(entityName); + List sortedAttributes = attributes.keySet().stream().sorted().collect(Collectors.toList()); + MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("updateRelations") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addAnnotation(getAnnotation(Transactional.class).addMember(PROPAGATION, ENUM_VALUE, Propagation.class, Propagation.MANDATORY.name()).build()) + .addParameter(entityType, persistedParamName) + .addParameter(entityType, paramName) + .addParameter(ParameterizedTypeName.get(ClassName.get(Set.class), ClassName.get(String.class)), "fields"); + + methodSpecBuilder.addStatement("$1T errors = new $1T()", RelationalErrors.class); + methodSpecBuilder.addStatement("boolean updateAll = (fields == null)"); + for (String attribute : sortedAttributes) { + AttributeData attributeData = attributes.get(attribute); + if (!attributeData.isOwned()) continue; + String capAttribute = StringUtils.capitalize(attribute); + + methodSpecBuilder.beginControlFlow("if (updateAll || fields.contains($S))", attribute); + methodSpecBuilder.beginControlFlow("if ($L.get$L() != null)", paramName, capAttribute); + if (attributeData.isCollection()) { + methodSpecBuilder.addStatement("$1L.get$2L().clear()", persistedParamName, capAttribute); + methodSpecBuilder.addStatement("$1L.get$3L().addAll($2L.get$3L().stream().map(e -> retrieve(e, errors)).collect($4T.toSet()))", + persistedParamName, paramName, capAttribute, Collectors.class); + } else { + methodSpecBuilder.addStatement("$1L.set$3L(retrieve($2L.get$3L(), errors))", persistedParamName, paramName, capAttribute); + } + methodSpecBuilder.nextControlFlow("else"); + methodSpecBuilder.addStatement("$L.set$L(null)", persistedParamName, capAttribute); + methodSpecBuilder.endControlFlow(); + methodSpecBuilder.endControlFlow(); + } + methodSpecBuilder.beginControlFlow("if (!errors.isEmpty())"); + methodSpecBuilder.addStatement("throw new $T(errors)", RelationalErrorsException.class); + methodSpecBuilder.endControlFlow(); + + builder.addMethod(methodSpecBuilder.build()); + } + + private void addCreateOrRetrieveMethods() { + sortedRelatedEntities.forEach(this::addCreateOrRetrieveMethod); + } + + private void addCreateOrRetrieveMethod(String entityName) { + String serviceName = StringUtils.uncapitalize(ServiceBuilder.getName(entityName)); + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + String paramName = StringUtils.uncapitalize(entityName); + MethodSpec methodSpec = MethodSpec.methodBuilder("createOrRetrieve") + .addModifiers(Modifier.PRIVATE) + .addParameter(entityType, paramName) + .addParameter(RelationalErrors.class, "errors") + .returns(entityType) + .beginControlFlow("if ($L.isReference())", paramName) + .addStatement("return retrieve($L, errors)", paramName) + .nextControlFlow("else", paramName) + .beginControlFlow("try") + .addStatement("return $L.create($L)", serviceName, paramName) + .nextControlFlow("catch ($T e)", RelationalErrorsException.class) + .addStatement("errors.merge(e.getRelationalErrors())") + .addStatement("return null") + .endControlFlow() + .endControlFlow() + .build(); + builder.addMethod(methodSpec); + } + + private void addRetrieveMethods() { + sortedRelatedEntities.forEach(this::addRetrieveMethod); + } + + private void addRetrieveMethod(String entityName) { + String serviceName = StringUtils.uncapitalize(ServiceBuilder.getName(entityName)); + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + String paramName = StringUtils.uncapitalize(entityName); + MethodSpec methodSpec = MethodSpec.methodBuilder("retrieve") + .addModifiers(Modifier.PRIVATE) + .addParameter(entityType, paramName) + .addParameter(RelationalErrors.class, "errors") + .returns(entityType) + .addStatement("$1T retrieved = ($2L.getId() == null) ? null : $3L.getOne($2L.getId()).orElse(null)", entityType, paramName, serviceName) + .addStatement("if (retrieved == null) errors.register($T.class, $L.getId())", entityType, paramName) + .addStatement("return retrieved") + .build(); + builder.addMethod(methodSpec); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagersGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagersGenerator.java new file mode 100644 index 0000000..edc2256 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagersGenerator.java @@ -0,0 +1,28 @@ +package net.cloudappi.apigen.generatorcore.generator.service; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Slf4j +public class RelationManagersGenerator extends AbstractGenerator { + + private List builders = new ArrayList<>(); + + public RelationManagersGenerator(Collection entities, EntitiesData entitiesData, String basePackage) { + for (Entity entity : entities) { + builders.add(new RelationManagerBuilder(entity.getName(), basePackage, entitiesData.getAttributes(entity.getName()))); + } + } + + @Override + protected Collection getBuilders() { + return new ArrayList<>(builders); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilder.java new file mode 100644 index 0000000..2d371c5 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilder.java @@ -0,0 +1,110 @@ +package net.cloudappi.apigen.generatorcore.generator.service; + +import com.squareup.javapoet.*; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.core.AbstractCrudService; +import net.cloudappi.apigen.archetypecore.core.AbstractRelationsManager; +import net.cloudappi.apigen.archetypecore.core.ApigenMapper; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import net.cloudappi.apigen.generatorcore.generator.persistence.repository.RepositoryBuilder; +import org.apache.commons.lang3.StringUtils; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Service; + +import javax.lang.model.element.Modifier; +import java.util.Set; + +public class ServiceBuilder extends AbstractClassBuilder { + + private String entityName; + private String basePackage; + private TypeName identifierType; + private TypeName entityType; + private TypeName repositoryType; + private Set basicAttributes; + + public ServiceBuilder(String entityName, String basePackage, Set basicAttributes, TypeName identifierType) { + this.entityName = entityName; + this.basePackage = basePackage; + this.entityType = EntityBuilder.getTypeName(entityName, basePackage); + this.repositoryType = RepositoryBuilder.getTypeName(entityName, basePackage); + this.identifierType = identifierType; + this.basicAttributes = basicAttributes; + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), getName(entityName)); + } + + public static String getName(String entityName) { + return entityName + "Service"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName); + } + + @Override + public String getPackage() { + return getPackage(entityName, basePackage); + } + + @Override + protected void initialize() { + initializeBuilder(); + addSuperclass(); + addAnnotations(); + addConstructor(); + addUpdateBasicDataPartiallyMethod(); + } + + private void initializeBuilder() { + builder = getClass(getName(entityName)); + } + + private void addSuperclass() { + builder.superclass(getParentClass()); + } + + private void addAnnotations() { + builder.addAnnotation(Slf4j.class) + .addAnnotation(Service.class); + + } + + private void addConstructor() { + TypeName relationsManagerType = ParameterizedTypeName.get(ClassName.get(AbstractRelationsManager.class), entityType); + TypeName mapperType = ParameterizedTypeName.get(ClassName.get(ApigenMapper.class), entityType); + builder.addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) + .addParameter(repositoryType, "repository") + .addParameter(ParameterSpec.builder(relationsManagerType, "relationsManager").addAnnotation(Nullable.class).build()) + .addParameter(ParameterSpec.builder(mapperType, "mapper").addAnnotation(Nullable.class).build()) + .addStatement("super(repository, relationsManager, mapper)").build()); + } + + private ParameterizedTypeName getParentClass() { + return ParameterizedTypeName.get(ClassName.get(AbstractCrudService.class), + entityType, + identifierType, + repositoryType + ); + } + + private void addUpdateBasicDataPartiallyMethod() { + MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("updateBasicDataPartially") + .addModifiers(Modifier.PROTECTED) + .addAnnotation(Override.class) + .addParameter(entityType, "persistedEntity") + .addParameter(entityType, "entity") + .addParameter(ParameterizedTypeName.get(ClassName.get(Set.class), ClassName.get(String.class)), "fields"); + + methodBuilder.beginControlFlow("if (fields == null)"); + methodBuilder.addStatement("mapper.updateBasicData(entity, persistedEntity)"); + methodBuilder.nextControlFlow("else"); + basicAttributes.forEach(attribute -> methodBuilder.addStatement("if (fields.contains($1S)) persistedEntity.set$2L(entity.get$2L())", attribute, StringUtils.capitalize(attribute))); + methodBuilder.endControlFlow(); + + builder.addMethod(methodBuilder.build()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGenerator.java new file mode 100644 index 0000000..133dcfc --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGenerator.java @@ -0,0 +1,29 @@ +package net.cloudappi.apigen.generatorcore.generator.service; + +import com.squareup.javapoet.TypeName; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; + +import java.util.*; + +@Slf4j +public class ServicesGenerator extends AbstractGenerator { + private Map builders = new HashMap<>(); + + public ServicesGenerator(Collection entities, EntitiesData entitiesData, String basePackage) { + entities.forEach(entity -> { + String name = entity.getName(); + Set basicAttributes = entitiesData.getBasicAttributes(name); + TypeName idTypeName = entitiesData.getIDType(name); + builders.put(name, new ServiceBuilder(name, basePackage, basicAttributes, idTypeName)); + }); + } + + @Override + protected Collection getBuilders() { + return new ArrayList<>(builders.values()); + } +} \ No newline at end of file diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilder.java new file mode 100644 index 0000000..587842f --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilder.java @@ -0,0 +1,71 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.TypeSpec; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointBuilderFactory; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.lang.model.element.Modifier; +import java.util.List; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.TAGS; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; + +public abstract class ControllerBuilder extends AbstractClassBuilder { + + protected String basePackage; + protected Mapping requestMapping; + protected EntitiesData entitiesData; + protected List endpoints; + + public ControllerBuilder(Controller controller, EntitiesData entitiesData, String basePackage) { + this.basePackage = basePackage; + this.entitiesData = entitiesData; + this.requestMapping = new Mapping(controller.getMapping()); + this.endpoints = controller.getEndpoints(); + } + + protected abstract String getName(); + + protected abstract String getTag(); + + protected void initializeBuilder() { + builder = TypeSpec + .classBuilder(getName()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Slf4j.class) + .addAnnotation(RestController.class) + .addAnnotation( + AnnotationSpec.builder(RequestMapping.class) + .addMember(VALUE, STRING, requestMapping.getValue()) + .build() + ).addAnnotation( + AnnotationSpec.builder(Api.class) + .addMember(TAGS, STRING, getTag()) + .build() + ) + .addAnnotation(Validated.class); + } + + protected void generateEndpoints() { + for (Endpoint endpoint : endpoints) { + generateEndpoint(requestMapping, endpoint); + } + } + + private void generateEndpoint(Mapping mapping, Endpoint endpoint) { + EndpointBuilder endpointBuilder = EndpointBuilderFactory.create(mapping, endpoint, entitiesData, basePackage); + endpointBuilder.apply(builder); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java new file mode 100644 index 0000000..5470ae9 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java @@ -0,0 +1,19 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller; + +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; + +public class ControllerBuilderFactory { + + private ControllerBuilderFactory() { + // Intentionally blank + } + + public static ControllerBuilder create(Controller controller, EntitiesData entitiesData, String basePackage) { + if (controller.getEntity() == null) { + return new ResourceControllerBuilder(controller, entitiesData, basePackage); + } else { + return new EntityControllerBuilder(controller, entitiesData, basePackage); + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGenerator.java new file mode 100644 index 0000000..1a79efc --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGenerator.java @@ -0,0 +1,27 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; + +import java.util.ArrayList; +import java.util.Collection; + +@Slf4j +public class ControllersGenerator extends AbstractGenerator { + + private ArrayList builders = new ArrayList<>(); + + public ControllersGenerator(Collection controllers, EntitiesData entitiesData, String basePackage) { + controllers.stream() + .map(c -> ControllerBuilderFactory.create(c, entitiesData, basePackage)) + .forEach(builders::add); + } + + @Override + protected Collection getBuilders() { + return new ArrayList<>(builders); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java new file mode 100644 index 0000000..071fd1a --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java @@ -0,0 +1,75 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.archetypecore.core.resource.ResourceNamingTranslator; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.generator.mapper.MapperBuilder; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import net.cloudappi.apigen.generatorcore.generator.service.ServiceBuilder; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.lang.model.element.Modifier; + +public class EntityControllerBuilder extends ControllerBuilder { + + private String entityName; + + public EntityControllerBuilder(Controller controller, EntitiesData entitiesData, String basePackage) { + super(controller, entitiesData, basePackage); + this.entityName = controller.getEntity(); + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName, "web"); + } + + @Override + protected String getName() { + return entityName + "Controller"; + } + + @Override + protected String getTag() { + return entityName; + } + + @Override + public String getPackage() { + return getPackage(entityName, basePackage); + } + + @Override + public void initialize() { + initializeBuilder(); + autowireService(); + autowireMapper(); + autowireNamingTranslator(); + generateEndpoints(); + } + + private void autowireService() { + TypeName serviceType = ServiceBuilder.getTypeName(entityName, basePackage); + FieldSpec field = FieldSpec.builder(serviceType, "service", Modifier.PRIVATE) + .addAnnotation(Autowired.class) + .build(); + builder.addField(field); + } + + private void autowireMapper() { + TypeName mapperType = MapperBuilder.getTypeName(entityName, basePackage); + FieldSpec field = FieldSpec.builder(mapperType, "mapper", Modifier.PRIVATE) + .addAnnotation(Autowired.class) + .build(); + builder.addField(field); + } + + private void autowireNamingTranslator() { + TypeName translatorType = ClassName.get(ResourceNamingTranslator.class); + FieldSpec field = FieldSpec.builder(translatorType, "namingTranslator", Modifier.PRIVATE) + .addAnnotation(Autowired.class) + .build(); + builder.addField(field); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java new file mode 100644 index 0000000..3495ff8 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java @@ -0,0 +1,32 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller; + +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; + +public class ResourceControllerBuilder extends ControllerBuilder { + + public ResourceControllerBuilder(Controller controller, EntitiesData entitiesData, String basePackage) { + super(controller, entitiesData, basePackage); + } + + @Override + protected String getName() { + return requestMapping.toName() + "Controller"; + } + + @Override + protected String getTag() { + return requestMapping.toName(); + } + + @Override + public String getPackage() { + return concatPackage(basePackage, requestMapping.toName().toLowerCase(), "web"); + } + + @Override + public void initialize() { + initializeBuilder(); + generateEndpoints(); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java new file mode 100644 index 0000000..c4b1942 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java @@ -0,0 +1,108 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.archetypecore.exceptions.NotImplementedException; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.config.controller.Response; +import net.cloudappi.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.ResourceListResponseBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.ResourceSimpleResponseBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; + +import java.util.List; + +public class CustomEndpointBuilder extends EndpointBuilder { + + public CustomEndpointBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + super(rootMapping, endpoint, basePackage); + } + + @Override + protected Class getMappingClass() { + switch (endpoint.getMethod()) { + case DELETE: + return DeleteMapping.class; + case PUT: + return PutMapping.class; + case POST: + return PostMapping.class; + case GET: + return GetMapping.class; + default: + return null; + } + } + + @Override + protected String getMapping() { + if (mapping.isEmpty()) return null; + return mapping.getValue(); + } + + @Override + protected HttpStatus getResponseStatus() { + HttpStatus status = HttpStatus.resolve(endpoint.getResponse().getDefaultStatusCode()); + if (status == null) status = HttpStatus.OK; + return status; + } + + @Override + protected TypeName getReturnTypeName() { + Response response = endpoint.getResponse(); + if (response.getAttributes() == null) return null; + + if (response.getIsStandard()) { + return getStandardTypeName(response); + } else { + return getNonStandardTypeName(response); + } + } + + private TypeName getStandardTypeName(Response response) { + String entityName = response.getRelatedEntity(); + boolean isCollection = response.getIsCollection(); + if (entityName != null) { + if (isCollection) { + return EntityListResponseBuilder.getTypeName(entityName, basePackage); + } else { + return SimpleResponseBuilder.getTypeName(entityName, basePackage); + } + } else { + if (isCollection) { + return ResourceListResponseBuilder.getTypeName(rootMapping, endpoint, basePackage); + } else { + return ResourceSimpleResponseBuilder.getTypeName(rootMapping, endpoint, basePackage); + } + } + } + + private TypeName getNonStandardTypeName(Response response) { + TypeName responseType = ResourceOutputResourceBuilder.getTypeName(rootMapping, endpoint, basePackage); + if (response.getIsCollection()) { + responseType = ParameterizedTypeName.get(ClassName.get(List.class), responseType); + } + return responseType; + } + + @Override + protected TypeName getBodyTypeName() { + if (endpoint.getRequest() == null) return null; + return AllInputResourceBuilder.getTypeName(rootMapping, endpoint, basePackage); + } + + @Override + protected void addStatements() { + builder.addComment("TODO: Implement this non standard endpoint"); + builder.addStatement("throw new $T($S)", NotImplementedException.class, endpoint.getMethod() + " " + this.getMapping()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java new file mode 100644 index 0000000..4740ebf --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java @@ -0,0 +1,38 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.TypeName; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.springframework.web.bind.annotation.DeleteMapping; + +@Slf4j +public class DeleteEndpointBuilder extends EndpointBuilder { + + private TypeName composedIdType; + + public DeleteEndpointBuilder(Mapping rootMapping, Endpoint endpoint, TypeName composedIdType, String basePackage) { + super(rootMapping, endpoint, basePackage); + this.composedIdType = composedIdType; + } + + @Override + protected Class getMappingClass() { + return DeleteMapping.class; + } + + @Override + protected String getMapping() { + return mapping.getValue(); + } + + @Override + protected void addStatements() { + builder.addStatement("$L.delete($L)", SERVICE_NAME, identifierParam); + } + + @Override + protected TypeName getIdentifierPathParamType() { + return composedIdType; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java new file mode 100644 index 0000000..eaf04f9 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java @@ -0,0 +1,165 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.*; +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import net.cloudappi.apigen.generatorcore.generator.web.controller.parameters.ParameterBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.controller.parameters.PathParameterBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.controller.parameters.QueryParameterBuilder; +import net.cloudappi.apigen.generatorcore.utils.CustomStringUtils; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import javax.lang.model.element.Modifier; +import javax.validation.Valid; +import java.util.HashSet; +import java.util.Set; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.ENUM_VALUE; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.CODE; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; + +@Slf4j +public abstract class EndpointBuilder { + + protected static final String SERVICE_NAME = "service"; + protected static final String MAPPER_NAME = "mapper"; + protected static final String NAMING_TRANSLATOR_NAME = "namingTranslator"; + + + protected String basePackage; + protected String entityName; + protected Mapping rootMapping; + protected Mapping mapping; + protected Endpoint endpoint; + + protected Set pathParams = new HashSet<>(); + protected Set queryParams = new HashSet<>(); + protected String identifierParam; + + protected MethodSpec.Builder builder; + + public EndpointBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + this.basePackage = basePackage; + this.rootMapping = rootMapping; + this.entityName = endpoint.getRelatedEntity(); + this.endpoint = endpoint; + this.mapping = new Mapping(endpoint.getMapping()); + } + + public void apply(TypeSpec.Builder builder) { + if (this.builder == null) { + generate(); + } + builder.addMethod(this.builder.build()); + } + + protected void generate() { + generateBuilder(); + addMapping(); + addResponseStatus(); + addReturnType(); + addParams(); + addRequestBody(); + addStatements(); + } + + protected void generateBuilder() { + builder = MethodSpec.methodBuilder(endpoint.getName()) + .addModifiers(Modifier.PUBLIC); + } + + protected void addMapping() { + AnnotationSpec.Builder mappingAnnotation = AnnotationSpec.builder(getMappingClass()); + String mappingValue = getMapping(); + if (mappingValue != null) mappingAnnotation.addMember(VALUE, STRING, mappingValue); + builder.addAnnotation(mappingAnnotation.build()); + } + + protected abstract Class getMappingClass(); + + protected String getMapping() { + return null; + } + + protected void addResponseStatus() { + builder.addAnnotation( + AnnotationSpec.builder(ResponseStatus.class) + .addMember(CODE, ENUM_VALUE, HttpStatus.class, getResponseStatus().name()) + .build() + ); + } + + protected HttpStatus getResponseStatus() { + return HttpStatus.OK; + } + + protected void addReturnType() { + TypeName typeName = getReturnTypeName(); + if (typeName != null) builder.returns(typeName); + } + + protected TypeName getReturnTypeName() { + return null; + } + + protected void addRequestBody() { + TypeName bodyType = getBodyTypeName(); + if (bodyType == null) return; + builder.addParameter(ParameterSpec.builder(bodyType, "body").addAnnotation(RequestBody.class).addAnnotation(Valid.class).build()); + } + + protected TypeName getBodyTypeName() { + return null; + } + + protected abstract void addStatements(); + + protected void addParams() { + endpoint.getParameters().forEach(this::addParam); + } + + private void addParam(Parameter param) { + if ("path".equalsIgnoreCase(param.getIn())) { + addPathParam(param); + } else if ("query".equalsIgnoreCase(param.getIn())) { + addQueryParam(param); + } else { + log.error("Param not supported in {}", param.getIn()); + } + } + + private void addPathParam(Parameter param) { + String javaName = getJavaParamName(param.getName()); + TypeName typeName = null; + if (identifierParam == null) { + identifierParam = javaName; + typeName = getIdentifierPathParamType(); + } + pathParams.add(javaName); + + ParameterBuilder parameterBuilder = + typeName == null ? new PathParameterBuilder(param, javaName) : new PathParameterBuilder(param, typeName, javaName); + builder.addParameter(parameterBuilder.build()); + } + + protected TypeName getIdentifierPathParamType() { + return null; + } + + private void addQueryParam(Parameter param) { + String javaName = getJavaParamName(param.getName()); + queryParams.add(javaName); + + ParameterBuilder parameterBuilder = new QueryParameterBuilder(param, javaName); + builder.addParameter(parameterBuilder.build()); + } + + private String getJavaParamName(String name) { + return CustomStringUtils.snakeCaseToCamelCase(name).replaceAll("\\$", ""); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java new file mode 100644 index 0000000..28b28d0 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java @@ -0,0 +1,44 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import net.cloudappi.apigen.generatorcore.utils.Mapping; + +public class EndpointBuilderFactory { + + private EndpointBuilderFactory() { + // Intentionally blank + } + + public static EndpointBuilder create(Mapping rootMapping, Endpoint endpoint, EntitiesData entitiesData, String basePackage) { + Mapping mapping = new Mapping(endpoint.getMapping()); + Endpoint.Method method = endpoint.getMethod(); + TypeName composedIdType = null; + + boolean isStandardWithEntity = (endpoint.getResponse() == null || endpoint.getResponse().getIsStandard()) && endpoint.getRelatedEntity() != null; + if (isStandardWithEntity) { + composedIdType = entitiesData.getComposedIDType(endpoint.getRelatedEntity()); + } + + boolean isGetAll = isStandardWithEntity && method == Endpoint.Method.GET && mapping.isEmpty(); + if (isGetAll) return new GetAllEndpointBuilder(rootMapping, endpoint, basePackage); + + boolean isGetOne = isStandardWithEntity && method == Endpoint.Method.GET && mapping.isById(); + if (isGetOne) return new GetByIdEndpointBuilder(rootMapping, endpoint, composedIdType, basePackage); + + boolean isPost = isStandardWithEntity && method == Endpoint.Method.POST && mapping.isEmpty(); + if (isPost) return new PostEndpointBuilder(rootMapping, endpoint, basePackage); + + boolean isSearch = isStandardWithEntity && method == Endpoint.Method.POST && mapping.isSearch(); + if (isSearch) return new PostSearchEndpointBuilder(rootMapping, endpoint, basePackage); + + boolean isPut = isStandardWithEntity && method == Endpoint.Method.PUT && mapping.isById(); + if (isPut) return new PutEndpointBuilder(rootMapping, endpoint, composedIdType, basePackage); + + boolean isDelete = isStandardWithEntity && method == Endpoint.Method.DELETE && mapping.isById(); + if (isDelete) return new DeleteEndpointBuilder(rootMapping, endpoint, composedIdType, basePackage); + + return new CustomEndpointBuilder(rootMapping, endpoint, basePackage); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java new file mode 100644 index 0000000..c40fe12 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java @@ -0,0 +1,59 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class GetAllEndpointBuilder extends EndpointBuilder { + + public GetAllEndpointBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + super(rootMapping, endpoint, basePackage); + } + + @Override + protected Class getMappingClass() { + return GetMapping.class; + } + + @Override + protected HttpStatus getResponseStatus() { + return HttpStatus.PARTIAL_CONTENT; + } + + @Override + protected TypeName getReturnTypeName() { + return EntityListResponseBuilder.getTypeName(entityName, basePackage); + } + + @Override + protected void addStatements() { + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + TypeName searchResultType = ParameterizedTypeName.get(ClassName.get(ApigenSearchResult.class), entityType); + TypeName resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + TypeName listResourceType = ParameterizedTypeName.get(ClassName.get(List.class), resourceType); + TypeName responseType = EntityListResponseBuilder.getTypeName(entityName, basePackage); + String translatorParams = pathParamsToString(Arrays.asList("select", "exclude", "expand", "orderby")); + String params = pathParamsToString(Arrays.asList("select", "exclude", "expand", "filter", "orderby", "init", "limit", "total")); + String pageParams = pathParamsToString(Arrays.asList("init", "limit")); + builder.addStatement("$L.translate($L, $T.class)", NAMING_TRANSLATOR_NAME, translatorParams, resourceType); + builder.addStatement("$T searchResult = $L.search($L)", searchResultType, SERVICE_NAME, params); + builder.addStatement("$T result = $L.toResource(searchResult.getSearchResult())", listResourceType, MAPPER_NAME); + builder.addStatement("return new $T(result).withMetadataPagination($L, searchResult.getTotal())", responseType, pageParams); + } + + private String pathParamsToString(List names) { + return names.stream().map(n -> queryParams.contains(n) ? n : "null").collect(Collectors.joining(", ")); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java new file mode 100644 index 0000000..a7aa4f4 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java @@ -0,0 +1,60 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; +import org.springframework.web.bind.annotation.GetMapping; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class GetByIdEndpointBuilder extends EndpointBuilder { + + private TypeName composedIdType; + + public GetByIdEndpointBuilder(Mapping rootMapping, Endpoint endpoint, TypeName composedIdType, String basePackage) { + super(rootMapping, endpoint, basePackage); + this.composedIdType = composedIdType; + } + + @Override + protected Class getMappingClass() { + return GetMapping.class; + } + + @Override + protected String getMapping() { + return mapping.getValue(); + } + + @Override + protected TypeName getReturnTypeName() { + return SimpleResponseBuilder.getTypeName(entityName, basePackage); + } + + @Override + protected void addStatements() { + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + TypeName resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + TypeName responseType = SimpleResponseBuilder.getTypeName(entityName, basePackage); + String translatorParams = pathParamsToString(Arrays.asList("select", "exclude", "expand")); + String params = pathParamsToString(Arrays.asList("select", "exclude", "expand")); + builder.addStatement("$L.translate($L, $T.class)", NAMING_TRANSLATOR_NAME, translatorParams, resourceType); + builder.addStatement("$T searchResult = $L.search($L, $L)", entityType, SERVICE_NAME, identifierParam, params); + builder.addStatement("$T result = $L.toResource(searchResult)", resourceType, MAPPER_NAME); + builder.addStatement("return new $T(result)", responseType); + } + + private String pathParamsToString(List names) { + return names.stream().map(n -> queryParams.contains(n) ? n : "null").collect(Collectors.joining(", ")); + } + + @Override + protected TypeName getIdentifierPathParamType() { + return composedIdType; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java new file mode 100644 index 0000000..bd8c90a --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java @@ -0,0 +1,55 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import javax.validation.Valid; + +public class PostEndpointBuilder extends EndpointBuilder { + + public PostEndpointBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + super(rootMapping, endpoint, basePackage); + } + + @Override + protected Class getMappingClass() { + return PostMapping.class; + } + + @Override + protected HttpStatus getResponseStatus() { + return HttpStatus.CREATED; + } + + @Override + protected TypeName getReturnTypeName() { + return SimpleResponseBuilder.getTypeName(entityName, basePackage); + } + + @Override + protected void addRequestBody() { + TypeName bodyType = AllInputResourceBuilder.getTypeName(endpoint, basePackage); + builder.addParameter(ParameterSpec.builder(bodyType, "body").addAnnotation(RequestBody.class).addAnnotation(Valid.class).build()); + } + + @Override + protected void addStatements() { + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + TypeName resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + TypeName responseType = SimpleResponseBuilder.getTypeName(entityName, basePackage); + builder.addStatement("$T createRequest = $L.toEntity(body)", entityType, MAPPER_NAME); + builder.addStatement("$L.create(createRequest)", SERVICE_NAME); + builder.addStatement("$T createResult = $L.search(createRequest.getId(), null, null, null)", entityType, SERVICE_NAME); + builder.addStatement("$T result = $L.toResource(createResult)", resourceType, MAPPER_NAME); + builder.addStatement("return new $T(result)", responseType); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java new file mode 100644 index 0000000..3f74ed0 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java @@ -0,0 +1,73 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; +import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import net.cloudappi.apigen.archetypecore.core.resource.FilterResource; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PostMapping; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +public class PostSearchEndpointBuilder extends EndpointBuilder { + + public PostSearchEndpointBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + super(rootMapping, endpoint, basePackage); + } + + @Override + protected Class getMappingClass() { + return PostMapping.class; + } + + @Override + protected String getMapping() { + return mapping.getValue(); + } + + @Override + protected HttpStatus getResponseStatus() { + return HttpStatus.PARTIAL_CONTENT; + } + + @Override + protected TypeName getReturnTypeName() { + return EntityListResponseBuilder.getTypeName(entityName, basePackage); + } + + @Override + protected TypeName getBodyTypeName() { + return ClassName.get(FilterResource.class); + } + + @Override + protected void addStatements() { + TypeName filterType = ClassName.get(Filter.class); + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + TypeName searchResultType = ParameterizedTypeName.get(ClassName.get(ApigenSearchResult.class), entityType); + TypeName resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + TypeName listResourceType = ParameterizedTypeName.get(ClassName.get(List.class), resourceType); + TypeName responseType = EntityListResponseBuilder.getTypeName(entityName, basePackage); + String translatorParams = pathParamsAndFilterToString(Arrays.asList("select", "exclude", "expand", "filter", "orderby")); + String params = pathParamsAndFilterToString(Arrays.asList("select", "exclude", "expand", "filter", "orderby", "init", "limit", "total")); + String pageParams = pathParamsAndFilterToString(Arrays.asList("init", "limit")); + builder.addStatement("$T filter = body.getFilter()", filterType); + builder.addStatement("$L.translate($L, $T.class)", NAMING_TRANSLATOR_NAME, translatorParams, resourceType); + builder.addStatement("$T searchResult = $L.search($L)", searchResultType, SERVICE_NAME, params); + builder.addStatement("$T result = $L.toResource(searchResult.getSearchResult())", listResourceType, MAPPER_NAME); + builder.addStatement("return new $T(result).withMetadataPagination($L, searchResult.getTotal())", responseType, pageParams); + } + + private String pathParamsAndFilterToString(List names) { + return names.stream().map(n -> queryParams.contains(n) || n.equals("filter") ? n : "null").collect(Collectors.joining(", ")); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java new file mode 100644 index 0000000..523938f --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java @@ -0,0 +1,74 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestAttribute; + +import java.util.Set; + +public class PutEndpointBuilder extends EndpointBuilder { + + private TypeName composedIdType; + + public PutEndpointBuilder(Mapping rootMapping, Endpoint endpoint, TypeName composedIdType, String basePackage) { + super(rootMapping, endpoint, basePackage); + this.composedIdType = composedIdType; + } + + @Override + protected void generate() { + super.generate(); + addUpdatedFieldsParam(); + } + + @Override + protected Class getMappingClass() { + return PutMapping.class; + } + + @Override + protected String getMapping() { + return mapping.getValue(); + } + + @Override + protected TypeName getReturnTypeName() { + return SimpleResponseBuilder.getTypeName(entityName, basePackage); + } + + @Override + protected TypeName getBodyTypeName() { + return AllInputResourceBuilder.getTypeName(endpoint, basePackage); + } + + private void addUpdatedFieldsParam() { + TypeName updatedFieldsType = ParameterizedTypeName.get(ClassName.get(Set.class), ClassName.get(String.class)); + builder.addParameter(ParameterSpec.builder(updatedFieldsType, "updatedFields").addAnnotation(RequestAttribute.class).build()); + } + + @Override + protected void addStatements() { + TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); + TypeName resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + TypeName responseType = SimpleResponseBuilder.getTypeName(entityName, basePackage); + builder.addStatement("$T updateRequest = $L.toEntity(body)", entityType, MAPPER_NAME); + builder.addStatement("$L.update($L, updateRequest, updatedFields)", SERVICE_NAME, identifierParam); + builder.addStatement("$T createResult = $L.search($L, null, null, null)", entityType, SERVICE_NAME, identifierParam); + builder.addStatement("$T result = $L.toResource(createResult)", resourceType, MAPPER_NAME); + builder.addStatement("return new $T(result)", responseType); + } + + @Override + protected TypeName getIdentifierPathParamType() { + return composedIdType; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java new file mode 100644 index 0000000..b83ae23 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java @@ -0,0 +1,47 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.parameters; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; + +import java.util.List; + +public abstract class ParameterBuilder { + + protected Parameter parameter; + protected String javaName; + protected TypeName typeName; + + protected ParameterSpec.Builder builder; + + public ParameterBuilder(Parameter parameter, String javaName) { + this.parameter = parameter; + this.javaName = javaName; + this.typeName = Openapi2JavapoetType.transformSimpleType(parameter.getType(), parameter.getFormat()); + if (parameter.isCollection()) typeName = ParameterizedTypeName.get(ClassName.get(List.class), typeName); + } + + public ParameterBuilder(Parameter parameter, TypeName typeName, String javaName) { + this.parameter = parameter; + this.typeName = typeName; + this.javaName = javaName; + } + + protected abstract void initialize(); + + public ParameterSpec build() { + if (builder == null) initialize(); + return builder.build(); + } + + protected void initializeBuilder() { + builder = ParameterSpec.builder(typeName, javaName); + } + + protected void addValidations() { + parameter.getValidations().forEach(v -> v.apply(builder)); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java new file mode 100644 index 0000000..fe43651 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java @@ -0,0 +1,34 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.parameters; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import org.springframework.web.bind.annotation.PathVariable; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; + +public class PathParameterBuilder extends ParameterBuilder { + + public PathParameterBuilder(Parameter parameter, String javaName) { + super(parameter, javaName); + } + + public PathParameterBuilder(Parameter parameter, TypeName typeName, String javaName) { + super(parameter, typeName, javaName); + } + + @Override + protected void initialize() { + initializeBuilder(); + addPathVariableAnnotation(); + addValidations(); + } + + private void addPathVariableAnnotation() { + AnnotationSpec.Builder annotation = AnnotationSpec.builder(PathVariable.class) + .addMember(VALUE, STRING, parameter.getName()); + builder.addAnnotation(annotation.build()); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java new file mode 100644 index 0000000..f54a7f9 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java @@ -0,0 +1,55 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.parameters; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.squareup.javapoet.AnnotationSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.ArrayList; +import java.util.List; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; + +public class QueryParameterBuilder extends ParameterBuilder { + + public QueryParameterBuilder(Parameter parameter, String javaName) { + super(parameter, javaName); + } + + @Override + protected void initialize() { + initializeBuilder(); + addRequestParamAnnotation(); + addValidations(); + } + + private void addRequestParamAnnotation() { + AnnotationSpec.Builder annotation = AnnotationSpec.builder(RequestParam.class) + .addMember(VALUE, STRING, parameter.getName()); + + if (parameter.getRequired() != null) { + annotation.addMember(REQUIRED, LITERAL, parameter.getRequired()); + } + if (parameter.getDefaultValue() != null) { + String defaultValue; + if (parameter.isCollection()) { + defaultValue = collectionToString(parameter.getDefaultValue()); + } else { + defaultValue = parameter.getDefaultValue().toString(); + } + annotation.addMember(DEFAULT_VALUE, STRING, defaultValue); + } + + builder.addAnnotation(annotation.build()); + } + + private String collectionToString(Object collection) { + ArrayNode arrayNode = (ArrayNode) collection; + List itemsArray = new ArrayList<>(arrayNode.size()); + arrayNode.forEach(jsonNode -> itemsArray.add(jsonNode.asText())); + return String.join(",", itemsArray); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceBuilder.java new file mode 100644 index 0000000..706cff7 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceBuilder.java @@ -0,0 +1,7 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource; + +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; + +public abstract class ResourceBuilder extends AbstractClassBuilder { + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesData.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesData.java new file mode 100644 index 0000000..07a10e8 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesData.java @@ -0,0 +1,23 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource; + +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.generator.web.resource.input.InputResourceBuilder; + +import java.util.*; + +public class ResourcesData { + private Map> resourcesToEntity = new HashMap<>(); + + public ResourcesData(List inputResourceBuilders) { + inputResourceBuilders.forEach(builder -> { + String entityName = builder.getEntityName(); + resourcesToEntity.putIfAbsent(entityName, new HashSet<>()); + resourcesToEntity.get(entityName).add(builder.getTypeName()); + }); + } + + + public Set getInputResources(String entityName) { + return resourcesToEntity.getOrDefault(entityName, Collections.emptySet()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java new file mode 100644 index 0000000..87ca5a3 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java @@ -0,0 +1,59 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.config.controller.Request; +import net.cloudappi.apigen.generatorcore.config.controller.Response; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import net.cloudappi.apigen.generatorcore.generator.web.resource.input.InputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.input.InputResourceBuilderFactory; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.OutputResourceBuilder; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.OutputResourceBuilderFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Slf4j +public class ResourcesGenerator extends AbstractGenerator { + + private List inputBuilders = new ArrayList<>(); + private List outputBuilders = new ArrayList<>(); + private ResourcesData resourcesData; + + public ResourcesGenerator(Collection controllers, String basePackage) { + for (Controller controller : controllers) { + for (Endpoint endpoint : controller.getEndpoints()) { + processRequest(controller, endpoint, basePackage); + processResponse(controller, endpoint, basePackage); + } + } + resourcesData = new ResourcesData(inputBuilders); + } + + private void processRequest(Controller controller, Endpoint endpoint, String basePackage) { + Request request = endpoint.getRequest(); + if (request == null) return; + inputBuilders.add(InputResourceBuilderFactory.create(controller, endpoint, basePackage)); + } + + private void processResponse(Controller controller, Endpoint endpoint, String basePackage) { + Response response = endpoint.getResponse(); + if (response == null || response.getAttributes() == null) return; + outputBuilders.add(OutputResourceBuilderFactory.create(controller, endpoint, basePackage)); + } + + @Override + protected Collection getBuilders() { + ArrayList builders = new ArrayList<>(); + builders.addAll(inputBuilders); + builders.addAll(outputBuilders); + return builders; + } + + public ResourcesData getResourcesData() { + return resourcesData; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java new file mode 100644 index 0000000..3865aab --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java @@ -0,0 +1,183 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.input; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.squareup.javapoet.*; +import lombok.Data; +import lombok.NoArgsConstructor; +import net.cloudappi.apigen.generatorcore.config.controller.Attribute; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.config.controller.Request; +import net.cloudappi.apigen.generatorcore.config.validation.Validation; +import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; +import org.apache.commons.lang3.StringUtils; + +import javax.validation.Valid; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static java.util.Objects.nonNull; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.MODE; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; + +public class AllInputResourceBuilder extends InputResourceBuilder { + + private String entityName; + private String basePackage; + private Mapping rootMapping; + private List attributes; + private Endpoint endpoint; + + public AllInputResourceBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + Request endpointRequestBody = endpoint.getRequest(); + this.basePackage = basePackage; + this.entityName = endpointRequestBody.getRelatedEntity(); + this.rootMapping = rootMapping; + this.attributes = endpointRequestBody.getAttributes(); + this.endpoint = endpoint; + } + + // FIXME analyze separation + public static TypeName getTypeName(Endpoint endpoint, String basePackage) { + String rootEntityName = endpoint.getRelatedEntity(); + return ClassName.get(getPackage(rootEntityName, basePackage), getName(new Mapping(null), endpoint)); + } + + // FIXME analyze separation + public static TypeName getTypeName(Mapping rootMapping, Endpoint endpoint, String basePackage) { + String rootEntityName = endpoint.getRelatedEntity(); + if (rootEntityName == null) rootEntityName = rootMapping.toName().toLowerCase(); + return ClassName.get(getPackage(rootEntityName, basePackage), getName(rootMapping, endpoint)); + } + + private static String getName(Mapping rootMapping, Endpoint endpoint) { + String rootEntityName = endpoint.getRelatedEntity(); + String entityName = endpoint.getRequest().getRelatedEntity(); + if (entityName == null) entityName = rootEntityName; + if (entityName == null) entityName = rootMapping.toName(); + Endpoint.Method method = endpoint.getMethod(); + Mapping mapping = new Mapping(endpoint.getMapping()); + String mappingName = mapping.toName(); + return method.prefix + entityName + mappingName + "Resource"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName, "web"); + } + + @Override + public TypeName getTypeName() { + return getTypeName(rootMapping, endpoint, basePackage); + } + + @Override + public String getEntityName() { + return entityName; + } + + @Override + public String getPackage() { + if (entityName == null) { // FIXME analyze separation + return getPackage(rootMapping.toName().toLowerCase(), basePackage); + } else { + return getPackage(entityName, basePackage); + } + } + + @Override + protected void initialize() { + initializeBuilder(); + addAttributes(); + } + + private void initializeBuilder() { + builder = getClass(getName(rootMapping, endpoint)) + .addAnnotation(Data.class); + } + + private void addAttributes() { + addAttributes(attributes, builder, getTypeName(rootMapping, endpoint, basePackage)); + } + + private void addAttributes(List attributes, TypeSpec.Builder builder, TypeName parentType) { + + for (Attribute attribute : attributes) { + boolean requiresNestedObject = requiredNestedObject(attribute); + String javaName = attribute.getEntityFieldName(); + TypeName type; + boolean nested = false; + + if (requiresNestedObject) { + type = createNestedObject(javaName, attribute.getAttributes(), parentType); + nested = true; + } else { + type = Openapi2JavapoetType.transformSimpleType(attribute.getType(), attribute.getFormat()); + boolean isUnwrappedIdentifier = isUnwrappedIdentifier(attribute); + if (isUnwrappedIdentifier) { + type = createIdentifierNestedObject(javaName, type, parentType); + javaName = javaName.split("\\.")[0]; + nested = true; + } + } + if (attribute.isCollection()) { + type = ParameterizedTypeName.get(ClassName.get(Set.class), type); + } + addAttribute(type, javaName, attribute.getName(), attribute.getValidations(), nested, builder); + } + } + + private boolean requiredNestedObject(Attribute attribute) { + return Openapi2JavapoetType.TYPE_OBJECT.equals(attribute.getType()) || Openapi2JavapoetType.TYPE_ARRAY.equals(attribute.getType()); + } + + private TypeName createNestedObject(String javaName, List attributes, TypeName parentType) { + String nestedName = StringUtils.capitalize(javaName); + TypeName type = ((ClassName) parentType).nestedClass(nestedName); + TypeSpec.Builder nestedBuilder = getPublicInnerClass(nestedName).addAnnotation(Data.class); + addAttributes(attributes, nestedBuilder, type); + builder.addType(nestedBuilder.build()); + return type; + } + + private boolean isUnwrappedIdentifier(Attribute attribute) { + return nonNull(attribute.getEntityFieldName()) && nonNull(attribute.getRelatedEntity()) + && attribute.getAttributes().isEmpty() && StringUtils.countMatches(attribute.getEntityFieldName(), '.') == 1; + } + + private TypeName createIdentifierNestedObject(String javaName, TypeName type, TypeName parentType) { + String[] nameParts = javaName.split("\\."); + javaName = nameParts[0]; + String nestedName = StringUtils.capitalize(javaName); + String nestedAttributeName = nameParts[1]; + TypeName nestedAttributeType = type; + type = ((ClassName) parentType).nestedClass(nestedName); + TypeSpec.Builder nestedAttributeBuilder = getPublicInnerClass(nestedName).addAnnotation(Data.class).addAnnotation(NoArgsConstructor.class); + MethodSpec constructor = MethodSpec.constructorBuilder() + .addAnnotation(AnnotationSpec.builder(JsonCreator.class).addMember(MODE, LITERAL, "JsonCreator.Mode.DELEGATING").build()) + .addParameter(nestedAttributeType, nestedAttributeName) + .addStatement("this.$1L = $1L", nestedAttributeName) + .build(); + nestedAttributeBuilder.addMethod(constructor); + addAttribute(nestedAttributeType, nestedAttributeName, nestedAttributeName, Collections.emptyList(), false, nestedAttributeBuilder); + builder.addType(nestedAttributeBuilder.build()); + return type; + } + + + private void addAttribute(TypeName type, String name, String jsonName, List validations, boolean nested, TypeSpec.Builder builder) { + FieldSpec.Builder fieldBuilder = getField(type, name); + addJsonName(jsonName, fieldBuilder); + validations.forEach(validation -> validation.apply(fieldBuilder)); + if (nested) fieldBuilder.addAnnotation(Valid.class); + builder.addField(fieldBuilder.build()); + } + + private void addJsonName(String name, FieldSpec.Builder builder) { + builder.addAnnotation(getAnnotation(JsonProperty.class).addMember(VALUE, STRING, name).build()); + } +} + diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java new file mode 100644 index 0000000..4a0f9e6 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java @@ -0,0 +1,11 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.input; + +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourceBuilder; + +public abstract class InputResourceBuilder extends ResourceBuilder { + + public abstract String getEntityName(); + + public abstract TypeName getTypeName(); +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java new file mode 100644 index 0000000..4e64e68 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java @@ -0,0 +1,17 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.input; + +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; + +public class InputResourceBuilderFactory { + + private InputResourceBuilderFactory() { + // Intentionally blank + } + + public static InputResourceBuilder create(Controller controller, Endpoint endpoint, String basePackage) { + Mapping mapping = new Mapping(controller.getMapping()); + return new AllInputResourceBuilder(mapping, endpoint, basePackage); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java new file mode 100644 index 0000000..62f5b45 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java @@ -0,0 +1,91 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.output; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.squareup.javapoet.*; +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.resource.ApigenEntityOutResource; +import net.cloudappi.apigen.generatorcore.config.controller.Attribute; +import net.cloudappi.apigen.generatorcore.config.controller.Response; +import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; + +import java.util.List; +import java.util.Set; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; + +public class EntityOutputResourceBuilder extends OutputResourceBuilder { + + private String entityName; + private String basePackage; + private List attributes; + + public EntityOutputResourceBuilder(Response response, String basePackage) { + this.basePackage = basePackage; + this.entityName = response.getRelatedEntity(); + this.attributes = response.getAttributes(); + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), getName(entityName)); + } + + private static String getName(String entityName) { + return entityName + "OutResource"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName, "web"); + } + + @Override + public String getPackage() { + return getPackage(entityName, basePackage); + } + + @Override + protected void initialize() { + initializeBuilder(); + addAttributes(); + } + + private void initializeBuilder() { + builder = getClass(getName(entityName)) + .addAnnotation(Data.class) + .addAnnotation(ApigenEntityOutResource.class); + } + + private void addAttributes() { + for (Attribute attribute : attributes) { + if (attribute.getRelatedEntity() != null) { + addRelatedAttribute(attribute, builder, basePackage); + } else { + addSimpleAttribute(attribute, builder); + } + } + } + + private void addRelatedAttribute(Attribute attribute, TypeSpec.Builder builder, String basePackage) { + String relatedEntity = attribute.getRelatedEntity(); + TypeName type = getTypeName(relatedEntity, basePackage); + if (attribute.isCollection()) { + type = ParameterizedTypeName.get(ClassName.get(Set.class), type); + } + addAttribute(type, attribute.getEntityFieldName(), attribute.getName(), builder); + } + + private void addSimpleAttribute(Attribute attribute, TypeSpec.Builder builder) { + TypeName type = Openapi2JavapoetType.transformSimpleType(attribute.getType(), attribute.getFormat()); + addAttribute(type, attribute.getEntityFieldName(), attribute.getName(), builder); + } + + private void addAttribute(TypeName type, String name, String jsonName, TypeSpec.Builder builder) { + FieldSpec.Builder fieldBuilder = getField(type, name); + addJsonName(jsonName, fieldBuilder); + builder.addField(fieldBuilder.build()); + } + + private void addJsonName(String name, FieldSpec.Builder builder) { + builder.addAnnotation(getAnnotation(JsonProperty.class).addMember(VALUE, STRING, name).build()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java new file mode 100644 index 0000000..74ac802 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java @@ -0,0 +1,7 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.output; + +import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourceBuilder; + +public abstract class OutputResourceBuilder extends ResourceBuilder { + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java new file mode 100644 index 0000000..f257946 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java @@ -0,0 +1,24 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.output; + +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.config.controller.Response; +import net.cloudappi.apigen.generatorcore.utils.Mapping; + +public class OutputResourceBuilderFactory { + + private OutputResourceBuilderFactory() { + // Intentionally blank + } + + public static OutputResourceBuilder create(Controller controller, Endpoint endpoint, String basePackage) { + Response response = endpoint.getResponse(); + String entityName = response.getRelatedEntity(); + if (entityName == null) { + Mapping mapping = new Mapping(controller.getMapping()); + return new ResourceOutputResourceBuilder(mapping, endpoint, basePackage); + } else { + return new EntityOutputResourceBuilder(response, basePackage); + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java new file mode 100644 index 0000000..393c16a --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java @@ -0,0 +1,135 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.output; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.squareup.javapoet.*; +import lombok.Data; +import net.cloudappi.apigen.generatorcore.config.controller.*; +import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apache.commons.lang3.StringUtils; + +import javax.validation.Valid; +import java.util.List; +import java.util.Set; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; + +public class ResourceOutputResourceBuilder extends OutputResourceBuilder { + + private String entityName; + private String basePackage; + private Mapping rootMapping; + private List attributes; + private Endpoint endpoint; + + public ResourceOutputResourceBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + Response endpointResponseBody = endpoint.getResponse(); + this.basePackage = basePackage; + this.entityName = endpointResponseBody.getRelatedEntity(); + this.rootMapping = rootMapping; + this.attributes = endpointResponseBody.getAttributes(); + this.endpoint = endpoint; + } + + public static TypeName getTypeName(Endpoint endpoint, String basePackage) { + String rootEntityName = endpoint.getRelatedEntity(); + return ClassName.get(getPackage(rootEntityName, basePackage), getName(null, endpoint)); + } + + public static TypeName getTypeName(Mapping rootMapping, Endpoint endpoint, String basePackage) { + String rootEntityName = endpoint.getRelatedEntity(); + if (rootEntityName == null) rootEntityName = rootMapping.toName().toLowerCase(); + return ClassName.get(getPackage(rootEntityName, basePackage), getName(rootMapping, endpoint)); + } + + private static String getName(Mapping rootMapping, Endpoint endpoint) { + String rootEntityName = endpoint.getRelatedEntity(); + String entityName = endpoint.getResponse().getRelatedEntity(); + if (entityName == null) entityName = rootEntityName; + if (entityName == null) entityName = rootMapping == null ? "" : rootMapping.toName(); + Endpoint.Method method = endpoint.getMethod(); + Mapping mapping = new Mapping(endpoint.getMapping()); + String mappingName = mapping.toName(); + return method.prefix + entityName + mappingName + "OutResource"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName, "web"); + } + + public TypeName getTypeName() { + return getTypeName(rootMapping, endpoint, basePackage); + } + + public String getEntityName() { + return entityName; + } + + @Override + public String getPackage() { + if (entityName == null) { // FIXME analyze separation + return getPackage(rootMapping.toName().toLowerCase(), basePackage); + } else { + return getPackage(entityName, basePackage); + } + } + + @Override + protected void initialize() { + initializeBuilder(); + addAttributes(); + } + + private void initializeBuilder() { + builder = getClass(getName(rootMapping, endpoint)) + .addAnnotation(Data.class); + } + + private void addAttributes() { + addAttributes(attributes, builder, getTypeName(rootMapping, endpoint, basePackage)); + } + + private void addAttributes(List attributes, TypeSpec.Builder builder, TypeName parentType) { + + for (Attribute attribute : attributes) { + boolean nested = requiredNestedObject(attribute); + String javaName = attribute.getEntityFieldName(); + TypeName type; + + if (nested) { + type = createNestedObject(javaName, attribute.getAttributes(), parentType); + } else { + type = Openapi2JavapoetType.transformSimpleType(attribute.getType(), attribute.getFormat()); + } + if (attribute.isCollection()) { + type = ParameterizedTypeName.get(ClassName.get(Set.class), type); + } + addAttribute(type, javaName, attribute.getName(), nested, builder); + } + } + + private boolean requiredNestedObject(Attribute attribute) { + return Openapi2JavapoetType.TYPE_OBJECT.equals(attribute.getType()) || Openapi2JavapoetType.TYPE_ARRAY.equals(attribute.getType()); + } + + private TypeName createNestedObject(String javaName, List attributes, TypeName parentType) { + String nestedName = StringUtils.capitalize(javaName); + TypeName type = ((ClassName) parentType).nestedClass(nestedName); + TypeSpec.Builder nestedBuilder = getPublicInnerClass(nestedName).addAnnotation(Data.class); + addAttributes(attributes, nestedBuilder, type); + builder.addType(nestedBuilder.build()); + return type; + } + + private void addAttribute(TypeName type, String name, String jsonName, boolean nested, TypeSpec.Builder builder) { + FieldSpec.Builder fieldBuilder = getField(type, name); + addJsonName(jsonName, fieldBuilder); + if (nested) fieldBuilder.addAnnotation(Valid.class); + builder.addField(fieldBuilder.build()); + } + + private void addJsonName(String name, FieldSpec.Builder builder) { + builder.addAnnotation(getAnnotation(JsonProperty.class).addMember(VALUE, STRING, name).build()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java new file mode 100644 index 0000000..e0ca647 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java @@ -0,0 +1,43 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; + +public class EntityListResponseBuilder extends ListResponseBuilder { + + private String entityName; + + public EntityListResponseBuilder(String entityName, String listName, String basePackage) { + super(listName, EntityOutputResourceBuilder.getTypeName(entityName, basePackage), basePackage); + this.entityName = entityName; + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), getName(entityName)); + } + + private static String getName(String entityName) { + return entityName + "ListResponse"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName, "web"); + } + + @Override + public String getPackage() { + return getPackage(entityName, basePackage); + } + + @Override + protected String getName() { + return getName(entityName); + } + + @Override + protected String getContentName() { + return entityName + "ListResponseContent"; + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ListResponseBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ListResponseBuilder.java new file mode 100644 index 0000000..c3c090f --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ListResponseBuilder.java @@ -0,0 +1,108 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.squareup.javapoet.*; +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.archetypecore.core.responses.content.ApiListResponseContent; +import org.codehaus.plexus.util.StringUtils; + +import java.util.List; + +import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; + +public abstract class ListResponseBuilder extends ResponseBuilder { + + protected String basePackage; + protected String listName; + protected TypeName resourceType; + protected TypeSpec.Builder contentBuilder; + + public ListResponseBuilder(String listName, TypeName resourceType, String basePackage) { + this.listName = listName; + this.resourceType = resourceType; + this.basePackage = basePackage; + } + + @Override + protected void initialize() { + initializeBuilder(); + addConstructor(); + addContentType(); + } + + private void initializeBuilder() { + builder = getClass(getName()) + .superclass(getParentClass()) + .addAnnotation(Data.class); + } + + protected abstract String getName(); + + private TypeName getParentClass() { + return ParameterizedTypeName.get( + ClassName.get(ApiResponse.class), + ParameterizedTypeName.get( + ClassName.get(ApiListResponseContent.class), + resourceType + ) + ); + } + + private void addConstructor() { + MethodSpec constructor = MethodSpec.constructorBuilder() + .addParameter(ParameterizedTypeName.get( + ClassName.get(List.class), + resourceType + ), listName) + .addStatement("super(new " + getContentName() + "(" + listName + "))") + .build(); + builder.addMethod(constructor); + } + + private void addContentType() { + generateContentBuilder(); + addContentConstructor(); + addContentGetter(); + builder.addType(contentBuilder.build()); + } + + private void generateContentBuilder() { + contentBuilder = getInnerClass(getContentName()) + .superclass(getContentParentClass()) + .addAnnotation(Data.class); + } + + protected abstract String getContentName(); + + private TypeName getContentParentClass() { + return ParameterizedTypeName.get( + ClassName.get(ApiListResponseContent.class), + resourceType + ); + } + + private void addContentConstructor() { + MethodSpec constructor = MethodSpec.constructorBuilder() + .addParameter(ParameterizedTypeName.get( + ClassName.get(List.class), + resourceType + ), listName) + .addStatement("super(" + listName + ")") + .build(); + contentBuilder.addMethod(constructor); + } + + private void addContentGetter() { + MethodSpec get = MethodSpec.methodBuilder("get" + StringUtils.capitalise(listName)) + .addAnnotation(getAnnotation(JsonProperty.class).addMember(VALUE, STRING, listName).build()) + .returns(ParameterizedTypeName.get( + ClassName.get(List.class), + resourceType + )) + .addStatement("return content") + .build(); + contentBuilder.addMethod(get); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java new file mode 100644 index 0000000..55b4e4b --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java @@ -0,0 +1,52 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; + +public class ResourceListResponseBuilder extends ListResponseBuilder { + + private Mapping rootMapping; + private Endpoint endpoint; + + public ResourceListResponseBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + super( + endpoint.getResponse().getCollectionName(), + ResourceOutputResourceBuilder.getTypeName(rootMapping, endpoint, basePackage), + basePackage + ); + this.rootMapping = rootMapping; + this.endpoint = endpoint; + } + + public static TypeName getTypeName(Mapping rootMapping, Endpoint endpoint, String basePackage) { + return ClassName.get(getPackage(rootMapping, basePackage), getName(rootMapping, endpoint)); + } + + private static String getName(Mapping rootMapping, Endpoint endpoint) { + Mapping mapping = new Mapping(endpoint.getMapping()); + return endpoint.getMethod().prefix + rootMapping.toName() + mapping.toName() + "ListResponse"; + } + + private static String getPackage(Mapping rootMapping, String basePackage) { + return concatPackage(basePackage, rootMapping.toName().toLowerCase(), "web"); + } + + @Override + public String getPackage() { + return getPackage(rootMapping, basePackage); + } + + @Override + protected String getName() { + return getName(rootMapping, endpoint); + } + + @Override + protected String getContentName() { + Mapping mapping = new Mapping(endpoint.getMapping()); + return rootMapping.toName() + mapping.toName() + "ListResponseContent"; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java new file mode 100644 index 0000000..5b8dbc3 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java @@ -0,0 +1,72 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; + +public class ResourceSimpleResponseBuilder extends ResponseBuilder { + + private Mapping rootMapping; + private Endpoint endpoint; + private String basePackage; + + public ResourceSimpleResponseBuilder(Mapping rootMapping, Endpoint endpoint, String basePackage) { + this.rootMapping = rootMapping; + this.endpoint = endpoint; + this.basePackage = basePackage; + } + + public static TypeName getTypeName(Mapping rootMapping, Endpoint endpoint, String basePackage) { + return ClassName.get(getPackage(rootMapping, basePackage), getName(rootMapping, endpoint)); + } + + private static String getName(Mapping rootMapping, Endpoint endpoint) { + Mapping mapping = new Mapping(endpoint.getMapping()); + return endpoint.getMethod().prefix + rootMapping.toName() + mapping.toName() + "Response"; + } + + private static String getPackage(Mapping rootMapping, String basePackage) { + return concatPackage(basePackage, rootMapping.toName().toLowerCase(), "web"); + } + + @Override + public String getPackage() { + return getPackage(rootMapping, basePackage); + } + + @Override + protected void initialize() { + generateBuilder(); + addConstructor(); + } + + private void generateBuilder() { + TypeName resourceType = ResourceOutputResourceBuilder.getTypeName(rootMapping, endpoint, basePackage); + this.builder = getClass(getName(rootMapping, endpoint)) + .superclass(getResponseParentClass(resourceType)) + .addAnnotation(Data.class); + } + + private void addConstructor() { + TypeName resourceType = ResourceOutputResourceBuilder.getTypeName(rootMapping, endpoint, basePackage); + MethodSpec constructor = MethodSpec.constructorBuilder() + .addParameter(resourceType, "data") + .addStatement("super(data)") + .build(); + builder.addMethod(constructor); + } + + private TypeName getResponseParentClass(TypeName resourceType) { + return ParameterizedTypeName.get( + ClassName.get(ApiResponse.class), + resourceType + ); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilder.java new file mode 100644 index 0000000..225da5f --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilder.java @@ -0,0 +1,7 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; + +public abstract class ResponseBuilder extends AbstractClassBuilder { + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java new file mode 100644 index 0000000..8351486 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java @@ -0,0 +1,32 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.config.controller.Response; +import net.cloudappi.apigen.generatorcore.utils.Mapping; + +public class ResponseBuilderFactory { + + private ResponseBuilderFactory() { + // Intentionally blank + } + + public static ResponseBuilder create(Controller controller, Endpoint endpoint, String basePackage) { + Response response = endpoint.getResponse(); + String entityName = response.getRelatedEntity(); + if (entityName != null) { + if (response.getIsCollection()) { + return new EntityListResponseBuilder(entityName, response.getCollectionName(), basePackage); + } else { + return new SimpleResponseBuilder(entityName, basePackage); + } + } else { + Mapping mapping = new Mapping(controller.getMapping()); + if (response.getIsCollection()) { + return new ResourceListResponseBuilder(mapping, endpoint, basePackage); + } else { + return new ResourceSimpleResponseBuilder(mapping, endpoint, basePackage); + } + } + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGenerator.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGenerator.java new file mode 100644 index 0000000..297d122 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGenerator.java @@ -0,0 +1,33 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.config.controller.Response; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Slf4j +public class ResponsesGenerator extends AbstractGenerator { + + private List builders = new ArrayList<>(); + + public ResponsesGenerator(Collection controllers, String basePackage) { + for (Controller controller : controllers) { + for (Endpoint endpoint : controller.getEndpoints()) { + Response response = endpoint.getResponse(); + if (response == null || response.getAttributes() == null || !response.getIsStandard()) continue; + builders.add(ResponseBuilderFactory.create(controller, endpoint, basePackage)); + } + } + } + + @Override + protected Collection getBuilders() { + return new ArrayList<>(builders); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java new file mode 100644 index 0000000..aa1cee0 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java @@ -0,0 +1,67 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import lombok.Data; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; + +public class SimpleResponseBuilder extends ResponseBuilder { + + private String basePackage; + private String entityName; + + public SimpleResponseBuilder(String entityName, String basePackage) { + this.basePackage = basePackage; + this.entityName = entityName; + } + + public static TypeName getTypeName(String entityName, String basePackage) { + return ClassName.get(getPackage(entityName, basePackage), getName(entityName)); + } + + private static String getName(String entityName) { + return entityName + "Response"; + } + + private static String getPackage(String entityName, String basePackage) { + return concatPackage(basePackage, entityName, "web"); + } + + @Override + public String getPackage() { + return getPackage(entityName, basePackage); + } + + @Override + protected void initialize() { + generateBuilder(); + addConstructor(); + } + + private void generateBuilder() { + TypeName resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + this.builder = getClass(getName(entityName)) + .superclass(getResponseParentClass(resourceType)) + .addAnnotation(Data.class); + } + + private void addConstructor() { + TypeName resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + MethodSpec constructor = MethodSpec.constructorBuilder() + .addParameter(resourceType, "data") + .addStatement("super(data)") + .build(); + builder.addMethod(constructor); + } + + private TypeName getResponseParentClass(TypeName resourceType) { + return ParameterizedTypeName.get( + ClassName.get(ApiResponse.class), + resourceType + ); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/OpenAPIExtended.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/OpenAPIExtended.java new file mode 100644 index 0000000..cb9fcec --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/OpenAPIExtended.java @@ -0,0 +1,73 @@ +package net.cloudappi.apigen.generatorcore.spec; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.media.Schema; +import lombok.Getter; +import net.cloudappi.apigen.generatorcore.spec.components.ApigenBinding; +import net.cloudappi.apigen.generatorcore.spec.components.ApigenModel; +import net.cloudappi.apigen.generatorcore.spec.components.ApigenProject; + +import java.util.HashMap; +import java.util.Map; + +import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.*; + +@Getter +public class OpenAPIExtended { + + private OpenAPI openAPI; + private ApigenProject project; + private Map models; + private Map pathBindings; + private Map paths; + + private ObjectMapper mapper; + + public OpenAPIExtended(OpenAPI openAPI) { + initializeMapper(); + this.openAPI = openAPI; + this.project = getProject(openAPI); + this.models = getModels(openAPI); + this.pathBindings = getBindings(openAPI); + this.paths = openAPI.getPaths(); + } + + public Map getSchemas() { + return this.openAPI.getComponents().getSchemas(); + } + + private ApigenProject getProject(OpenAPI openAPI) { + return mapper.convertValue(openAPI.getExtensions().get(PROJECT), ApigenProject.class); + } + + private Map getModels(OpenAPI openAPI) { + if (openAPI.getComponents() == null + || openAPI.getComponents().getExtensions() == null + || openAPI.getComponents().getExtensions().get(MODELS) == null) { + return new HashMap<>(); + } + return mapper.convertValue(openAPI.getComponents().getExtensions().get(MODELS), new TypeReference>() {}); + } + + private Map getBindings(OpenAPI openAPI) { + return openAPI.getPaths().values().stream() + .collect(HashMap::new, (m, v) -> m.put(v, getBinding(v)), HashMap::putAll); + } + + private ApigenBinding getBinding(PathItem pathItem) { + if (pathItem.getExtensions() == null || pathItem.getExtensions().get(BINDING) == null) return null; + return mapper.convertValue(pathItem.getExtensions().get(BINDING), ApigenBinding.class); + } + + private void initializeMapper() { + mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenBinding.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenBinding.java new file mode 100644 index 0000000..8f581e0 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenBinding.java @@ -0,0 +1,8 @@ +package net.cloudappi.apigen.generatorcore.spec.components; + +import lombok.Data; + +@Data +public class ApigenBinding { + private String model; +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenModel.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenModel.java new file mode 100644 index 0000000..38d8f26 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenModel.java @@ -0,0 +1,56 @@ +package net.cloudappi.apigen.generatorcore.spec.components; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; + +@Data +public class ApigenModel { + private boolean readOnly = false; + private ModelRelationalPersistence relationalPersistence = new ModelRelationalPersistence(); + private List attributes = new ArrayList<>(); + + @Data + public static class ModelRelationalPersistence { + private String table; + + } + + @Data + public static class ApigenModelAttribute { + private String name; + private String type; + private String itemsType; + private AttributeRelationalPersistence relationalPersistence = new AttributeRelationalPersistence(); + private List validations = new ArrayList<>(); + private List attributes = new ArrayList<>(); + } + + @Data + public static class AttributeRelationalPersistence { + private String column; + private SortedMap columns; + private boolean primaryKey = false; + private boolean unique = false; + private boolean autogenerated = false; + private String foreignColumn; + private SortedMap foreignColumns; + private String intermediateTable; + private boolean owner; + private String sequence; + } + + @Data + public static class AttributeValidation { + private String type; + private Integer min; + private Integer max; + private String regex; + private String value; + private Integer integer; + private Integer fraction; + private boolean inclusive; + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenProject.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenProject.java new file mode 100644 index 0000000..fe33c48 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenProject.java @@ -0,0 +1,21 @@ +package net.cloudappi.apigen.generatorcore.spec.components; + +import lombok.Data; + +@Data +public class ApigenProject { + + private String name; + private String description; + private String version; + private Boolean partial = false; + + private JavaProperties javaProperties = new JavaProperties(); + + @Data + public static class JavaProperties { + private String groupId; + private String artifactId; + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/Extensions.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/Extensions.java new file mode 100644 index 0000000..d9509d2 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/Extensions.java @@ -0,0 +1,15 @@ +package net.cloudappi.apigen.generatorcore.spec.components; + +public class Extensions { + + public Extensions() { + // Intentional blank + } + + public static final String PROJECT = "x-apigen-project"; + public static final String MODELS = "x-apigen-models"; + public static final String BINDING = "x-apigen-binding"; + public static final String MAPPING = "x-apigen-mapping"; + public static final String MAPPING_MODEL = "model"; + public static final String MAPPING_FIELD = "field"; +} \ No newline at end of file diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtils.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtils.java new file mode 100644 index 0000000..a553029 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtils.java @@ -0,0 +1,52 @@ +package net.cloudappi.apigen.generatorcore.utils; + +import org.apache.commons.lang3.StringUtils; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class CustomStringUtils { + + private static final String UNDERSCORE = "_"; + private static final String DASH = "-"; + + private CustomStringUtils() { + // Intentional blank + } + + public static String snakeCaseToCamelCase(String snakeCase) { + if (snakeCase == null) return null; + return StringUtils.uncapitalize( + Stream.of(snakeCase.split(UNDERSCORE)) + .map(String::toLowerCase).map(StringUtils::capitalize) + .collect(Collectors.joining()) + ); + } + + public static String kebabCaseToCamelCase(String snakeCase) { + if (snakeCase == null) return null; + return StringUtils.uncapitalize( + Stream.of(snakeCase.split(DASH)) + .map(String::toLowerCase).map(StringUtils::capitalize) + .collect(Collectors.joining()) + ); + } + + public static String camelCaseToSnakeCase(String camelCase) { + if (camelCase == null) return null; + if (camelCase.equals("")) return ""; + StringBuilder result = new StringBuilder(); + char c = camelCase.charAt(0); + result.append(Character.toLowerCase(c)); + for (int i = 1; i < camelCase.length(); i++) { + char ch = camelCase.charAt(i); + if (Character.isUpperCase(ch)) { + result.append(UNDERSCORE); + result.append(Character.toLowerCase(ch)); + } else { + result.append(ch); + } + } + return result.toString(); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/Mapping.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/Mapping.java new file mode 100644 index 0000000..0af6a43 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/Mapping.java @@ -0,0 +1,69 @@ +package net.cloudappi.apigen.generatorcore.utils; + +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Data +public class Mapping { + private String value; + private String[] parts; + + public Mapping(String value) { + this.value = value; + if (value != null) { + if (!value.isEmpty() && value.charAt(0) != '/') this.value = "/" + value; + parts = Stream.of(value.split("/")).filter(s -> !s.trim().isEmpty()).toArray(String[]::new); + } + } + + public int size() { + if (parts == null) return 0; + return parts.length; + } + + public boolean isEmpty() { + return size() == 0; + } + + public boolean isFirstVariable() { + return isVariable(0); + } + + public boolean isSearch() { + return size() == 1 && parts[0].equals("search"); + } + + public boolean isById() { + return size() == 1 && isFirstVariable(); + } + + public String getValue() { + return value; + } + + private boolean isVariable(int i) { + return isVariable(parts[i]); + } + + private boolean isVariable(String s) { + return s.charAt(0) == '{' && s.charAt(s.length() - 1) == '}'; + } + + public String toName() { + if (parts == null) return ""; + return Stream.of(parts) + .map(s -> isVariable(s) ? variableToName(s) : partToName(s)).collect(Collectors.joining()); + } + + private String variableToName(String variable) { + return "By" + StringUtils.capitalize(CustomStringUtils.snakeCaseToCamelCase(variable.substring(1, variable.length() - 1))); + } + + private String partToName(String part) { + return StringUtils.capitalize(CustomStringUtils.kebabCaseToCamelCase(part)); + } + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/ZipUtils.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/ZipUtils.java new file mode 100644 index 0000000..f9c18c6 --- /dev/null +++ b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/ZipUtils.java @@ -0,0 +1,98 @@ +package net.cloudappi.apigen.generatorcore.utils; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +@Slf4j +public class ZipUtils { + + private static final int BUFFER_SIZE = 2048; + + private ZipUtils() { + // Intentional blank + } + + public static Path zip(Path folderPath) { + zipDirectory(folderPath.toFile(), folderPath.toString() + ".zip"); + return Paths.get(folderPath.toString() + ".zip"); + } + + private static void zipDirectory(File folder, String zipDirName) { + try (FileOutputStream fos = new FileOutputStream(zipDirName); ZipOutputStream zos = new ZipOutputStream(fos)) { + List filePaths = getFilePaths(folder); + for (String filePath : filePaths) { + ZipEntry ze = new ZipEntry(filePath.substring(folder.getAbsolutePath().length() + 1)); + zos.putNextEntry(ze); + try (FileInputStream fis = new FileInputStream(filePath)) { + byte[] buffer = new byte[BUFFER_SIZE]; + int len; + while ((len = fis.read(buffer)) > 0) { + zos.write(buffer, 0, len); + } + zos.closeEntry(); + } + } + } catch (IOException e) { + log.error("Error creating zip file", e); + } + } + + private static List getFilePaths(File folder) { + List filePaths = new ArrayList<>(); + File[] files = folder.listFiles(); + if (files == null) return filePaths; + for (File file : files) { + if (file.isFile()) { + filePaths.add(file.getAbsolutePath()); + } else { + filePaths.addAll(getFilePaths(file)); + } + } + return filePaths; + } + + public static boolean isZip(MultipartFile file) { + String type = file.getContentType(); + return "application/zip".equals(type) || "application/x-zip-compressed".equals(type); + } + + public static Path unzip(byte[] bytes) throws IOException { + Path directory = Files.createTempDirectory(""); + return unzip(bytes, directory); + } + + public static Path unzip(byte[] bytes, Path directory) throws IOException { + ZipInputStream zipIn = new ZipInputStream(new ByteArrayInputStream(bytes)); + ZipEntry entry = zipIn.getNextEntry(); + while (entry != null) { + String filePath = directory.toString() + File.separator + entry.getName(); + if (!entry.isDirectory()) { + try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath))) { + byte[] bytesIn = new byte[BUFFER_SIZE]; + int read; + while ((read = zipIn.read(bytesIn)) != -1) { + bos.write(bytesIn, 0, read); + } + } + } else { + File dir = new File(filePath); + dir.mkdir(); + } + zipIn.closeEntry(); + entry = zipIn.getNextEntry(); + } + zipIn.close(); + return directory; + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/ConfigurationObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/ConfigurationObjectMother.java new file mode 100644 index 0000000..e5d5163 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/ConfigurationObjectMother.java @@ -0,0 +1,19 @@ +package net.cloudappi.apigen.generatorcore.config; + +public class ConfigurationObjectMother { + + private ConfigurationObjectMother() { + // Intentional blank + } + + public static Configuration createCompleteConfigurationWithoutEntitiesAndControllers() { + Configuration c = new Configuration(); + c.setName("name"); + c.setDescription("description"); + c.setGroup("the.group"); + c.setArtifact(("artifact")); + c.setVersion(("1.0.0")); + c.setPartial(false); + return c; + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/ControllerObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/ControllerObjectMother.java new file mode 100644 index 0000000..fe33539 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/ControllerObjectMother.java @@ -0,0 +1,70 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; + +import java.util.ArrayList; +import java.util.List; + +public class ControllerObjectMother { + + public static Controller createControllerWithStandardEndpoints(String entityName, String requestMapping) { + Controller controller = new Controller(); + controller.setEntity(entityName); + controller.setMapping(requestMapping); + + List endpointList = new ArrayList<>(); + endpointList.add(EndpointObjectMother.standardPost("postEndpoint", entityName)); + endpointList.add(EndpointObjectMother.standardSearch("postSearchEndpoint", entityName)); + endpointList.add(EndpointObjectMother.standardGetAll("getAllEndpoint", entityName)); + endpointList.add(EndpointObjectMother.standardGetById("getByIdEndpoint", entityName)); + endpointList.add(EndpointObjectMother.standardPut("putEndpoint", entityName)); + endpointList.add(EndpointObjectMother.standardDelete("deleteEndpoint", entityName)); + controller.setEndpoints(endpointList); + return controller; + } + + public static Controller createControllerWithSimpleResponse(String entityName) { + Controller controller = new Controller(); + controller.setEntity(entityName); + controller.setMapping(entityName + "s"); + + Endpoint endpointSimple = EndpointObjectMother.standardGetById("postSearch", "EntityName"); + + List listEndpoints = new ArrayList<>(); + listEndpoints.add(endpointSimple); + + controller.setEndpoints(listEndpoints); + return controller; + } + + public static Controller createControllerWithListResponse(String entityName) { + Controller controller = new Controller(); + controller.setEntity(entityName); + controller.setMapping(entityName + "s"); + + Endpoint endpointList = EndpointObjectMother.standardGetAll("getAll", "EntityName"); + + List listEndpoints = new ArrayList<>(); + listEndpoints.add(endpointList); + + controller.setEndpoints(listEndpoints); + return controller; + } + + public static Controller createControllerWithSimpleAndListResponse(String entityName) { + Controller controller = new Controller(); + controller.setEntity(entityName); + controller.setMapping(entityName + "s"); + + Endpoint endpointSimple = EndpointObjectMother.standardGetById("postSearch", "EntityName"); + Endpoint endPointList = EndpointObjectMother.standardGetAll("getAll", "EntityName"); + + List listEndpoints = new ArrayList<>(); + listEndpoints.add(endpointSimple); + listEndpoints.add(endPointList); + + controller.setEndpoints(listEndpoints); + return controller; + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java new file mode 100644 index 0000000..02ee5ce --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java @@ -0,0 +1,76 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import java.util.ArrayList; + +public class EndpointBaseResponseObjectMother { + + private EndpointBaseResponseObjectMother() { + // Intentional blank + } + + public static Response simpleResponseWithoutAttributes(String entityName) { + Response response = new Response(); + response.setIsStandard(true); + response.setRelatedEntity(entityName); + response.setAttributes(new ArrayList<>()); + response.setIsCollection(false); + return response; + } + + public static Response listResponseWithoutAttributesAndCollectionNameEqualsEntityName(String entityName) { + return listResponseWithoutAttributes(entityName, entityName.toLowerCase()); + } + + public static Response listResponseWithoutAttributes(String entityName, String collectionName) { + Response response = new Response(); + response.setIsStandard(true); + response.setRelatedEntity(entityName); + response.setAttributes(new ArrayList<>()); + response.setIsCollection(true); + response.setCollectionName(collectionName); + return response; + } + + public static Response simpleResponseWithSimpleAttribute(String entityName) { + Response response = simpleResponseWithoutAttributes(entityName); + Attribute attribute = new Attribute(); + attribute.setEntityFieldName("entityField"); + attribute.setType("string"); + attribute.setName("jsonField"); + response.getAttributes().add(attribute); + return response; + } + + public static Response simpleResponseWithRelatedAttribute(String entityName, String relatedEntityName) { + Response response = simpleResponseWithoutAttributes(entityName); + Attribute attribute = new Attribute(); + attribute.setEntityFieldName("relatedEntityField"); + attribute.setType("object"); + attribute.setName("relatedJsonField"); + attribute.setRelatedEntity(relatedEntityName); + attribute.setCollection(false); + response.getAttributes().add(attribute); + return response; + } + + public static Response simpleResponseWithRelatedListAttribute(String entityName, String relatedEntityName) { + Response response = simpleResponseWithoutAttributes(entityName); + Attribute attribute = new Attribute(); + attribute.setEntityFieldName("relatedEntityListField"); + attribute.setType("array"); + attribute.setName("relatedArrayJsonField"); + attribute.setRelatedEntity(relatedEntityName); + attribute.setCollection(true); + response.getAttributes().add(attribute); + return response; + } + + public static Response customResponseWithoutAttributes(int responseStatus) { + Response response = new Response(); + response.setIsStandard(false); + response.setAttributes(new ArrayList<>()); + response.setIsCollection(false); + response.setDefaultStatusCode(responseStatus); + return response; + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java new file mode 100644 index 0000000..4f5f3d7 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java @@ -0,0 +1,18 @@ +package net.cloudappi.apigen.generatorcore.config.controller; + +import java.util.ArrayList; + +public class EndpointRequestObjectMother { + public static Request requestWithoutAttributes(String entityName) { + Request request = new Request(); + request.setRelatedEntity(entityName); + request.setAttributes(new ArrayList<>()); + return request; + } + + public static Request customRequestWithoutAttributes() { + Request request = new Request(); + request.setAttributes(new ArrayList<>()); + return request; + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/entity/EntityObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/entity/EntityObjectMother.java new file mode 100644 index 0000000..12bfceb --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/entity/EntityObjectMother.java @@ -0,0 +1,256 @@ +package net.cloudappi.apigen.generatorcore.config.entity; + +import net.cloudappi.apigen.generatorcore.config.validation.Validation; +import net.cloudappi.apigen.generatorcore.config.validation.ValidationType; + +import java.math.BigDecimal; +import java.util.*; + +public class EntityObjectMother { + + public static Entity createSimpleEntityWithName() { + return new Entity("SimpleTestEntity", "", new ArrayList<>()); + } + + public static Entity createSimpleEntityWithStringAsPrimaryKey() { + Attribute primaryKeyAttribute = new Attribute("id", "String"); + primaryKeyAttribute.setColumns(Arrays.asList(new Column("primaryKeyColumn", true, true, null))); + return new Entity("SimpleTestEntity", "", Collections.singletonList(primaryKeyAttribute)); + } + + public static Entity createEntityWithSimpleAttributes() { + List attributeList = new ArrayList<>(); + + Column column = new Column(); + column.setName("columnName"); + column.setPrimaryKey(false); + + Attribute longAttribute = new Attribute("longAttribute", "Long"); + longAttribute.setColumns(Arrays.asList(column)); + Attribute stringAttribute = new Attribute("stringAttribute", "String"); + stringAttribute.setColumns(Arrays.asList(column)); + + attributeList.add(longAttribute); + attributeList.add(stringAttribute); + + return new Entity("TestEntityWithSimpleAttributes", "tableName", attributeList); + } + + public static Entity createEntityWithOneToManyAttributes(String foreignClassName) { + List attributeList = new ArrayList<>(); + + Column primaryKeyColumn = new Column("primaryKeyColumn", true, true, null); + + Column simpleColumn = new Column("simpleColumn", false); + + Column manyToOneRelationColumn = new Column("manyToOneRelationColumn", false); + + Attribute oneToManyRelationAttribute = new Attribute("oneToManyRelationAttribute", "Array"); + oneToManyRelationAttribute.setIsCollection(true); + Relation relation = new Relation(foreignClassName); + oneToManyRelationAttribute.setRelation(relation); + oneToManyRelationAttribute.setType(foreignClassName); + + oneToManyRelationAttribute.setForeignColumns(Arrays.asList(manyToOneRelationColumn)); + + Attribute longAttribute = new Attribute("longAttribute", "Long"); + longAttribute.setColumns(Arrays.asList(primaryKeyColumn)); + Attribute stringAttribute = new Attribute("stringAttribute", "String"); + stringAttribute.setColumns(Arrays.asList(simpleColumn)); + + attributeList.add(longAttribute); + attributeList.add(stringAttribute); + attributeList.add(oneToManyRelationAttribute); + + return new Entity("TestEntityWithOneToManyAttributes", "tableName", attributeList); + } + + public static List createEntitiesWithOneToOneAttributes() { + List entitiesList = new ArrayList<>(); + + List attributeListOwner = new ArrayList<>(); + List attributeList = new ArrayList<>(); + + + Column oneToOneOwnerRelationColumn = new Column("oneToOneOwnerRelationColumn", false); + + Attribute oneToOneOwnerRelationAttribute = new Attribute("oneToOneOwnerRelationAttribute", "TestEntityWithOneToOneAttributes"); + oneToOneOwnerRelationAttribute.setIsCollection(false); + Relation relationOwner = new Relation("TestEntityWithOneToOneAttributes", true); + + oneToOneOwnerRelationAttribute.setRelation(relationOwner); + oneToOneOwnerRelationAttribute.setType("TestEntityWithOneToOneAttributes"); + + oneToOneOwnerRelationAttribute.setColumns(Arrays.asList(oneToOneOwnerRelationColumn)); + + attributeListOwner.add(oneToOneOwnerRelationAttribute); + + Attribute oneToOneRelationAttribute = new Attribute("oneToOneRelationAttribute", "TestEntityWithOneToOneOwnerAttributes"); + oneToOneRelationAttribute.setIsCollection(false); + + Relation relation = new Relation("TestEntityWithOneToOneOwnerAttributes"); + oneToOneRelationAttribute.setRelation(relation); + oneToOneRelationAttribute.setType("TestEntityWithOneToOneOwnerAttributes"); + + oneToOneRelationAttribute.setForeignColumns(Arrays.asList(oneToOneOwnerRelationColumn)); + + attributeList.add(oneToOneRelationAttribute); + + entitiesList.add(new Entity("TestEntityWithOneToOneOwnerAttributes", "tableName", attributeListOwner)); + entitiesList.add(new Entity("TestEntityWithOneToOneAttributes", "tableName", attributeList)); + + return entitiesList; + } + + public static List createEntitiesWithOneToManyAndManyToOneAttributes() { + List entitiesList = new ArrayList<>(); + entitiesList.add(createEntityWithOneToManyAttributes("TestEntityWithManyToOneAttributes")); + + List attributeList = new ArrayList<>(); + + Column oneToManyRelationColumn = new Column("manyToOneRelationColumn", false); + + Attribute manyToOneRelationAttribute = new Attribute("manyToOneRelationAttribute", "TestEntityWithOneToManyAttributes"); + manyToOneRelationAttribute.setIsCollection(false); + Relation relation = new Relation("TestEntityWithOneToManyAttributes"); + manyToOneRelationAttribute.setRelation(relation); + manyToOneRelationAttribute.setType("TestEntityWithOneToManyAttributes"); + + manyToOneRelationAttribute.setColumns(Arrays.asList(oneToManyRelationColumn)); + manyToOneRelationAttribute.setForeignColumns(new ArrayList<>()); + + attributeList.add(manyToOneRelationAttribute); + + entitiesList.add(new Entity("TestEntityWithManyToOneAttributes", "tableName", attributeList)); + + return entitiesList; + } + + public static List createEntitiesWithManyToManyAttributes() { + List entitiesList = new ArrayList<>(); + + List attributeListOwner = new ArrayList<>(); + List attributeList = new ArrayList<>(); + + List columns = new ArrayList<>(); + Column column1 = new Column("column_1_id"); + columns.add(column1); + + List reverseColumns = new ArrayList<>(); + Column column2 = new Column("column_2_id"); + reverseColumns.add(column2); + + Attribute manyToManyOwnerRelationAttribute = new Attribute("manyToManyOwnerRelationAttribute", "Array"); + manyToManyOwnerRelationAttribute.setIsCollection(true); + Relation relationOwner = new Relation("TestEntityWithManyToManyAttributes", columns, reverseColumns, "intermediateTableName", true); + manyToManyOwnerRelationAttribute.setRelation(relationOwner); + manyToManyOwnerRelationAttribute.setType("TestEntityWithManyToManyAttributes"); + Column manyToManyOwnerRelationColumn = new Column("manyToManyOwnerRelationColumn"); + manyToManyOwnerRelationAttribute.setColumns(Arrays.asList(manyToManyOwnerRelationColumn)); + attributeListOwner.add(manyToManyOwnerRelationAttribute); + entitiesList.add(new Entity("TestEntityWithManyToManyOwnerAttributes", "tableName", attributeListOwner)); + + Attribute manyToManyRelationAttribute = new Attribute("manyToManyRelationAttribute", "Array"); + manyToManyRelationAttribute.setIsCollection(true); + Relation relation = new Relation("TestEntityWithManyToManyOwnerAttributes", columns, reverseColumns, "intermediateTableName"); + manyToManyRelationAttribute.setRelation(relation); + manyToManyRelationAttribute.setType("TestEntityWithManyToManyOwnerAttributes"); + Column manyToManyRelationColumn = new Column("manyToManyRelationColumn"); + manyToManyRelationAttribute.setColumns(Arrays.asList(manyToManyRelationColumn)); + attributeList.add(manyToManyRelationAttribute); + entitiesList.add(new Entity("TestEntityWithManyToManyAttributes", "tableName", attributeList)); + + return entitiesList; + } + + public static Entity createEntityWithAttributeValidations() { + List attributeList = new ArrayList<>(); + + Attribute validationsAttribute = new Attribute("validationsAttribute", "Long"); + validationsAttribute.setColumns(new ArrayList<>()); + validationsAttribute.setForeignColumns(new ArrayList<>()); + + List validationList = new ArrayList<>(); + + Validation notNullValidation = new Validation(ValidationType.NOT_NULL); + validationList.add(notNullValidation); + + Validation sizeValidation = new Validation(ValidationType.SIZE, 1, 2); + validationList.add(sizeValidation); + + Validation minValidation = new Validation(ValidationType.MIN, 1L); + validationList.add(minValidation); + + Validation maxValidation = new Validation(ValidationType.MAX, 2L); + validationList.add(maxValidation); + + Validation emailValidation = new Validation(ValidationType.EMAIL); + validationList.add(emailValidation); + + Validation notEmptyValidation = new Validation(ValidationType.NOT_EMPTY); + validationList.add(notEmptyValidation); + + Validation notBlankValidation = new Validation(ValidationType.NOT_BLANK); + validationList.add(notBlankValidation); + + Validation positiveValidation = new Validation(ValidationType.POSITIVE); + validationList.add(positiveValidation); + + Validation positiveOrZeroValidation = new Validation(ValidationType.POSITIVE_OR_ZERO); + validationList.add(positiveOrZeroValidation); + + Validation negativeValidation = new Validation(ValidationType.NEGATIVE); + validationList.add(negativeValidation); + + Validation negativeOrZeroValidation = new Validation(ValidationType.NEGATIVE_OR_ZERO); + validationList.add(negativeOrZeroValidation); + + Validation pastValidation = new Validation(ValidationType.PAST); + validationList.add(pastValidation); + + Validation pastOrPresentValidation = new Validation(ValidationType.PAST_OR_PRESENT); + validationList.add(pastOrPresentValidation); + + Validation futureValidation = new Validation(ValidationType.FUTURE); + validationList.add(futureValidation); + + Validation futureOrPresentValidation = new Validation(ValidationType.FUTURE_OR_PRESENT); + validationList.add(futureOrPresentValidation); + + Validation patternValidation = new Validation(ValidationType.PATTERN, "[^i*&2@]"); + validationList.add(patternValidation); + + Validation digitsValidation = new Validation(ValidationType.DIGITS, 2, 2); + validationList.add(digitsValidation); + + Validation decimalMinValidation = new Validation(ValidationType.DECIMAL_MIN, BigDecimal.valueOf(0.1d), true); + validationList.add(decimalMinValidation); + + Validation decimalMaxValidation = new Validation(ValidationType.DECIMAL_MAX, BigDecimal.valueOf(0.2d), true); + validationList.add(decimalMaxValidation); + + validationsAttribute.setValidations(validationList); + + attributeList.add(validationsAttribute); + + return new Entity("TestEntityWithAttributeValidations", "tableName", attributeList); + } + + public static Entity createEntitiesSequenceIdAttribute() { + List attributeList = new ArrayList<>(); + + Column column = new Column(); + column.setName("id_column"); + column.setSequence("SEQUENCE_NAME"); + column.setPrimaryKey(true); + column.setAutogenerated(true); + + Attribute longAttribute = new Attribute("id", "Long"); + longAttribute.setColumns(Arrays.asList(column)); + + attributeList.add(longAttribute); + + return new Entity("TestEntityWithSequenceIdAttribute", "tableName", attributeList); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java new file mode 100644 index 0000000..4501f5c --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java @@ -0,0 +1,265 @@ +package net.cloudappi.apigen.generatorcore.config.extractors; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.parser.OpenAPIV3Parser; +import io.swagger.v3.parser.core.models.ParseOptions; +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import net.cloudappi.apigen.generatorcore.config.controller.Request; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.validation.Validation; +import net.cloudappi.apigen.generatorcore.config.validation.ValidationType; +import net.cloudappi.apigen.generatorcore.spec.OpenAPIExtended; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.util.List; + +import static net.cloudappi.apigen.generatorcore.config.controller.Endpoint.Method.*; +import static org.junit.jupiter.api.Assertions.*; + +// TODO #14909 refactor tests to use individualized api fragments +class ConfigurationExtractorTest { + + private static Configuration configuration; + + @BeforeAll + static void prepareTest() { + OpenAPIExtended openAPIExtended = load("testApi.yaml"); + configuration = new ConfigurationExtractor(openAPIExtended).extract(); + assertNotNull(configuration); + } + + private static OpenAPIExtended load(String fileName) { + ParseOptions parseOptions = new ParseOptions(); + parseOptions.setResolveFully(true); + OpenAPI openAPI = new OpenAPIV3Parser().read("src/test/resources/api_fragments/" + fileName, null, parseOptions); + return new OpenAPIExtended(openAPI); + } + + @Test + void checkExtractedProjectInfoFromYAML() { + assertEquals(configuration.getName(), "test"); + assertEquals(configuration.getDescription(), "test"); + assertEquals("the.test", configuration.getGroup()); + assertEquals("1.0.0", configuration.getVersion()); + assertEquals(configuration.getArtifact(), "test"); + } + + @Test + void checkExtractedEntitiesFromYAML() { + assertNotNull(configuration.getEntities()); + + List entities = configuration.getEntities(); + + assertEquals(2, entities.size()); + + assertEquals("Owner", entities.get(0).getName(), "Check Entity Name"); + assertEquals("owners", entities.get(0).getTable(), "Check Entity Table"); + assertEquals(2, entities.get(0).getAttributes().size(), "Check Entity Number of Attributes"); + + assertEquals("id", entities.get(0).getAttributes().get(0).getName(), "Check Attribute Name"); + assertEquals("String", entities.get(0).getAttributes().get(0).getType(), "Check Attribute Type"); + assertEquals("id", entities.get(0).getAttributes().get(0).getColumns().get(0).getName(), "Check Attribute ColumnName"); + assertTrue(entities.get(0).getAttributes().get(0).getForeignColumns().isEmpty(), "Check Attribute ForeignColumn"); + assertFalse(entities.get(0).getAttributes().get(0).getIsCollection(), "Check if Attribute is Collection"); + assertNull(entities.get(0).getAttributes().get(0).getRelation(), "Check if Attribute has Relation"); + + assertEquals("pets", entities.get(0).getAttributes().get(1).getName(), "Check Attribute Name"); + assertEquals("Pet", entities.get(0).getAttributes().get(1).getType(), "Check Attribute Type"); + assertTrue(entities.get(0).getAttributes().get(1).getColumns().isEmpty(), "Check Attribute Column"); + assertEquals("owner_id", entities.get(0).getAttributes().get(1).getForeignColumns().get(0).getName(), "Check Attribute ForeignColumnName"); + assertTrue(entities.get(0).getAttributes().get(1).getIsCollection(), "Check if Attribute is Collection"); + assertNotNull(entities.get(0).getAttributes().get(1).getRelation(), "Check if Attribute has Relation"); + assertEquals("Pet", entities.get(0).getAttributes().get(1).getRelation().getRelatedEntity(), "Check Attribute's ForeignClassName"); + + + assertEquals("Pet", entities.get(1).getName()); + assertEquals("pets", entities.get(1).getTable(), "Check Entity Table"); + assertEquals(2, entities.get(1).getAttributes().size(), "Check Entity Number of Attributes"); + + assertEquals("owner", entities.get(1).getAttributes().get(0).getName(), "Check Attribute Name"); + assertEquals("Owner", entities.get(1).getAttributes().get(0).getType(), "Check Attribute Type"); + assertFalse(entities.get(1).getAttributes().get(0).getColumns().isEmpty(), "Check Attribute Column"); + assertEquals("owner_id", entities.get(1).getAttributes().get(0).getColumns().get(0).getName(), "Check Attribute ColumnName"); + assertTrue(entities.get(1).getAttributes().get(0).getForeignColumns().isEmpty(), "Check Attribute ForeignColumn"); + assertFalse(entities.get(1).getAttributes().get(0).getIsCollection(), "Check if Attribute is Collection"); + assertNotNull(entities.get(1).getAttributes().get(0).getRelation(), "Check if Attribute has Relation"); + assertEquals("Owner", entities.get(1).getAttributes().get(0).getRelation().getRelatedEntity(), "Check Attribute's ForeignClassName"); + + assertEquals("tags", entities.get(1).getAttributes().get(1).getName(), "Check Attribute Name"); + assertEquals("Tag", entities.get(1).getAttributes().get(1).getType(), "Check Attribute Type"); + assertTrue(entities.get(1).getAttributes().get(1).getIsCollection(), "Check if Attribute is Collection"); + assertNotNull(entities.get(1).getAttributes().get(1).getRelation(), "Check if Attribute has Relation"); + assertEquals("Tag", entities.get(1).getAttributes().get(1).getRelation().getRelatedEntity(), "Check Attribute's ForeignClassName"); + assertEquals("pet_tags", entities.get(1).getAttributes().get(1).getRelation().getIntermediateTable(), "Check Attribute's IntermediateTable"); + assertTrue(entities.get(1).getAttributes().get(1).getRelation().getOwner(), "Check if Attribute is Owner part of Relation"); + assertNotNull(entities.get(1).getAttributes().get(1).getRelation().getColumns().get(0).getName(), "Check Attribute Column"); + assertEquals("pet_id", entities.get(1).getAttributes().get(1).getRelation().getColumns().get(0).getName(), "Check Attribute ColumnName"); + assertNotNull(entities.get(1).getAttributes().get(1).getRelation().getReverseColumns().get(0).getName(), "Check Attribute ForeignColumn"); + assertEquals("tag_id", entities.get(1).getAttributes().get(1).getRelation().getReverseColumns().get(0).getName(), "Check Attribute ForeignColumnName"); + } + + @Test + void checkExtractedValidationsFromYAML() { + assertNotNull(configuration.getEntities()); + + List validations = configuration.getEntities().get(0).getAttributes().get(0).getValidations(); + + assertEquals(ValidationType.NOT_NULL, validations.get(0).getType(), "Check NotNull Validation"); + + assertEquals(ValidationType.SIZE, validations.get(1).getType(), "Check Size Validation"); + assertEquals(1, validations.get(1).getIntegerValueOne(), "Check Size Min Value"); + assertEquals(2, validations.get(1).getIntegerValueTwo(), "Check Size Max Value"); + + assertEquals(ValidationType.MIN, validations.get(2).getType(), "Check Min Validation"); + assertEquals(1L, validations.get(2).getLongValue(), "Check Min Value"); + + assertEquals(ValidationType.MAX, validations.get(3).getType(), "Check Max Validation"); + assertEquals(2L, validations.get(3).getLongValue(), "Check Max Value"); + + assertEquals(ValidationType.EMAIL, validations.get(4).getType(), "Check Email Validation"); + + assertEquals(ValidationType.NOT_EMPTY, validations.get(5).getType(), "Check NotEmpty Validation"); + + assertEquals(ValidationType.NOT_BLANK, validations.get(6).getType(), "Check NotBlank Validation"); + + assertEquals(ValidationType.POSITIVE, validations.get(7).getType(), "Check Positive Validation"); + + assertEquals(ValidationType.POSITIVE_OR_ZERO, validations.get(8).getType(), "Check PositiveOrZero Validation"); + + assertEquals(ValidationType.NEGATIVE, validations.get(9).getType(), "Check Negative Validation"); + + assertEquals(ValidationType.NEGATIVE_OR_ZERO, validations.get(10).getType(), "Check NegativeOrZero Validation"); + + assertEquals(ValidationType.PAST, validations.get(11).getType(), "Check Past Validation"); + + assertEquals(ValidationType.PAST_OR_PRESENT, validations.get(12).getType(), "Check PastOrPresent Validation"); + + assertEquals(ValidationType.FUTURE, validations.get(13).getType(), "Check Future Validation"); + + assertEquals(ValidationType.FUTURE_OR_PRESENT, validations.get(14).getType(), "Check FutureOrPresent Validation"); + + assertEquals(ValidationType.PATTERN, validations.get(15).getType(), "Check Pattern Validation"); + assertEquals("[^i*&2@]", validations.get(15).getStringValue(), "Check Pattern Regex"); + + assertEquals(ValidationType.DIGITS, validations.get(16).getType(), "Check Digits Validation"); + assertEquals(4, validations.get(16).getIntegerValueOne(), "Check Digits Validation"); + assertEquals(2, validations.get(16).getIntegerValueTwo(), "Check Digits Validation"); + + assertEquals(ValidationType.DECIMAL_MIN, validations.get(17).getType(), "Check DecimalMin Validation"); + assertEquals(BigDecimal.valueOf(0.1), validations.get(17).getDecimalValue(), "Check DecimalMin Value"); + + assertEquals(ValidationType.DECIMAL_MAX, validations.get(18).getType(), "Check DecimalMax Validation"); + assertEquals(BigDecimal.valueOf(0.2), validations.get(18).getDecimalValue(), "Check DecimanMax Value"); + + } + + @Test + void checkExtractedControllersFromYAML() { + assertNotNull(configuration.getControllers()); + + List controllers = configuration.getControllers(); + + assertEquals(1, controllers.size()); + + Controller controller = controllers.get(0); + + assertEquals("Owner", controller.getEntity(), "Check Controller Entity"); + + List endpoints = controller.getEndpoints(); + + assertEquals(4, endpoints.size()); + + assertEquals(POST, endpoints.get(0).getMethod(), "Check Controller POST Endpoint"); + assertEquals(null, endpoints.get(0).getMapping(), "Check Controller POST Endpoint"); + + assertEquals(GET, endpoints.get(1).getMethod(), "Check Controller GETbyID Endpoint"); + assertEquals("{id}", endpoints.get(1).getMapping(), "Check Controller GETbyID Endpoint"); + + assertEquals(PUT, endpoints.get(2).getMethod(), "Check Controller PUT Endpoint"); + assertEquals("{id}", endpoints.get(2).getMapping(), "Check Controller PUT Endpoint"); + + assertEquals(DELETE, endpoints.get(3).getMethod(), "Check Controller DELETE Endpoint"); + assertEquals("{id}", endpoints.get(3).getMapping(), "Check Controller DELETE Endpoint"); + } + + @Test + void checkExtractedEndpointsParametersFromYAML() { + assertNotNull(configuration.getControllers()); + Controller controller = configuration.getControllers().get(0); + + List parameters = controller.getEndpoints().get(1).getParameters(); + + checkIdParameter(parameters.get(0)); + checkIntegerParameter(parameters.get(1)); + checkDoubleParameter(parameters.get(2)); + checkStringParameter(parameters.get(3)); + checkListParameter(parameters.get(4)); + checkBooleanParameter(parameters.get(5)); + } + + @Test + void givenOAPI3ExtendedWithRequestBody_whenConfigurationExtracted_thenResponseGenerated() { + OpenAPIExtended extended = load("standard_request.yaml"); + Configuration configuration = new ConfigurationExtractor(extended).extract(); + assertEquals(1, configuration.getControllers().size(), "One controller expected"); + Controller controller = configuration.getControllers().get(0); + assertEquals(1, controller.getEndpoints().size(), "One endpoint expected"); + Endpoint endpoint = controller.getEndpoints().get(0); + Request request = endpoint.getRequest(); + assertNotNull(request, "Request expected"); + } + + private void checkIdParameter(Parameter parameter) { + assertEquals("id", parameter.getName(), "Check Endpoint idParameter name"); + assertEquals("path", parameter.getIn(), "Check Endpoint idParameter in"); + assertEquals("string", parameter.getType(), "Check Endpoint idParameter type"); + } + + private void checkIntegerParameter(Parameter parameter) { + assertEquals("integer", parameter.getName(), "Check Endpoint IntegerParameter name"); + assertEquals("query", parameter.getIn(), "Check Endpoint IntegerParameter in"); + assertEquals("integer", parameter.getType(), "Check Endpoint IntegerParameter type"); + assertEquals(10, parameter.getDefaultValue(), "Check Endpoint IntegerParameter defaultValue"); + + assertEquals(new Validation(ValidationType.MIN, 10L), parameter.getValidations().get(0)); + assertEquals(new Validation(ValidationType.MAX, 20L), parameter.getValidations().get(1)); + } + + private void checkDoubleParameter(Parameter parameter) { + assertEquals("double", parameter.getName(), "Check Endpoint DoubleParameter name"); + assertEquals("query", parameter.getIn(), "Check Endpoint DoubleParameter in"); + assertEquals("double", parameter.getFormat(), "Check Endpoint DoubleParameter type"); + assertEquals(10.5, Double.parseDouble(parameter.getDefaultValue().toString()), 0.00, "Check Endpoint DoubleParameter defaultValue"); + assertEquals(new Validation(ValidationType.DECIMAL_MIN, BigDecimal.valueOf(10.2), false), parameter.getValidations().get(0)); + assertEquals(new Validation(ValidationType.DECIMAL_MAX, BigDecimal.valueOf(20.3), true), parameter.getValidations().get(1)); + } + + private void checkStringParameter(Parameter parameter) { + assertEquals("string", parameter.getName(), "Check Endpoint StringParameter name"); + assertEquals("query", parameter.getIn(), "Check Endpoint StringParameter in"); + assertEquals("string", parameter.getType(), "Check Endpoint StringParameter type"); + assertEquals("test", parameter.getDefaultValue(), "Check Endpoint StringParameter defaultValue"); + assertEquals(new Validation(ValidationType.SIZE, 3, 7), parameter.getValidations().get(0)); + assertEquals(new Validation(ValidationType.PATTERN, "\\\\d{9}"), parameter.getValidations().get(1)); + } + + private void checkListParameter(Parameter parameter) { + assertEquals("list", parameter.getName(), "Check Endpoint ListParameter name"); + assertEquals("query", parameter.getIn(), "Check Endpoint ListParameter in"); + assertTrue(parameter.isCollection(), "Check Endpoint ListParameter type"); + assertEquals("string", parameter.getType(), "Check Endpoint ListParameter itemsType"); + assertEquals(new Validation(ValidationType.SIZE, 2, 5), parameter.getValidations().get(0)); + } + + private void checkBooleanParameter(Parameter parameter) { + assertEquals("boolean", parameter.getName(), "Check Endpoint BooleanParameter name"); + assertEquals("query", parameter.getIn(), "Check Endpoint BooleanParameter in"); + assertEquals("boolean", parameter.getType(), "Check Endpoint BooleanParameter type"); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java new file mode 100644 index 0000000..31480f5 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java @@ -0,0 +1,18 @@ +package net.cloudappi.apigen.generatorcore.config.mapper; + +import net.cloudappi.apigen.generatorcore.generator.mapper.MapperBuilder; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + + +public class MapperBuilderObjectMother { + + public static MapperBuilder createMapper(String entityName, String basePackage, String basicAttribute, String relatedEntityName) { + Set basicAttributes = new HashSet<>(Collections.singletonList(basicAttribute)); + Set relatedEntities = new HashSet<>(Collections.singletonList(relatedEntityName)); + return new MapperBuilder(entityName, basePackage, basicAttributes, relatedEntities, Collections.emptySet(), null); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/parameter/ParameterObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/parameter/ParameterObjectMother.java new file mode 100644 index 0000000..ca8a89f --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/parameter/ParameterObjectMother.java @@ -0,0 +1,255 @@ +package net.cloudappi.apigen.generatorcore.config.parameter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import net.cloudappi.apigen.generatorcore.config.validation.Validation; +import net.cloudappi.apigen.generatorcore.config.validation.ValidationType; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class ParameterObjectMother { + + public static Parameter getBasicPathParameter() { + return getBasicPathParameter("basicPathParameter", "String"); + } + + public static Parameter getBasicPathParameter(String name) { + return getBasicPathParameter(name, "String"); + } + + public static Parameter getBasicPathParameter(String name, String type) { + Parameter parameter = new Parameter(); + parameter.setIn("path"); + parameter.setName(name); + parameter.setType(type); + parameter.setValidations(Collections.emptyList()); + return parameter; + } + + public static Parameter getBasicIntegerPathParameter(String name) { + Parameter parameter = new Parameter(); + parameter.setIn("path"); + parameter.setName("basicPathParameter"); + parameter.setType("string"); + parameter.setValidations(Collections.emptyList()); + return parameter; + } + + public static Parameter getBasicQueryParameter() { + Parameter parameter = new Parameter(); + parameter.setIn("query"); + parameter.setName("basicQueryParameter"); + parameter.setType("integer"); + parameter.setValidations(Collections.emptyList()); + return parameter; + } + + public static Parameter getIntegerParameter() { + Parameter parameter = new Parameter(); + parameter.setIn("query"); + parameter.setName("integerParameter"); + parameter.setType("integer"); + parameter.setFormat("int32"); + parameter.setRequired(true); + parameter.setDefaultValue(5); + parameter.setValidations(Arrays.asList( + new Validation(ValidationType.MIN, 5L), + new Validation(ValidationType.MAX, 10L) + )); + return parameter; + } + + public static Parameter getLongParameter() { + Parameter parameter = new Parameter(); + parameter.setIn("query"); + parameter.setName("longParameter"); + parameter.setType("integer"); + parameter.setFormat("int64"); + parameter.setRequired(true); + parameter.setDefaultValue(5); + parameter.setValidations(Arrays.asList( + new Validation(ValidationType.MIN, 5L), + new Validation(ValidationType.MAX, 10L) + )); + return parameter; + } + + public static Parameter getDoubleParameter() { + Parameter parameter = new Parameter(); + parameter.setIn("query"); + parameter.setName("doubleParameter"); + parameter.setType("number"); + parameter.setFormat("double"); + parameter.setRequired(true); + parameter.setDefaultValue(5.0); + parameter.setValidations(Arrays.asList( + new Validation(ValidationType.DECIMAL_MIN, BigDecimal.valueOf(5), false), + new Validation(ValidationType.DECIMAL_MAX, BigDecimal.valueOf(10), false) + )); + return parameter; + } + + public static Parameter getFloatParameter() { + Parameter parameter = new Parameter(); + parameter.setIn("query"); + parameter.setName("floatParameter"); + parameter.setType("number"); + parameter.setFormat("float"); + parameter.setRequired(true); + parameter.setDefaultValue(5.0); + parameter.setValidations(Arrays.asList( + new Validation(ValidationType.DECIMAL_MIN, BigDecimal.valueOf(5), false), + new Validation(ValidationType.DECIMAL_MAX, BigDecimal.valueOf(10), false) + )); + return parameter; + } + + public static Parameter getStringParameter() { + Parameter parameter = new Parameter(); + parameter.setIn("query"); + parameter.setName("stringParameter"); + parameter.setType("string"); + parameter.setRequired(true); + parameter.setDefaultValue("test"); + parameter.setValidations(Arrays.asList( + new Validation(ValidationType.SIZE, 5, 10), + new Validation(ValidationType.PATTERN, "\\d{9}") + )); + return parameter; + } + + public static Parameter getArrayParameter() { + Parameter parameter = new Parameter(); + parameter.setIn("query"); + parameter.setName("arrayParameter"); + parameter.setType("string"); + parameter.setCollection(true); + parameter.setRequired(true); + ObjectMapper mapper = new ObjectMapper(); + List arrayElements = Arrays.asList("primero", "segundo", "tercero", "cuarto", "quinto"); + ArrayNode arrayNode = mapper.valueToTree(arrayElements); + parameter.setDefaultValue(arrayNode); + parameter.setValidations(Collections.singletonList( + new Validation(ValidationType.SIZE, 5, 10) + )); + return parameter; + } + + public static Parameter getBooleanParameter() { + Parameter parameter = new Parameter(); + parameter.setIn("query"); + parameter.setName("booleanParameter"); + parameter.setType("boolean"); + parameter.setRequired(true); + parameter.setDefaultValue(true); + parameter.setValidations(Collections.emptyList()); + return parameter; + } + + public static Parameter createInitParameter(boolean required) { + return createInitParameter(required, null, null, null); + } + + public static Parameter createInitParameter(boolean required, String defaultValue) { + return createInitParameter(required, defaultValue, null, null); + } + + public static Parameter createInitParameter(boolean required, String defaultValue, BigDecimal minValue, BigDecimal maxValue) { + return setAllParameters("query", "$init", "Integer", "int32", defaultValue, minValue, maxValue, required, false); + } + + public static Parameter createLimitParameter(boolean required) { + return createLimitParameter(required, null, null, null); + } + + public static Parameter createLimitParameter(boolean required, String defaultValue) { + return createLimitParameter(required, defaultValue, null, null); + } + + public static Parameter createLimitParameter(boolean required, String defaultValue, BigDecimal minValue, BigDecimal maxValue) { + return setAllParameters("query", "$limit", "Integer", "int32", defaultValue, minValue, maxValue, required, false); + } + + public static Parameter createTotalParameter(boolean required) { + return createTotalParameter(required, null, null, null); + } + + public static Parameter createTotalParameter(boolean required, String defaultValue) { + return createTotalParameter(required, defaultValue, null, null); + } + + public static Parameter createTotalParameter(boolean required, String defaultValue, BigDecimal minValue, BigDecimal maxValue) { + return setAllParameters("query", "$total", "boolean", null, defaultValue, minValue, maxValue, required, false); + } + + public static Parameter createSelectParameter(boolean required) { + return createSelectParameter(required, null); + } + + public static Parameter createSelectParameter(boolean required, String defaultValue) { + return setAllParameters("query", "$select", "string", null, defaultValue, null, null, required, true); + } + + public static Parameter createExcludeParameter(boolean required) { + return createExcludeParameter(required, null); + } + + public static Parameter createExcludeParameter(boolean required, String defaultValue) { + return setAllParameters("query", "$exclude", "string", null, defaultValue, null, null, required, true); + } + + public static Parameter createExpandParameter(boolean required) { + return createExpandParameter(required, null); + } + + public static Parameter createExpandParameter(boolean required, String defaultValue) { + return setAllParameters("query", "$expand", "string", null, defaultValue, null, null, required, true); + } + + public static Parameter createOrderbyParameter(boolean required) { + return createOrderbyParameter(required, null); + } + + public static Parameter createOrderbyParameter(boolean required, String defaultValue) { + return setAllParameters("query", "$orderby", "string", null, defaultValue, null, null, required, true); + } + + public static Parameter setAllParameters(String in, String name, String type, String format, Object defaultValue, + BigDecimal minValue, BigDecimal maxValue, boolean requiered, boolean collection) { + Parameter parameter = new Parameter(); + parameter.setIn(in); + parameter.setName(name); + parameter.setType(type); + parameter.setFormat(format); + parameter.setDefaultValue(defaultValue); + parameter.setRequired(requiered); + parameter.setCollection(collection); + parameter.setValidations(Collections.emptyList()); + return parameter; + } + + public static List createGetByIdStandardParameters() { + List parameters = new ArrayList<>(); + parameters.add(createSelectParameter(false)); + parameters.add(createExcludeParameter(false)); + parameters.add(createExpandParameter(false)); + return parameters; + } + + public static List createGetAllStandardParameters() { + List parameters = new ArrayList<>(); + parameters.add(createInitParameter(true, "0")); + parameters.add(createLimitParameter(true, "25")); + parameters.add(createTotalParameter(true, "false")); + parameters.add(createSelectParameter(false)); + parameters.add(createExcludeParameter(false)); + parameters.add(createExpandParameter(false)); + parameters.add(createOrderbyParameter(false)); + return parameters; + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/PomFileGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/PomFileGeneratorTest.java new file mode 100644 index 0000000..c8a3070 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/PomFileGeneratorTest.java @@ -0,0 +1,42 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.config.ConfigurationObjectMother; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PomFileGeneratorTest { + + Configuration configuration; + + @BeforeEach + void prepareTest() { + configuration = ConfigurationObjectMother.createCompleteConfigurationWithoutEntitiesAndControllers(); + } + + @Test + void thatGenerates(@TempDir File projectFolder) throws IOException { + PomGenerator.generate( + configuration, + "net.cloudappi.apigen", + "archetype-parent-spring-boot", + "0.0.1-SNAPSHOT", + projectFolder + ); + assertPomExists(projectFolder); + } + + private void assertPomExists(File projectFolder) { + Path pomPath = Paths.get(projectFolder.getPath(), "/pom.xml"); + assertTrue(Files.exists(pomPath), "pom.xml file not generated"); + } +} \ No newline at end of file diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java new file mode 100644 index 0000000..24d1433 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java @@ -0,0 +1,41 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.config.ConfigurationObjectMother; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ProjectStructureGeneratorTest { + + + Configuration configuration; + File projectDirectory; + + @BeforeEach + void prepareTest() { + configuration = ConfigurationObjectMother.createCompleteConfigurationWithoutEntitiesAndControllers(); + } + + @Test + void thatGenerates() throws IOException { + projectDirectory = ProjectStructureGenerator.generate(configuration); + assertBaseFoldersExists(); + } + + private void assertBaseFoldersExists() { + Path resourcesPath = Paths.get(projectDirectory.getPath(), "/src/main/resources"); + assertTrue(Files.exists(resourcesPath), "Resources folder not generated"); + Path javaPath = Paths.get(projectDirectory.getPath(), "/src/main/java/the/group/artifact"); + assertTrue(Files.exists(javaPath), "Java folder not generated"); + Path testPath = Paths.get(projectDirectory.getPath(), "/src/test/java/the/group/artifact"); + assertTrue(Files.exists(testPath), "Test folder not generated"); + } +} \ No newline at end of file diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java new file mode 100644 index 0000000..877154e --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java @@ -0,0 +1,52 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.config.ConfigurationObjectMother; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertTrue; + + +class SpringBootBaseGeneratorTest { + + Configuration configuration; + + @BeforeEach + void prepareTest() { + configuration = ConfigurationObjectMother.createCompleteConfigurationWithoutEntitiesAndControllers(); + } + + @Test + void thatGenerates(@TempDir File projectFolder) throws IOException { + SpringBootBaseGenerator.generate(configuration, projectFolder); + assertPropertiesFilesExists(projectFolder); + assertApplicationClassExists(projectFolder); + } + + private void assertPropertiesFilesExists(File projectFolder) { + Path resourcesPath = Paths.get(projectFolder.getPath(), "/src/main/resources"); + Path prop = Paths.get(resourcesPath.toString(), "application.properties"); + assertTrue(Files.exists(prop), "Properties file not generated"); + String[] profiles = {"dev", "pre", "pro", "test"}; + for (String profile : profiles) { + Path profProp = Paths.get(resourcesPath.toString(), "application-" + profile + ".properties"); + assertTrue(Files.exists(profProp), "Profile " + profile + " config file not generated"); + } + } + + private void assertApplicationClassExists(File projectFolder) { + Path applicationClassPath = Paths.get( + projectFolder.getPath(), + "/src/main/java/the/group/artifact/Application.java" + ); + assertTrue(Files.exists(applicationClassPath), "Application.java not generated"); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java new file mode 100644 index 0000000..23a89d0 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java @@ -0,0 +1,47 @@ +package net.cloudappi.apigen.generatorcore.generator.base; + +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.config.ConfigurationObjectMother; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SpringBootContextTestGeneratorTest { + + Configuration configuration; + + @BeforeEach + void prepareTest() { + configuration = ConfigurationObjectMother.createCompleteConfigurationWithoutEntitiesAndControllers(); + } + + @Test + void thatGenerates(@TempDir File projectFolder) throws IOException { + Path javaTestDir = Paths.get(projectFolder.getPath(), "src/test/java"); + SpringBootContextTestGenerator.generate(configuration.getBasePackage()).writeTo(javaTestDir); + assertTestFolderExists(projectFolder); + assertTestClassExists(projectFolder); + } + + private void assertTestFolderExists(File projectFolder) { + Path testPath = Paths.get(projectFolder.getPath(), "/src/test/"); + assertTrue(Files.exists(testPath), "Test file not generated"); + } + + private void assertTestClassExists(File projectFolder) { + Path applicationClassPath = Paths.get( + projectFolder.getPath(), + "/src/test/java/the/group/artifact/ApplicationTests.java" + ); + assertTrue(Files.exists(applicationClassPath), "NameApplicationTests.java not generated"); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java new file mode 100644 index 0000000..892ccc0 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java @@ -0,0 +1,39 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class ApigenExt2JavapoetTypeTests { + + private static Stream typesProvider() { + return Stream.of( + Arguments.of("String", "java.lang.String"), + Arguments.of("Boolean", "java.lang.Boolean"), + Arguments.of("Float", "java.lang.Float"), + Arguments.of("Double", "java.lang.Double"), + Arguments.of("Integer", "java.lang.Integer"), + Arguments.of("Long", "java.lang.Long"), + Arguments.of("LocalDate", "java.time.LocalDate"), + Arguments.of("OffsetDateTime", "java.time.OffsetDateTime") + ); + } + + @MethodSource("typesProvider") + @ParameterizedTest(name = "{index} => type={0} -> javaType={1}") + void givenType_whenTransformToSimpleType_thenReturnJavaType(String type, String javaType) { + assertEquals(javaType, ApigenExt2JavapoetType.transformSimpleType(type).toString()); + } + + @Test + void givenNonSimpleType_whenTransformToSimpleType_thenThrowException() { + assertThrows(IllegalArgumentException.class, () -> + ApigenExt2JavapoetType.transformSimpleType("Other")); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java new file mode 100644 index 0000000..aed51ab --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java @@ -0,0 +1,87 @@ +package net.cloudappi.apigen.generatorcore.generator.common; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class Openapi2JavapoetTypeTest { + + @Test + void givenStringTypeWithoutFormat_whenTransformToSimpleType_thenReturnJavaString() { + assertEquals("java.lang.String", Openapi2JavapoetType.transformSimpleType("string", null).toString()); + } + + @Test + void givenStringTypeWithByteFormat_whenTransformToSimpleType_thenReturnJavaString() { + assertEquals("java.lang.String", Openapi2JavapoetType.transformSimpleType("string", "byte").toString()); + } + + @Test + void givenStringTypeWithBinaryFormat_whenTransformToSimpleType_thenReturnJavaByteArray() { + assertEquals("byte[]", Openapi2JavapoetType.transformSimpleType("string", "binary").toString()); + } + + @Test + void givenStringTypeWithDateFormat_whenTransformToSimpleType_thenReturnJavaLocalDate() { + assertEquals("java.time.LocalDate", Openapi2JavapoetType.transformSimpleType("string", "date").toString()); + } + + @Test + void givenStringTypeWithDateTimeFormat_whenTransformToSimpleType_thenReturnJavaOffsetDateTime() { + assertEquals("java.time.OffsetDateTime", Openapi2JavapoetType.transformSimpleType("string", "date-time").toString()); + } + + @Test + void givenNumberTypeWithoutFormat_whenTransformToSimpleType_thenReturnJavaDouble() { + assertEquals("java.lang.Double", Openapi2JavapoetType.transformSimpleType("number", null).toString()); + } + + @Test + void givenNumberTypeWithFloatFormat_whenTransformToSimpleType_thenReturnJavaFloat() { + assertEquals("java.lang.Float", Openapi2JavapoetType.transformSimpleType("number", "float").toString()); + } + + @Test + void givenNumberTypeWitDoubleFormat_whenTransformToSimpleType_thenReturnJavaDouble() { + assertEquals("java.lang.Double", Openapi2JavapoetType.transformSimpleType("number", "double").toString()); + } + + @Test + void givenIntegerTypeWithoutFormat_whenTransformToSimpleType_thenReturnJavaLong() { + assertEquals("java.lang.Long", Openapi2JavapoetType.transformSimpleType("integer", null).toString()); + } + + @Test + void givenIntegerTypeWithInt32Format_whenTransformToSimpleType_thenReturnJavaInteger() { + assertEquals("java.lang.Integer", Openapi2JavapoetType.transformSimpleType("integer", "int32").toString()); + } + + @Test + void givenIntegerTypeWithInt64Format_whenTransformToSimpleType_thenReturnJavaLong() { + assertEquals("java.lang.Long", Openapi2JavapoetType.transformSimpleType("integer", "int64").toString()); + } + + @Test + void givenBooleanTypeWithoutFormat_whenTransformToSimpleType_thenReturnJavaBoolean() { + assertEquals("java.lang.Boolean", Openapi2JavapoetType.transformSimpleType("boolean", null).toString()); + } + + @Test + void givenNonSimpleTypeObject_whenTransformToSimpleType_thenThrowException() { + assertThrows(IllegalArgumentException.class, () -> + Openapi2JavapoetType.transformSimpleType("object", null)); + } + + @Test + void givenNonSimpleTypeArray_whenTransformToSimpleType_thenThrowException() { + assertThrows(IllegalArgumentException.class, () -> + Openapi2JavapoetType.transformSimpleType("array", null)); + } + + @Test + void givenNoType_whenTransformToSimpleType_thenThrowException() { + assertThrows(IllegalArgumentException.class, () -> + Openapi2JavapoetType.transformSimpleType(null, null)); + } +} \ No newline at end of file diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilderTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilderTest.java new file mode 100644 index 0000000..7671ec6 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilderTest.java @@ -0,0 +1,95 @@ +package net.cloudappi.apigen.generatorcore.generator.mapper; + +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.mapper.MapperBuilderObjectMother; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.lang.model.element.Modifier; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MapperBuilderTest { + + private static TypeSpec generatedMapper; + + @BeforeAll + static void prepareTest() { + generatedMapper = MapperBuilderObjectMother + .createMapper("EntityName", "the.base.package", "simpleAttribute", "RelatedEntity") + .build(); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenFileStructureIsCorrect() { + assertEquals(Modifier.PUBLIC, generatedMapper.modifiers.toArray()[0], "Public modifier is wrong"); + assertEquals(TypeSpec.Kind.INTERFACE, generatedMapper.kind, "Interface declaration is wrong"); + assertEquals("EntityNameMapper", generatedMapper.name, "The name is wrong"); + assertEquals(1, generatedMapper.annotations.size(), "Number of annotations is wrong"); + assertEquals(4, generatedMapper.methodSpecs.size(), "Number of methods is wrong"); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenPackageIsCorrect() { + MapperBuilder builder = new MapperBuilder("EntityName", "the.base.package", Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), null); + assertEquals("the.base.package.entityname", builder.getPackage(), "The package is wrong"); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenTypeNameIsCorrect() { + TypeName typeName = MapperBuilder.getTypeName("EntityName", "the.base.package"); + assertEquals("the.base.package.entityname.EntityNameMapper", typeName.toString(), "TypeName is wrong"); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenSuperinterfaceIsCorrect() { + assertEquals("[net.cloudappi.apigen.archetypecore.core.ApigenMapper]", generatedMapper.superinterfaces.toString()); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenGeneratedAnnotationIsCorrect() { + assertEquals("@org.mapstruct.Mapper(componentModel = \"spring\", uses = {the.base.package.relatedentity.RelatedEntityMapper.class})", + generatedMapper.annotations.get(0).toString(), "Annotation is wrong"); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenGeneratedToResourceMethodIsCorrect() { + assertEquals("public abstract the.base.package.entityname.web.EntityNameOutResource toResource(\n" + + " the.base.package.entityname.EntityName entity);\n", + generatedMapper.methodSpecs.get(0).toString(), "toResource is wrong"); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenGeneratedListToResourceMethodIsCorrect() { + assertEquals("public abstract java.util.List toResource(\n" + + " java.util.List entities);\n", + generatedMapper.methodSpecs.get(1).toString(), "List toResource is wrong"); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenGeneratedSetToResourceMethodIsCorrect() { + assertEquals("public abstract java.util.Set toResource(\n" + + " java.util.Set entities);\n", + generatedMapper.methodSpecs.get(2).toString(), "Set toResource is wrong"); + } + + @Test + void givenValidAttributes_whenBuildMapper_thenGeneratedUpdateBasicDataIsCorrect() { + MethodSpec methodSpec = generatedMapper.methodSpecs.get(3); + assertEquals("" + + "[" + + "@java.lang.Override, " + + "@org.mapstruct.BeanMapping(ignoreByDefault = true), " + + "@org.mapstruct.Mappings(@org.mapstruct.Mapping(source = \"simpleAttribute\", target = \"simpleAttribute\"))" + + "]", + methodSpec.annotations.toString()); + assertEquals("[the.base.package.entityname.EntityName source, @org.mapstruct.MappingTarget the.base.package.entityname.EntityName target]" + , methodSpec.parameters.toString()); + assertEquals("void", methodSpec.returnType.toString()); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java new file mode 100644 index 0000000..98fc028 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java @@ -0,0 +1,51 @@ +package net.cloudappi.apigen.generatorcore.generator.mapper; + +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourcesData; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MapperGeneratorTest { + + @Test + void givenValidEntitiesData_whenGenerateMapper_thenMapperFileIsGenerated(@TempDir Path filesRootPath) throws IOException { + + Entity testEntity = EntityObjectMother.createEntityWithSimpleAttributes(); + + EntitiesData entitiesData = createMockedEntitiesData(testEntity.getName()); + ResourcesData resourcesData = new ResourcesData(Collections.emptyList()); + + boolean success = new MappersGenerator(Arrays.asList(testEntity), entitiesData, resourcesData, "the.base.package").generate(filesRootPath); + + assertTrue(success, "Mapper generation failed"); + + File projectFolder = filesRootPath.toFile(); + + Path entityPath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "testentitywithsimpleattributes", "TestEntityWithSimpleAttributesMapper.java" + ); + assertTrue(Files.exists(entityPath), "TestEntityWithSimpleAttributesMapper.java not generated"); + } + + private EntitiesData createMockedEntitiesData(String entityName) { + EntitiesData mockedEntitiesData = mock(EntitiesData.class); + when(mockedEntitiesData.getRelatedEntities(entityName)).thenReturn(Collections.emptySet()); + return mockedEntitiesData; + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java new file mode 100644 index 0000000..e9e1cba --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java @@ -0,0 +1,417 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence; + +import com.squareup.javapoet.*; +import lombok.Getter; +import lombok.Setter; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; +import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.junit.jupiter.api.Test; + +import javax.persistence.*; +import javax.validation.constraints.*; +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// TODO #14908 refactor to avoid validate full generation +class EntitiesGeneratorTest { + + private static final String BASE_PACKAGE = "org.test"; + + @Test + void generateSingleEntityTest() { + Entity simpleTestEntity = EntityObjectMother.createSimpleEntityWithName(); + List simpleEntitiesList = new ArrayList<>(); + simpleEntitiesList.add(simpleTestEntity); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(simpleEntitiesList, BASE_PACKAGE); + + assertEquals(1, getGeneratedEntities(entitiesGenerator).size()); + } + + @Test + void checkGeneratedEntityTest() { + Entity simpleTestEntity = EntityObjectMother.createSimpleEntityWithName(); + List simpleEntitiesList = new ArrayList<>(); + simpleEntitiesList.add(simpleTestEntity); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(simpleEntitiesList, BASE_PACKAGE); + + assertEquals(simpleTestEntity.getName(), getGeneratedEntities(entitiesGenerator).get(simpleTestEntity.getName()).name); + } + + @Test + void generateEntityWith_Simple_Attributes() { + Entity testEntityWithSimpleAttributes = EntityObjectMother.createEntityWithSimpleAttributes(); + List testEntitiesList = new ArrayList<>(); + testEntitiesList.add(testEntityWithSimpleAttributes); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(testEntitiesList, BASE_PACKAGE); + + TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntityWithSimpleAttributes.getName()); + + assertEquals(testEntityWithSimpleAttributes.getName(), generatedEntity.name, "Checking if Entity name matches given name:"); + + assertEquals(2, generatedEntity.fieldSpecs.size(), "Checking if Entity contains the given number of fields:"); + + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(Getter.class).build()), "Checking if Entity contains @Getter annotation:"); + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(Setter.class).build()), "Checking if Entity contains @Setter annotation:"); + + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(javax.persistence.Entity.class).build()), "Checking if Entity contains @Entity annotation:"); + + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(javax.persistence.Table.class).addMember("name", "$S", "tableName").build()), "Checking if Entity contains @Table annotation:"); + } + + @Test + void generateEntityWith_OneToMany_Empty_MappedBy() { + Entity testEntityWithComplexAttributes = EntityObjectMother.createEntityWithOneToManyAttributes(""); + List testEntitiesList = new ArrayList<>(); + testEntitiesList.add(testEntityWithComplexAttributes); + testEntitiesList.add(EntityObjectMother.createSimpleEntityWithName()); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(testEntitiesList, BASE_PACKAGE); + + TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntityWithComplexAttributes.getName()); + + assertEquals(testEntityWithComplexAttributes.getName(), generatedEntity.name, "Checking if Entity name matches given name:"); + + assertEquals(3, generatedEntity.fieldSpecs.size(), "Checking if Entity contains the given number of fields:"); + + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(Getter.class).build()), "Checking if Entity contains @Getter annotation:"); + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(Setter.class).build()), "Checking if Entity contains @Setter annotation:"); + + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(javax.persistence.Entity.class).build()), "Checking if Entity contains @Entity annotation:"); + + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(javax.persistence.Table.class).addMember("name", "$S", "tableName").build()), "Checking if Entity contains @Table annotation:"); + + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains(AnnotationSpec.builder(Id.class).build()), "Checking if Entity contains @Id attribute:"); + + assertTrue(generatedEntity.fieldSpecs.get(2).annotations.contains(AnnotationSpec.builder(OneToMany.class).addMember("mappedBy", "$S", "").build()), "Checking if Entity contains @OneToMany attribute:"); + } + + @Test + void generateEntityWith_OneToMany_ManyToOne_Relation() { + List testEntitiesList = EntityObjectMother.createEntitiesWithOneToManyAndManyToOneAttributes(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(testEntitiesList, BASE_PACKAGE); + + TypeSpec generatedEntityOneToMany = getGeneratedEntities(entitiesGenerator).get(testEntitiesList.get(0).getName()); + TypeSpec generatedEntityManyToOne = getGeneratedEntities(entitiesGenerator).get(testEntitiesList.get(1).getName()); + + + assertEquals(testEntitiesList.get(0).getName(), generatedEntityOneToMany.name, "Checking if Entity name matches given name:"); + assertEquals(testEntitiesList.get(1).getName(), generatedEntityManyToOne.name, "Checking if Entity name matches given name:"); + + assertTrue(generatedEntityOneToMany.fieldSpecs.get(2).annotations.contains(AnnotationSpec.builder(OneToMany.class).addMember("mappedBy", "$S", "manyToOneRelationAttribute").build()), "Checking if Entity1 contains @OneToMany annotation:"); + + assertTrue(generatedEntityManyToOne.fieldSpecs.get(0).annotations.contains(AnnotationSpec.builder(ManyToOne.class).build()), "Checking if Entity2 contains @ManyToOne annotation:"); + assertTrue(generatedEntityManyToOne.fieldSpecs.get(0).annotations.contains(AnnotationSpec.builder(JoinColumn.class).addMember("name", "$S", "manyToOneRelationColumn").build()), "Checking if Entity2 contains @JoinColumn annotation:"); + } + + @Test + void generateEntityWith_ManyToMany_Relation() { + List testEntitiesList = EntityObjectMother.createEntitiesWithManyToManyAttributes(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(testEntitiesList, BASE_PACKAGE); + + TypeSpec generatedEntityManyToManyOwner = getGeneratedEntities(entitiesGenerator).get(testEntitiesList.get(0).getName()); + TypeSpec generatedEntityManyToMany = getGeneratedEntities(entitiesGenerator).get(testEntitiesList.get(1).getName()); + + + assertEquals(testEntitiesList.get(0).getName(), generatedEntityManyToManyOwner.name, "Checking if Entity name matches given name:"); + assertEquals(testEntitiesList.get(1).getName(), generatedEntityManyToMany.name, "Checking if Entity name matches given name:"); + + assertEquals("@javax.persistence.ManyToMany", generatedEntityManyToManyOwner.fieldSpecs.get(0).annotations.get(0).toString()); + assertEquals("" + + "@javax.persistence.JoinTable(" + + "name = \"intermediateTableName\", " + + "joinColumns = @javax.persistence.JoinColumn(name = \"column_1_id\"), " + + "inverseJoinColumns = @javax.persistence.JoinColumn(name = \"column_2_id\"), " + + "uniqueConstraints = @javax.persistence.UniqueConstraint(columnNames = {\"column_1_id\", \"column_2_id\"})" + + ")", generatedEntityManyToManyOwner.fieldSpecs.get(0).annotations.get(1).toString()); + assertTrue(generatedEntityManyToMany.fieldSpecs.get(0).annotations.contains(AnnotationSpec.builder(ManyToMany.class).addMember("mappedBy", "$S", "manyToManyOwnerRelationAttribute").build()), "Checking if Entity2 contains @ManyToMany annotation:"); + } + + @Test + void generateEntityWith_OneToOne_Relation() { + List testEntitiesList = EntityObjectMother.createEntitiesWithOneToOneAttributes(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(testEntitiesList, BASE_PACKAGE); + + TypeSpec generatedEntityOneToOneOwner = getGeneratedEntities(entitiesGenerator).get(testEntitiesList.get(0).getName()); + TypeSpec generatedEntityOneToOne = getGeneratedEntities(entitiesGenerator).get(testEntitiesList.get(1).getName()); + + + assertEquals(testEntitiesList.get(0).getName(), generatedEntityOneToOneOwner.name, "Checking if Entity name matches given name:"); + assertEquals(testEntitiesList.get(1).getName(), generatedEntityOneToOne.name, "Checking if Entity name matches given name:"); + + assertTrue(generatedEntityOneToOneOwner.fieldSpecs.get(0).annotations.contains(AnnotationSpec.builder(OneToOne.class).build()), "Checking if Entity1 contains @OneToOne annotation:"); + assertTrue(generatedEntityOneToOneOwner.fieldSpecs.get(0).annotations.contains(AnnotationSpec.builder(JoinColumn.class).addMember("name", "$S", "oneToOneOwnerRelationColumn").build()), "Checking if Entity1 contains @JoinColumn annotation:"); + + assertTrue(generatedEntityOneToOne.fieldSpecs.get(0).annotations.contains(AnnotationSpec.builder(OneToOne.class).addMember("mappedBy", "$S", "oneToOneOwnerRelationAttribute").build()), "Checking if Entity2 contains @OneToOne annotation:"); + } + + private TypeSpec setValidationTest() { + Entity testEntityWithAttributeValidations = EntityObjectMother.createEntityWithAttributeValidations(); + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntityWithAttributeValidations), BASE_PACKAGE); + return getGeneratedEntities(entitiesGenerator).get(testEntityWithAttributeValidations.getName()); + } + + @Test + void generateAttribute_NotNull_Validation() { + TypeSpec generatedEntity = setValidationTest(); + + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(NotNull.class) + .build()), "Checking if Attribute has @NotNull Annotation:" + ); + } + + @Test + void generateAttribute_Size_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Size.class) + .addMember("min", "1") + .addMember("max", "2") + .build()), + "Checking if Attribute has @Size Annotation:" + ); + } + + @Test + void generateAttribute_Min_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Min.class) + .addMember("value", "1") + .build()), "Checking if Attribute has @Min Annotation:" + ); + } + + @Test + void generateAttribute_Max_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Max.class) + .addMember("value", "2") + .build()), "Checking if Attribute has @Max Annotation:" + ); + } + + @Test + void generateAttribute_Email_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Email.class) + .build()), "Checking if Attribute has @Email Annotation:" + ); + } + + @Test + void generateAttribute_NotEmtpy_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(NotEmpty.class) + .build()), "Checking if Attribute has @NotEmtpy Annotation:" + ); + } + + @Test + void generateAttribute_NotBlank_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(NotBlank.class) + .build()), "Checking if Attribute has @NotBlank Annotation:" + ); + } + + @Test + void generateAttribute_Positive_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Positive.class) + .build()), "Checking if Attribute has @Positive Annotation:" + ); + } + + @Test + void generateAttribute_PositiveOrZero_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(PositiveOrZero.class) + .build()), "Checking if Attribute has @PositiveOrZero Annotation:" + ); + } + + @Test + void generateAttribute_Negative_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Negative.class) + .build()), "Checking if Attribute has @Negative Annotation:" + ); + } + + @Test + void generateAttribute_NegativeOrZero_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(NegativeOrZero.class) + .build()), "Checking if Attribute has @NegativeOrZero Annotation:" + ); + } + + @Test + void generateAttribute_Past_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Past.class) + .build()), "Checking if Attribute has @Past Annotation:" + ); + } + + @Test + void generateAttribute_PastOrPresent_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(PastOrPresent.class) + .build()), "Checking if Attribute has @PastOrPresent Annotation:" + ); + } + + @Test + void generateAttribute_Future_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Future.class) + .build()), "Checking if Attribute has @Future Annotation:" + ); + } + + @Test + void generateAttribute_FutureOrPresent_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(FutureOrPresent.class) + .build()), "Checking if Attribute has @FutureOrPresent Annotation:" + ); + } + + @Test + void generateAttribute_Pattern_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Pattern.class) + .addMember("regexp", "$S", "[^i*&2@]") + .build()), "Checking if Attribute has @Pattern Annotation:" + ); + } + + @Test + void generateAttribute_Digits_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(Digits.class) + .addMember("integer", "2") + .addMember("fraction", "2") + .build()), "Checking if Attribute has @Digits Annotation:" + ); + } + + @Test + void generateAttribute_DecimalMin_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(DecimalMin.class) + .addMember("value", "$S", "0.1") + .addMember("inclusive", "$L", true) + .build()), "Checking if Attribute has @DecimalMin Annotation:" + ); + } + + @Test + void generateAttribute_DecimalMax_Validation() { + TypeSpec generatedEntity = setValidationTest(); + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(DecimalMax.class) + .addMember("value", "$S", "0.2") + .addMember("inclusive", "$L", true) + .build()), "Checking if Attribute has @DecimalMax Annotation:" + ); + } + + @Test + void generateEntityWith_Array_ManyToMany_Attribute() { + List testEntitiesList = EntityObjectMother.createEntitiesWithManyToManyAttributes(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(testEntitiesList, BASE_PACKAGE); + + TypeSpec generatedEntityManyToManyOwner = getGeneratedEntities(entitiesGenerator).get(testEntitiesList.get(0).getName()); + + String type = testEntitiesList.get(0).getAttributes().get(0).getType(); + + assertEquals(ParameterizedTypeName.get(ClassName.get(Set.class), ClassName.get(concatPackage(BASE_PACKAGE, type), type)), generatedEntityManyToManyOwner.fieldSpecs.get(0).type, "Checking if attribute has Set field"); + } + + @Test + void generateEntityWith_Array_OneToMany_Attribute() { + List testEntitiesList = EntityObjectMother.createEntitiesWithOneToManyAndManyToOneAttributes(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(testEntitiesList, BASE_PACKAGE); + + TypeSpec generatedEntityOneToMany = getGeneratedEntities(entitiesGenerator).get(testEntitiesList.get(0).getName()); + + String type = testEntitiesList.get(0).getAttributes().get(2).getType(); + + assertEquals(ParameterizedTypeName.get(ClassName.get(Set.class), ClassName.get(concatPackage(BASE_PACKAGE, type), type)), generatedEntityOneToMany.fieldSpecs.get(2).type, "Checking if attribute has Set field"); + } + + @Test + void generateEntityWith_Sequence_Id_Attribute() { + Entity testEntity = EntityObjectMother.createEntitiesSequenceIdAttribute(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntity), BASE_PACKAGE); + + TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntity.getName()); + + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(SequenceGenerator.class) + .addMember("name", "$S", testEntity.getAttributes().get(0).getColumns().get(0).getSequence() + "_name") + .addMember("sequenceName", "$S", testEntity.getAttributes().get(0).getColumns().get(0).getSequence()) + .build() + ), "Checking if attribute has @SequenceGenerator annotation"); + + assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( + AnnotationSpec.builder(GeneratedValue.class) + .addMember("generator", "$S", testEntity.getAttributes().get(0).getColumns().get(0).getSequence() + "_name") + .build() + ), "Checking if attribute has @GeneratedValue annotation"); + } + + @Test + void generateEntityWith_GetAndSet_Id_Method() { + Entity testEntity = EntityObjectMother.createEntitiesSequenceIdAttribute(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntity), BASE_PACKAGE); + + TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntity.getName()); + + assertEquals("getId", generatedEntity.methodSpecs.get(0).name, "Checking if getId method exists"); + assertEquals("java.lang.Long", generatedEntity.methodSpecs.get(0).returnType.toString(), "Checking getId method return type"); + + assertEquals("setId", generatedEntity.methodSpecs.get(1).name, "Checking if setId method exists"); + assertEquals("void", generatedEntity.methodSpecs.get(1).returnType.toString(), "Checking setId method return type"); + } + + private String concatPackage(String basePackage, String newPackage) { + return String.format("%s.%s", basePackage, newPackage.toLowerCase()); + } + + private Map getGeneratedEntities(EntitiesGenerator eg) { + return eg.getBuilders().stream().map(AbstractClassBuilder::build).collect(Collectors.toMap(b -> b.name, b -> b)); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java new file mode 100644 index 0000000..13da229 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java @@ -0,0 +1,40 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.repository; + +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; + +class RepositoriesGeneratorTest { + + @Test + void givenValidData_whenGenerateRepository_thenRepositoryFileIsGenerated(@TempDir Path filesRootPath) throws IOException { + + Entity testEntity = EntityObjectMother.createSimpleEntityWithStringAsPrimaryKey(); + EntitiesData entitiesData = Mockito.mock(EntitiesData.class); + Mockito.when(entitiesData.getIDType(anyString())).thenReturn(TypeName.get(Long.class)); + new RepositoriesGenerator(Collections.singletonList(testEntity), entitiesData, "the.base.package").generate(filesRootPath); + + File projectFolder = filesRootPath.toFile(); + + Path entityPath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "simpletestentity", "SimpleTestEntityRepository.java" + ); + assertTrue(Files.exists(entityPath), "SimpleTestEntityRepository.java not generated"); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java new file mode 100644 index 0000000..938a757 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java @@ -0,0 +1,58 @@ +package net.cloudappi.apigen.generatorcore.generator.persistence.repository; + +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.lang.model.element.Modifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class RepositoryBuilderTests { + + private static TypeSpec generatedRepository; + + @BeforeAll + static void prepareTest() { + Entity entity = EntityObjectMother.createSimpleEntityWithStringAsPrimaryKey(); + RepositoryBuilder repositoryBuilder = new RepositoryBuilder(entity.getName(), "the.base.package", EntityBuilder.getIDTypeName(entity, "the.base.package")); + generatedRepository = repositoryBuilder.build(); + } + + @Test + void givenAnEntityWithStringAsPrimaryKey_whenGenerateService_thenFileStructureIsCorrect() { + assertEquals(Modifier.PUBLIC, generatedRepository.modifiers.toArray()[0], "Public modifier is wrong"); + assertEquals(TypeSpec.Kind.INTERFACE, generatedRepository.kind, "Interface declaration is wrong"); + assertEquals("SimpleTestEntityRepository", generatedRepository.name, "The name is wrong"); + assertEquals(1, generatedRepository.annotations.size(), "Number of annotations is wrong"); + assertEquals(0, generatedRepository.methodSpecs.size(), "Number of methods is wrong"); + } + + @Test + void givenAnEntityWithStringAsPrimaryKey_whenGenerateRepository_thenAnnotationsAreCorrect() { + assertEquals("@org.springframework.stereotype.Repository", + generatedRepository.annotations.get(0).toString(), "Annotation is wrong"); + } + + @Test + void givenAnEntityWithStringAsPrimaryKey_whenGenerateRepository_thenSuperinterfaceIsCorrect() { + assertEquals("net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository", + generatedRepository.superinterfaces.get(0).toString(), "Superinterface is wrong"); + } + + @Test + void givenValidAttributes_whenAskForPackage_thenPackageIsCorrect() { + RepositoryBuilder repositoryBuilder = new RepositoryBuilder("EntityName", "the.base.package", TypeName.get(String.class)); + assertEquals("the.base.package.entityname", repositoryBuilder.getPackage(), "The package is wrong"); + } + + @Test + void givenValidAttributes_whenAskForTypeName_thenTypeNameIsCorrect() { + TypeName typeName = RepositoryBuilder.getTypeName("EntityName", "the.base.package"); + assertEquals("the.base.package.entityname.EntityNameRepository", typeName.toString(), "TypeName is wrong"); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java new file mode 100644 index 0000000..fa1aeb1 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java @@ -0,0 +1,175 @@ +package net.cloudappi.apigen.generatorcore.generator.service; + +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData.AttributeData; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class RelationManagerBuilderTests { + + static TypeSpec spec; + + @BeforeAll + static void init() { + Map attributes = new HashMap<>(); + attributes.put("simple", new AttributeData(false, "Simple", true, false, TypeName.get(String.class))); + attributes.put("simpleTwo", new AttributeData(false, "Simple", true, false, TypeName.get(String.class))); + attributes.put("list", new AttributeData(true, "ListItem", true, false, TypeName.get(String.class))); + attributes.put("notOwned", new AttributeData(true, "Other", false, false, TypeName.get(String.class))); + RelationManagerBuilder builder = new RelationManagerBuilder("EntityFirst", "the.pkg", attributes); + + spec = builder.build(); + } + + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenNameCorrect() { + assertEquals("EntityFirstRelationManager", spec.name); + } + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenAnnotationsCorrect() { + assertEquals("[@org.springframework.stereotype.Component]", spec.annotations.toString()); + } + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenSuperclassCorrect() { + assertEquals("net.cloudappi.apigen.archetypecore.core.AbstractRelationsManager", spec.superclass.toString()); + } + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenHasAllFields() { + assertEquals(2, spec.fieldSpecs.size()); + + FieldSpec fieldSpec; + + fieldSpec = spec.fieldSpecs.get(0); + assertEquals("listItemService", fieldSpec.name); + assertEquals("the.pkg.listitem.ListItemService", fieldSpec.type.toString()); + assertEquals("[@org.springframework.beans.factory.annotation.Autowired]", fieldSpec.annotations.toString()); + + fieldSpec = spec.fieldSpecs.get(1); + assertEquals("simpleService", fieldSpec.name); + assertEquals("the.pkg.simple.SimpleService", fieldSpec.type.toString()); + assertEquals("[@org.springframework.beans.factory.annotation.Autowired]", fieldSpec.annotations.toString()); + } + + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenHasAllMethods() { + assertEquals(6, spec.methodSpecs.size()); + } + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenMainCreateMethodCorrect() { + MethodSpec methodSpec = spec.methodSpecs.get(0); + + assertEquals("createOrRetrieveRelations", methodSpec.name); + assertEquals("[@java.lang.Override, @org.springframework.transaction.annotation.Transactional(propagation = org.springframework.transaction.annotation.Propagation.MANDATORY)]", + methodSpec.annotations.toString()); + assertEquals("[the.pkg.entityfirst.EntityFirst entityFirst]", methodSpec.parameters.toString()); + assertEquals("void", methodSpec.returnType.toString()); + assertEquals("" + + "net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors errors = new net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors();\n" + + "if (entityFirst.getList() != null) {\n" + + " entityFirst.setList(entityFirst.getList().stream().map(e -> createOrRetrieve(e, errors)).collect(java.util.stream.Collectors.toSet()));\n" + + "}\n" + + "if (entityFirst.getSimple() != null) {\n" + + " entityFirst.setSimple(createOrRetrieve(entityFirst.getSimple(), errors));\n" + + "}\n" + + "if (entityFirst.getSimpleTwo() != null) {\n" + + " entityFirst.setSimpleTwo(createOrRetrieve(entityFirst.getSimpleTwo(), errors));\n" + + "}\n" + + "if (!errors.isEmpty()) {\n" + + " throw new net.cloudappi.apigen.archetypecore.exceptions.RelationalErrorsException(errors);\n" + + "}\n" + , methodSpec.code.toString()); + } + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenMainUpdateMethodCorrect() { + MethodSpec methodSpec = spec.methodSpecs.get(1); + + assertEquals("updateRelations", methodSpec.name); + assertEquals("[@java.lang.Override, @org.springframework.transaction.annotation.Transactional(propagation = org.springframework.transaction.annotation.Propagation.MANDATORY)]", + methodSpec.annotations.toString()); + assertEquals("[the.pkg.entityfirst.EntityFirst persistedEntityFirst, the.pkg.entityfirst.EntityFirst entityFirst, java.util.Set fields]", methodSpec.parameters.toString()); + assertEquals("void", methodSpec.returnType.toString()); + assertEquals("" + + "net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors errors = new net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors();\n" + + "boolean updateAll = (fields == null);\n" + + "if (updateAll || fields.contains(\"list\")) {\n" + + " if (entityFirst.getList() != null) {\n" + + " persistedEntityFirst.getList().clear();\n" + + " persistedEntityFirst.getList().addAll(entityFirst.getList().stream().map(e -> retrieve(e, errors)).collect(java.util.stream.Collectors.toSet()));\n" + + " } else {\n" + + " persistedEntityFirst.setList(null);\n" + + " }\n" + + "}\n" + + "if (updateAll || fields.contains(\"simple\")) {\n" + + " if (entityFirst.getSimple() != null) {\n" + + " persistedEntityFirst.setSimple(retrieve(entityFirst.getSimple(), errors));\n" + + " } else {\n" + + " persistedEntityFirst.setSimple(null);\n" + + " }\n" + + "}\n" + + "if (updateAll || fields.contains(\"simpleTwo\")) {\n" + + " if (entityFirst.getSimpleTwo() != null) {\n" + + " persistedEntityFirst.setSimpleTwo(retrieve(entityFirst.getSimpleTwo(), errors));\n" + + " } else {\n" + + " persistedEntityFirst.setSimpleTwo(null);\n" + + " }\n" + + "}\n" + + "if (!errors.isEmpty()) {\n" + + " throw new net.cloudappi.apigen.archetypecore.exceptions.RelationalErrorsException(errors);\n" + + "}\n" + , methodSpec.code.toString()); + } + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenCreateSubMethodCorrect() { + MethodSpec methodSpec = spec.methodSpecs.get(2); + assertEquals("createOrRetrieve", methodSpec.name); + assertEquals("[the.pkg.listitem.ListItem listItem, net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors errors]", methodSpec.parameters.toString()); + assertEquals("the.pkg.listitem.ListItem", methodSpec.returnType.toString()); + assertEquals("" + + "if (listItem.isReference()) {\n" + + " return retrieve(listItem, errors);\n" + + "} else {\n" + + " try {\n" + + " return listItemService.create(listItem);\n" + + " } catch (net.cloudappi.apigen.archetypecore.exceptions.RelationalErrorsException e) {\n" + + " errors.merge(e.getRelationalErrors());\n" + + " return null;\n" + + " }\n" + + "}\n" + , methodSpec.code.toString()); + } + + @Test + void givenValidRelationManagerBuilder_whenBuild_thenUpdateSubMethodCorrect() { + MethodSpec methodSpec = spec.methodSpecs.get(4); + assertEquals("retrieve", methodSpec.name); + assertEquals("[the.pkg.listitem.ListItem listItem, net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors errors]", methodSpec.parameters.toString()); + assertEquals("the.pkg.listitem.ListItem", methodSpec.returnType.toString()); + assertEquals("" + + "the.pkg.listitem.ListItem retrieved = (listItem.getId() == null) ? null : listItemService.getOne(listItem.getId()).orElse(null);\n" + + "if (retrieved == null) errors.register(the.pkg.listitem.ListItem.class, listItem.getId());\n" + + "return retrieved;\n" + , methodSpec.code.toString()); + } + + @Test + void givenEntityName_whenGetTypeName_thenCorrect() { + TypeName typeName = RelationManagerBuilder.getTypeName("EntityName", "the.pkg"); + assertEquals("the.pkg.entityname.EntityNameRelationManager", typeName.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilderTests.java new file mode 100644 index 0000000..1646779 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilderTests.java @@ -0,0 +1,89 @@ +package net.cloudappi.apigen.generatorcore.generator.service; + +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.entity.Attribute; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.lang.model.element.Modifier; +import java.util.Collections; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ServiceBuilderTests { + + private static TypeSpec generatedService; + + @BeforeAll + static void prepareTest() { + Entity entity = EntityObjectMother.createSimpleEntityWithStringAsPrimaryKey(); + ServiceBuilder serviceBuilder = new ServiceBuilder( + entity.getName(), + "the.base.package", + entity.getAttributes().stream().map(Attribute::getName).collect(Collectors.toSet()), + EntityBuilder.getIDTypeName(entity, "the.base.package") + ); + generatedService = serviceBuilder.build(); + } + + @Test + void givenAnEntityWithStringAsPrimaryKey_whenGenerateService_thenFileStructureIsCorrect() { + assertEquals(Modifier.PUBLIC, generatedService.modifiers.toArray()[0], "Public modifier is wrong"); + assertEquals(TypeSpec.Kind.CLASS, generatedService.kind, "Class declaration is wrong"); + assertEquals("SimpleTestEntityService", generatedService.name, "The name is wrong"); + assertEquals(2, generatedService.annotations.size(), "Number of annotations is wrong"); + assertEquals(2, generatedService.methodSpecs.size(), "Number of methods is wrong"); + } + + @Test + void givenAnEntityWithStringAsPrimaryKey_whenGenerateService_thenAnnotationsAreCorrect() { + assertEquals("@lombok.extern.slf4j.Slf4j", + generatedService.annotations.get(0).toString(), "Annotation is wrong"); + assertEquals("@org.springframework.stereotype.Service", + generatedService.annotations.get(1).toString(), "Annotation is wrong"); + } + + @Test + void givenAnEntityWithStringAsPrimaryKey_whenGenerateService_thenConstructorMethodIsCorrect() { + assertEquals("public Constructor(the.base.package.simpletestentity.SimpleTestEntityRepository repository,\n" + + " @org.springframework.lang.Nullable net.cloudappi.apigen.archetypecore.core.AbstractRelationsManager relationsManager,\n" + + " @org.springframework.lang.Nullable net.cloudappi.apigen.archetypecore.core.ApigenMapper mapper) {\n" + + " super(repository, relationsManager, mapper);\n" + + "}\n", + generatedService.methodSpecs.get(0).toString(), "Constructor Method is wrong"); + } + + @Test + void givenAnEntity_whenGenerateService_thenUpdateBasicDataPartiallyMethodIsCorrect() { + assertEquals("@java.lang.Override\n" + + "protected void updateBasicDataPartially(\n" + + " the.base.package.simpletestentity.SimpleTestEntity persistedEntity,\n" + + " the.base.package.simpletestentity.SimpleTestEntity entity,\n" + + " java.util.Set fields) {\n" + + " if (fields == null) {\n" + + " mapper.updateBasicData(entity, persistedEntity);\n" + + " } else {\n" + + " if (fields.contains(\"id\")) persistedEntity.setId(entity.getId());\n" + + " }\n" + + "}\n", + generatedService.methodSpecs.get(1).toString()); + } + + @Test + void givenValidAttributes_whenAskForPackage_thenPackageIsCorrect() { + ServiceBuilder serviceBuilder = new ServiceBuilder("EntityName", "the.base.package", Collections.emptySet(), TypeName.get(String.class)); + assertEquals("the.base.package.entityname", serviceBuilder.getPackage(), "The package is wrong"); + } + + @Test + void givenValidAttributes_whenAskForTypeName_thenTypeNameIsCorrect() { + TypeName typeName = ServiceBuilder.getTypeName("EntityName", "the.base.package"); + assertEquals("the.base.package.entityname.EntityNameService", typeName.toString(), "TypeName is wrong"); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGeneratorTest.java new file mode 100644 index 0000000..1e0f5f7 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGeneratorTest.java @@ -0,0 +1,41 @@ +package net.cloudappi.apigen.generatorcore.generator.service; + +import com.squareup.javapoet.TypeName; +import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; + +class ServicesGeneratorTest { + + @Test + void givenValidData_whenGenerateService_thenServiceFileIsGenerated(@TempDir Path filesRootPath) throws IOException { + Entity testEntity = EntityObjectMother.createSimpleEntityWithStringAsPrimaryKey(); + EntitiesData entitiesData = Mockito.mock(EntitiesData.class); + Mockito.when(entitiesData.getBasicAttributes(anyString())).thenReturn(Collections.emptySet()); + Mockito.when(entitiesData.getIDType(anyString())).thenReturn(TypeName.get(Long.class)); + new ServicesGenerator(Arrays.asList(testEntity), entitiesData, "the.base.package").generate(filesRootPath); + + File projectFolder = filesRootPath.toFile(); + + Path entityPath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "simpletestentity", "SimpleTestEntityService.java" + ); + assertTrue(Files.exists(entityPath), "SimpleTestEntityService.java not generated"); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java new file mode 100644 index 0000000..3cf49c8 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java @@ -0,0 +1,37 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller; + +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.ControllerObjectMother; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; + +class ControllersGeneratorTest { + + @Test + void givenValidControllers_whenGenerateController_thenControllerFileIsGenerated(@TempDir Path filesRootPath) throws IOException { + + List controllerList = Collections.singletonList(ControllerObjectMother.createControllerWithStandardEndpoints("EntityName", "/mapping")); + EntitiesData entitiesData = Mockito.mock(EntitiesData.class); + new ControllersGenerator(controllerList, entitiesData, "the.base.package").generate(filesRootPath); + + File projectFolder = filesRootPath.toFile(); + + Path entityPath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "entityname", "web", "EntityNameController.java" + ); + Assertions.assertTrue(Files.exists(entityPath), "EntityNameController.java not generated"); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java new file mode 100644 index 0000000..5b255a0 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java @@ -0,0 +1,91 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller; + +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.ControllerObjectMother; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import javax.lang.model.element.Modifier; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EntityControllerBuilderTests { + + private static TypeSpec generatedController; + + @BeforeAll + static void prepareTest() { + Controller controller = ControllerObjectMother.createControllerWithStandardEndpoints("EntityName", "/mapping"); + EntitiesData entitiesData = Mockito.mock(EntitiesData.class); + EntityControllerBuilder controllerBuilder = new EntityControllerBuilder(controller, entitiesData,"the.base.package"); + generatedController = controllerBuilder.build(); + } + + @Test + void givenValidController_whenBuildController_thenFileStructureIsCorrect() { + assertEquals(Modifier.PUBLIC.toString(), generatedController.modifiers.toArray()[0].toString(), "Public modifier is wrong"); + assertEquals("CLASS", generatedController.kind.name(), "Class declaration is wrong"); + assertEquals("EntityNameController", generatedController.name, "The name is wrong"); + assertEquals(5, generatedController.annotations.size(), "Number of annotations is wrong"); + } + + @Test + void givenValidController_whenBuildController_thenAnnotationsAreCorrect() { + assertEquals("@lombok.extern.slf4j.Slf4j", + generatedController.annotations.get(0).toString(), + "@Slf4j annotation is wrong"); + assertEquals("@org.springframework.web.bind.annotation.RestController", + generatedController.annotations.get(1).toString(), + "@RestController annotation is wrong"); + assertEquals("@org.springframework.web.bind.annotation.RequestMapping(\"/mapping\")", + generatedController.annotations.get(2).toString(), + "@RequestMapping annotation is wrong"); + assertEquals("@io.swagger.annotations.Api(tags = \"EntityName\")", + generatedController.annotations.get(3).toString(), + "@Api annotation is wrong"); + assertEquals("@org.springframework.validation.annotation.Validated", + generatedController.annotations.get(4).toString(), + "@Validated annotation is wrong"); + } + + @Test + void givenValidController_whenBuildController_thenServiceFieldIsCorrect() { + FieldSpec serviceFieldSpec = generatedController.fieldSpecs.get(0); + assertEquals("service", serviceFieldSpec.name, "The service field name is wrong"); + assertEquals("the.base.package.entityname.EntityNameService", serviceFieldSpec.type.toString(), "The service field type is wrong"); + assertEquals("@org.springframework.beans.factory.annotation.Autowired", serviceFieldSpec.annotations.get(0).toString(), "The service field annotation is wrong"); + } + + @Test + void givenValidController_whenBuildController_thenMapperFieldIsCorrect() { + FieldSpec mapperFieldSpec = generatedController.fieldSpecs.get(1); + assertEquals("mapper", mapperFieldSpec.name, "The mapper field name is wrong"); + assertEquals("the.base.package.entityname.EntityNameMapper", mapperFieldSpec.type.toString(), "The mapper field type is wrong"); + assertEquals("@org.springframework.beans.factory.annotation.Autowired", mapperFieldSpec.annotations.get(0).toString(), "The mapper field annotation is wrong"); + } + + @Test + void givenValidController_whenBuildController_thenResourceNamingTranslatorFieldIsCorrect() { + FieldSpec mapperFieldSpec = generatedController.fieldSpecs.get(2); + assertEquals("namingTranslator", mapperFieldSpec.name, "The mapper field name is wrong"); + assertEquals("net.cloudappi.apigen.archetypecore.core.resource.ResourceNamingTranslator", mapperFieldSpec.type.toString(), "The mapper field type is wrong"); + assertEquals("@org.springframework.beans.factory.annotation.Autowired", mapperFieldSpec.annotations.get(0).toString(), "The mapper field annotation is wrong"); + } + + @Test + void givenValidControllerWithAllEndpoints_whenBuildController_thenAllEndpointsAreGenerated() { + assertEquals(6, generatedController.methodSpecs.size(), "Not all endpoints have been generated"); + } + + @Test + void givenValidController_whenBuildController_thenPackageIsCorrect() { + Controller controller = ControllerObjectMother.createControllerWithStandardEndpoints("EntityName", "/mapping"); + EntitiesData entitiesData = Mockito.mock(EntitiesData.class); + EntityControllerBuilder builder = new EntityControllerBuilder(controller, entitiesData, "the.base.package"); + assertEquals("the.base.package.entityname.web", builder.getPackage(), "The package is wrong"); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java new file mode 100644 index 0000000..1dcd757 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java @@ -0,0 +1,36 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import net.cloudappi.apigen.generatorcore.config.controller.Attribute; + +import java.util.ArrayList; + +public class AttributeObjectMother { + + private AttributeObjectMother() { + // Intentional blank + } + + public static Attribute createSimpleStringAttribute(String name) { + return createSimpleAttribute("string", name, name, null); + } + + public static Attribute createSimpleAttribute(String type, String name) { + return createSimpleAttribute(type, name, name, null); + } + + public static Attribute createSimpleAttribute(String type, String name, String entityFieldName) { + return createSimpleAttribute(type, name, entityFieldName, null); + } + + public static Attribute createSimpleAttribute(String type, String name, String entityFieldName, String format) { + Attribute attribute = new Attribute(); + attribute.setType(type); + attribute.setName(name); + attribute.setEntityFieldName(entityFieldName); + attribute.setFormat(format); + attribute.setAttributes(new ArrayList<>()); + attribute.setValidations(new ArrayList<>()); + attribute.setCollection(false); + return attribute; + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java new file mode 100644 index 0000000..59fa31c --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java @@ -0,0 +1,53 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class CustomEndpointBuilderTests { + + static TypeSpec typeSpec; + + @BeforeAll + static void init() { + Endpoint endPoint = EndpointObjectMother.customEndpoint(); + CustomEndpointBuilder builderEndpoint = new CustomEndpointBuilder(new Mapping("/custom"), endPoint, "the.base.package"); + TypeSpec.Builder builder = TypeSpec.classBuilder("Endpoint"); + builderEndpoint.apply(builder); + typeSpec = builder.build(); + } + + @Test + void givenCustomEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertFalse(methodSpec.isConstructor()); + assertEquals("custom", methodSpec.name); + + assertEquals(2, methodSpec.annotations.size()); + AnnotationSpec annotationSpec = methodSpec.annotations.get(0); + assertEquals("@org.springframework.web.bind.annotation.PostMapping(\"/endpoint\")", annotationSpec.toString()); + annotationSpec = methodSpec.annotations.get(1); + assertEquals("@org.springframework.web.bind.annotation.ResponseStatus(code = org.springframework.http.HttpStatus.CREATED)", annotationSpec.toString()); + + assertEquals(1, methodSpec.modifiers.size()); + assertEquals("[public]", methodSpec.modifiers.toString()); + + assertEquals(1, methodSpec.parameters.size()); + ParameterSpec parameterSpec = methodSpec.parameters.get(0); + assertEquals("[@org.springframework.web.bind.annotation.RequestBody, @javax.validation.Valid]", parameterSpec.annotations.toString()); + assertEquals("the.base.package.custom.web.CreateCustomEndpointResource", parameterSpec.type.toString()); + assertEquals("body", parameterSpec.name); + + assertEquals("// TODO: Implement this non standard endpoint\n" + + "throw new net.cloudappi.apigen.archetypecore.exceptions.NotImplementedException(\"POST /endpoint\");\n", methodSpec.code.toString()); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java new file mode 100644 index 0000000..b1edc3f --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java @@ -0,0 +1,82 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class DeleteEndpointBuilderTests { + + static TypeSpec typeSpec; + + @BeforeAll + static void init() { + Endpoint endPoint = EndpointObjectMother.standardDelete("delete", "EntityName"); + DeleteEndpointBuilder builderEndpoint = new DeleteEndpointBuilder(new Mapping("/entities"), endPoint, null, "the.base.package"); + TypeSpec.Builder builder = TypeSpec.classBuilder("DeleteEndpoint"); + builderEndpoint.apply(builder); + typeSpec = builder.build(); + } + + @Test + void givenDeleteEndpointBuilder_whenBuild_thenNameCorrect() { + assertEquals("DeleteEndpoint", typeSpec.name); + } + + @Test + void givenDeleteEndpointBuilder_whenBuild_thenModifierIsPublic() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenDeleteEndpointBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenDeleteEndpointBuilder_whenBuild_thenNoHaveAnnotation() { + assertEquals(0, typeSpec.annotations.size()); + } + + @Test + void givenDeleteEndpointBuilder_whenBuild_thenNoHaveModifier() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenDeleteEndpointBuilder_whenBuild_thenNoHaveFieldsSpecs() { + assertEquals(0, typeSpec.fieldSpecs.size()); + } + + @Test + void givenDeleteEndpointBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("java.lang.Object", typeSpec.superclass.toString()); + } + + @Test + void givenDeleteEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertFalse(methodSpec.isConstructor()); + assertEquals("delete", methodSpec.name); + + assertEquals(2, methodSpec.annotations.size()); + AnnotationSpec annotationSpec = methodSpec.annotations.get(0); + assertEquals("@org.springframework.web.bind.annotation.DeleteMapping(\"/{id}\")", annotationSpec.toString()); + annotationSpec = methodSpec.annotations.get(1); + assertEquals("@org.springframework.web.bind.annotation.ResponseStatus(code = org.springframework.http.HttpStatus.OK)", annotationSpec.toString()); + assertEquals(1, methodSpec.modifiers.size()); + assertEquals("[public]", methodSpec.modifiers.toString()); + + assertEquals("@org.springframework.web.bind.annotation.PathVariable(\"id\") java.lang.Long id", methodSpec.parameters.get(0).toString()); + assertEquals("java.lang.Long", methodSpec.parameters.get(0).type.toString()); + assertEquals("id", methodSpec.parameters.get(0).name); + + assertEquals("service.delete(id);\n", methodSpec.code.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java new file mode 100644 index 0000000..17ff3c6 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java @@ -0,0 +1,73 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class EndpointBuilderFactoryTests { + + private final String PACKAGE = "the.base.package"; + private final String ENTITY_NAME = "EntityName"; + private final Mapping ROOT_MAPPING = new Mapping("/entities"); + private final EntitiesData ENTITIES_DATA = Mockito.mock(EntitiesData.class); + + @Test + void givenPostEndpoint_whenAskForBuilder_thenCorrectBuilderIsReturned() { + Endpoint endpoint = EndpointObjectMother.standardPost("postEndpoint", ENTITY_NAME); + EndpointBuilder endpointBuilder = EndpointBuilderFactory.create(ROOT_MAPPING, endpoint, ENTITIES_DATA, PACKAGE); + + assertTrue(endpointBuilder instanceof PostEndpointBuilder, "Expected PostEndpointBuilder"); + } + + @Test + void givenPostSearchEndpoint_whenAskForBuilder_thenCorrectBuilderIsReturned() { + Endpoint endpoint = EndpointObjectMother.standardSearch("postSearchEndpoint", ENTITY_NAME); + EndpointBuilder endpointBuilder = EndpointBuilderFactory.create(ROOT_MAPPING, endpoint, ENTITIES_DATA, PACKAGE); + + assertTrue(endpointBuilder instanceof PostSearchEndpointBuilder, "Expected PostSearchEndpointBuilder"); + } + + @Test + void givenGetAllEndpoint_whenAskForBuilder_thenCorrectBuilderIsReturned() { + Endpoint endpoint = EndpointObjectMother.standardGetAll("getAllEndpoint", ENTITY_NAME); + EndpointBuilder endpointBuilder = EndpointBuilderFactory.create(ROOT_MAPPING, endpoint, ENTITIES_DATA, PACKAGE); + + assertTrue(endpointBuilder instanceof GetAllEndpointBuilder, "Expected GetAllEndpointBuilder"); + } + + @Test + void givenGetByIdEndpoint_whenAskForBuilder_thenCorrectBuilderIsReturned() { + Endpoint endpoint = EndpointObjectMother.standardGetById("getByIdEndpoint", ENTITY_NAME); + EndpointBuilder endpointBuilder = EndpointBuilderFactory.create(ROOT_MAPPING, endpoint, ENTITIES_DATA, PACKAGE); + + assertTrue(endpointBuilder instanceof GetByIdEndpointBuilder, "Expected GetByIdEndpointBuilder"); + } + + @Test + void givenPutEndpoint_whenAskForBuilder_thenCorrectBuilderIsReturned() { + Endpoint endpoint = EndpointObjectMother.standardPut("putEndpoint", ENTITY_NAME); + EndpointBuilder endpointBuilder = EndpointBuilderFactory.create(ROOT_MAPPING, endpoint, ENTITIES_DATA, PACKAGE); + + assertTrue(endpointBuilder instanceof PutEndpointBuilder, "Expected PutEndpointBuilder"); + } + + @Test + void givenDeleteEndpoint_whenAskForBuilder_thenCorrectBuilderIsReturned() { + Endpoint endpoint = EndpointObjectMother.standardDelete("deleteEndpoint", ENTITY_NAME); + EndpointBuilder endpointBuilder = EndpointBuilderFactory.create(ROOT_MAPPING, endpoint, ENTITIES_DATA, PACKAGE); + + assertTrue(endpointBuilder instanceof DeleteEndpointBuilder, "Expected DeleteEndpointBuilder"); + } + + @Test + void givenNonStandardEndpoint_whenAskForBuilder_thenCorrectBuilderIsReturned() { + Endpoint endpoint = EndpointObjectMother.customEndpoint(); + EndpointBuilder endpointBuilder = EndpointBuilderFactory.create(ROOT_MAPPING, endpoint, ENTITIES_DATA, PACKAGE); + + assertTrue(endpointBuilder instanceof CustomEndpointBuilder, "Expected CustomEndpointBuilder"); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java new file mode 100644 index 0000000..e2f5b93 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java @@ -0,0 +1,93 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.config.controller.EndpointBaseResponseObjectMother; +import net.cloudappi.apigen.generatorcore.config.controller.EndpointRequestObjectMother; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import net.cloudappi.apigen.generatorcore.config.parameter.ParameterObjectMother; + +import java.util.ArrayList; +import java.util.List; + +public class EndpointObjectMother { + + private EndpointObjectMother() { + // Intentional blank + } + + public static Endpoint standardPost(String endpointName, String entityName) { + Endpoint endpoint = createEndpoint(endpointName, Endpoint.Method.POST, null); + endpoint.setRequest(EndpointRequestObjectMother.requestWithoutAttributes(entityName)); + endpoint.setResponse(EndpointBaseResponseObjectMother.simpleResponseWithoutAttributes(entityName)); + endpoint.setRelatedEntity(entityName); + return endpoint; + } + + public static Endpoint standardSearch(String endpointName, String entityName) { + List parameters = ParameterObjectMother.createGetAllStandardParameters(); + Endpoint endpoint = createEndpoint(endpointName, Endpoint.Method.POST, "/search", parameters); + endpoint.setResponse(EndpointBaseResponseObjectMother.listResponseWithoutAttributesAndCollectionNameEqualsEntityName(entityName)); + endpoint.setRelatedEntity(entityName); + return endpoint; + } + + public static Endpoint standardGetAll(String endpointName, String entityName) { + List parameters = ParameterObjectMother.createGetAllStandardParameters(); + Endpoint endpoint = createEndpoint(endpointName, Endpoint.Method.GET, null, parameters); + endpoint.setResponse(EndpointBaseResponseObjectMother.listResponseWithoutAttributesAndCollectionNameEqualsEntityName(entityName)); + endpoint.setRelatedEntity(entityName); + return endpoint; + } + + public static Endpoint standardGetById(String endpointName, String entityName) { + List parameters = ParameterObjectMother.createGetByIdStandardParameters(); + Parameter parameter = ParameterObjectMother.getBasicPathParameter("id", "integer"); + parameters.add(0, parameter); + Endpoint endpoint = createEndpoint(endpointName, Endpoint.Method.GET, "/{id}", parameters); + endpoint.setResponse(EndpointBaseResponseObjectMother.simpleResponseWithoutAttributes(entityName)); + endpoint.setRelatedEntity(entityName); + return endpoint; + } + + public static Endpoint standardPut(String endpointName, String entityName) { + List parameters = new ArrayList<>(); + Parameter parameter = ParameterObjectMother.getBasicPathParameter("id", "integer"); + parameters.add(parameter); + Endpoint endpoint = createEndpoint(endpointName, Endpoint.Method.PUT, "/{id}", parameters); + endpoint.setRequest(EndpointRequestObjectMother.requestWithoutAttributes(entityName)); + endpoint.setResponse(EndpointBaseResponseObjectMother.simpleResponseWithoutAttributes(entityName)); + endpoint.setRelatedEntity(entityName); + ; + return endpoint; + } + + public static Endpoint standardDelete(String endpointName, String entityName) { + List parameters = new ArrayList<>(); + Parameter parameter = ParameterObjectMother.getBasicPathParameter("id", "integer"); + parameters.add(parameter); + Endpoint endpoint = createEndpoint(endpointName, Endpoint.Method.DELETE, "/{id}", parameters); + endpoint.setResponse(EndpointBaseResponseObjectMother.simpleResponseWithoutAttributes(entityName)); + endpoint.setRelatedEntity(entityName); + return endpoint; + } + + public static Endpoint customEndpoint() { + Endpoint endpoint = createEndpoint("custom", Endpoint.Method.POST, "endpoint"); + endpoint.setRequest(EndpointRequestObjectMother.customRequestWithoutAttributes()); + endpoint.setResponse(EndpointBaseResponseObjectMother.customResponseWithoutAttributes(201)); + return endpoint; + } + + private static Endpoint createEndpoint(String name, Endpoint.Method operation, String mapping) { + return createEndpoint(name, operation, mapping, new ArrayList<>()); + } + + private static Endpoint createEndpoint(String name, Endpoint.Method operation, String mapping, List parameters) { + Endpoint endpoint = new Endpoint(); + endpoint.setMethod(operation); + endpoint.setMapping(mapping); + endpoint.setName(name); + endpoint.setParameters(parameters); + return endpoint; + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java new file mode 100644 index 0000000..e77e384 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java @@ -0,0 +1,115 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class GetAllEndpointBuilderTests { + + static TypeSpec typeSpec; + + @BeforeAll + static void init() { + Endpoint endPoint = EndpointObjectMother.standardGetAll("getAll", "EntityName"); + GetAllEndpointBuilder builderEndpoint = new GetAllEndpointBuilder(new Mapping("/entities"), endPoint, "the.base.package"); + TypeSpec.Builder builder = TypeSpec.classBuilder("GetAllEndpoint"); + builderEndpoint.apply(builder); + typeSpec = builder.build(); + } + + @Test + void givenGetAllEndpointBuilder_whenBuild_thenNameCorrect() { + assertEquals("GetAllEndpoint", typeSpec.name); + } + + @Test + void givenGetAllEndpointBuilder_whenBuild_thenNoHaveModifier() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenGetAllEndpointBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenGetAllEndpointBuilder_whenBuild_thenNoHaveAnnotation() { + assertEquals(0, typeSpec.annotations.size()); + } + + @Test + void givenGetAllEndpointBuilder_whenBuild_thenNoHaveFieldsSpecs() { + assertEquals(0, typeSpec.fieldSpecs.size()); + } + + @Test + void givenGetAllEndpointBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("java.lang.Object", typeSpec.superclass.toString()); + } + + @Test + void givenGetAllEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertFalse(methodSpec.isConstructor()); + assertEquals("getAll", methodSpec.name); + + assertEquals(2, methodSpec.annotations.size()); + AnnotationSpec annotationSpec = methodSpec.annotations.get(0); + assertEquals("@org.springframework.web.bind.annotation.GetMapping", annotationSpec.toString()); + annotationSpec = methodSpec.annotations.get(1); + assertEquals("@org.springframework.web.bind.annotation.ResponseStatus(code = org.springframework.http.HttpStatus.PARTIAL_CONTENT)", annotationSpec.toString()); + + assertEquals(1, methodSpec.modifiers.size()); + assertEquals("[public]", methodSpec.modifiers.toString()); + + assertEquals(7, methodSpec.parameters.size()); + ParameterSpec parameterSpec = methodSpec.parameters.get(0); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$init\", required = true, defaultValue = \"0\")]", parameterSpec.annotations.toString()); + assertEquals("java.lang.Integer", parameterSpec.type.toString()); + assertEquals("init", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(1); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$limit\", required = true, defaultValue = \"25\")]", parameterSpec.annotations.toString()); + assertEquals("java.lang.Integer", parameterSpec.type.toString()); + assertEquals("limit", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(2); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$total\", required = true, defaultValue = \"false\")]", parameterSpec.annotations.toString()); + assertEquals("java.lang.Boolean", parameterSpec.type.toString()); + assertEquals("total", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(3); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$select\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("select", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(4); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$exclude\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("exclude", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(5); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$expand\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("expand", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(6); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$orderby\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("orderby", parameterSpec.name); + + assertEquals("namingTranslator.translate(select, exclude, expand, orderby, the.base.package.entityname.web.EntityNameOutResource.class);\n" + + "net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult searchResult = service.search(select, exclude, expand, null, orderby, init, limit, total);\n" + + "java.util.List result = mapper.toResource(searchResult.getSearchResult());\n" + + "return new the.base.package.entityname.web.EntityNameListResponse(result).withMetadataPagination(init, limit, searchResult.getTotal());\n", methodSpec.code.toString()); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java new file mode 100644 index 0000000..a7d414a --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java @@ -0,0 +1,104 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class GetByIdEndpointBuilderTests { + + private static TypeSpec typeSpec; + + @BeforeAll + static void init() { + Endpoint endPoint = EndpointObjectMother.standardGetById("getById", "EntityName"); + GetByIdEndpointBuilder builderEndpoint = new GetByIdEndpointBuilder(new Mapping("/entities"), endPoint, null, "the.base.package"); + TypeSpec.Builder builder = TypeSpec.classBuilder("GetByIdEndpoint"); + builderEndpoint.apply(builder); + typeSpec = builder.build(); + } + + @Test + void givenGetByIdEndpointBuilder_whenBuild_thenNameCorrect() { + assertEquals("GetByIdEndpoint", typeSpec.name); + } + + @Test + void givenGetByIdEndpointBuilder_whenBuild_thenModifierIsPublic() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenGetByIdEndpointBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenGetByIdEndpointBuilder_whenBuild_thenNoHaveAnnotation() { + assertEquals(0, typeSpec.annotations.size()); + } + + @Test + void givenGetByIdEndpointBuilder_whenBuild_thenNoHaveModifier() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenGetByIdEndpointBuilder_whenBuild_thenNoHaveFieldsSpecs() { + assertEquals(0, typeSpec.fieldSpecs.size()); + } + + @Test + void givenGetByIdEndpointBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("java.lang.Object", typeSpec.superclass.toString()); + } + + @Test + void givenGetByIdEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertFalse(methodSpec.isConstructor()); + assertEquals("getById", methodSpec.name); + + assertEquals(2, methodSpec.annotations.size()); + AnnotationSpec annotationSpec = methodSpec.annotations.get(0); + assertEquals("@org.springframework.web.bind.annotation.GetMapping(\"/{id}\")", annotationSpec.toString()); + annotationSpec = methodSpec.annotations.get(1); + assertEquals("@org.springframework.web.bind.annotation.ResponseStatus(code = org.springframework.http.HttpStatus.OK)", annotationSpec.toString()); + + assertEquals(1, methodSpec.modifiers.size()); + assertEquals("[public]", methodSpec.modifiers.toString()); + + assertEquals(4, methodSpec.parameters.size()); + ParameterSpec parameterSpec = methodSpec.parameters.get(0); + assertEquals("[@org.springframework.web.bind.annotation.PathVariable(\"id\")]", parameterSpec.annotations.toString()); + assertEquals("java.lang.Long", parameterSpec.type.toString()); + assertEquals("id", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(1); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$select\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("select", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(2); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$exclude\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("exclude", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(3); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$expand\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("expand", parameterSpec.name); + + assertEquals("namingTranslator.translate(select, exclude, expand, the.base.package.entityname.web.EntityNameOutResource.class);\n" + + "the.base.package.entityname.EntityName searchResult = service.search(id, select, exclude, expand);\n" + + "the.base.package.entityname.web.EntityNameOutResource result = mapper.toResource(searchResult);\n" + + "return new the.base.package.entityname.web.EntityNameResponse(result);\n", methodSpec.code.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java new file mode 100644 index 0000000..0aad6c6 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java @@ -0,0 +1,82 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class PostEndpointBuilderTests { + + static TypeSpec typeSpec; + + @BeforeAll + static void init() { + Endpoint endPoint = EndpointObjectMother.standardPost("post", "EntityName"); + PostEndpointBuilder builderEndpoint = new PostEndpointBuilder(new Mapping("/entities"), endPoint, "the.base.package"); + TypeSpec.Builder builder = TypeSpec.classBuilder("PostEndpoint"); + builderEndpoint.apply(builder); + typeSpec = builder.build(); + } + + @Test + void givenPostEndpointBuilder_whenBuild_thenNameCorrect() { + assertEquals("PostEndpoint", typeSpec.name); + } + + @Test + void givenPostEndpointBuilder_whenBuild_thenNoHaveModifier() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenPostEndpointBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenPostEndpointBuilder_whenBuild_thenNoHaveAnnotation() { + assertEquals(0, typeSpec.annotations.size()); + } + + @Test + void givenPostEndpointBuilder_whenBuild_thenNoHaveFieldsSpecs() { + assertEquals(0, typeSpec.fieldSpecs.size()); + } + + @Test + void givenPostEndpointBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("java.lang.Object", typeSpec.superclass.toString()); + } + + @Test + void givenPostEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertFalse(methodSpec.isConstructor()); + assertEquals("post", methodSpec.name); + assertEquals(2, methodSpec.annotations.size()); + + AnnotationSpec annotationSpec = methodSpec.annotations.get(0); + assertEquals("@org.springframework.web.bind.annotation.PostMapping", annotationSpec.toString()); + annotationSpec = methodSpec.annotations.get(1); + assertEquals("@org.springframework.web.bind.annotation.ResponseStatus(code = org.springframework.http.HttpStatus.CREATED)", annotationSpec.toString()); + + assertEquals(1, methodSpec.modifiers.size()); + assertEquals("[public]", methodSpec.modifiers.toString()); + + assertEquals("[@org.springframework.web.bind.annotation.RequestBody, @javax.validation.Valid]", methodSpec.parameters.get(0).annotations.toString()); + assertEquals("the.base.package.entityname.web.CreateEntityNameResource", methodSpec.parameters.get(0).type.toString()); + assertEquals("body", methodSpec.parameters.get(0).name); + + assertEquals("the.base.package.entityname.EntityName createRequest = mapper.toEntity(body);\n" + + "service.create(createRequest);\n" + + "the.base.package.entityname.EntityName createResult = service.search(createRequest.getId(), null, null, null);\n" + + "the.base.package.entityname.web.EntityNameOutResource result = mapper.toResource(createResult);\n" + + "return new the.base.package.entityname.web.EntityNameResponse(result);\n", methodSpec.code.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java new file mode 100644 index 0000000..40df298 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java @@ -0,0 +1,118 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class PostSearchEndpointBuilderTests { + + static TypeSpec typeSpec; + + @BeforeAll + static void init() { + Endpoint endPoint = EndpointObjectMother.standardSearch("postSearch", "EntityName"); + PostSearchEndpointBuilder builderEndpoint = new PostSearchEndpointBuilder(new Mapping("/entities"), endPoint, "the.base.package"); + TypeSpec.Builder builder = TypeSpec.classBuilder("PostSearch"); + builderEndpoint.apply(builder); + typeSpec = builder.build(); + } + + @Test + void givenPostSearchEndpointBuilder_whenBuild_thenNameCorrect() { + assertEquals("PostSearch", typeSpec.name); + } + + @Test + void givenPostSearchEndpointBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenPostSearchEndpointBuilder_whenBuild_thenNoHaveAnnotation() { + assertEquals(0, typeSpec.annotations.size()); + } + + @Test + void givenPostSearchEndpointBuilder_whenBuild_thenNoHaveModifier() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenPostSearchEndpointBuilder_whenBuild_thenNoHaveFieldsSpecs() { + assertEquals(0, typeSpec.fieldSpecs.size()); + } + + @Test + void givenPostSearchEndpointBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("java.lang.Object", typeSpec.superclass.toString()); + } + + @Test + void givenPostSearchEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertFalse(methodSpec.isConstructor()); + assertEquals("postSearch", methodSpec.name); + assertEquals(2, methodSpec.annotations.size()); + + AnnotationSpec annotationSpec = methodSpec.annotations.get(0); + assertEquals("@org.springframework.web.bind.annotation.PostMapping(\"/search\")", annotationSpec.toString()); + annotationSpec = methodSpec.annotations.get(1); + assertEquals("@org.springframework.web.bind.annotation.ResponseStatus(code = org.springframework.http.HttpStatus.PARTIAL_CONTENT)", annotationSpec.toString()); + assertEquals(1, methodSpec.modifiers.size()); + assertEquals("[public]", methodSpec.modifiers.toString()); + + ParameterSpec parameterSpec = methodSpec.parameters.get(0); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$init\", required = true, defaultValue = \"0\")]", parameterSpec.annotations.toString()); + assertEquals("java.lang.Integer", parameterSpec.type.toString()); + assertEquals("init", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(1); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$limit\", required = true, defaultValue = \"25\")]", parameterSpec.annotations.toString()); + assertEquals("java.lang.Integer", parameterSpec.type.toString()); + assertEquals("limit", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(2); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$total\", required = true, defaultValue = \"false\")]", parameterSpec.annotations.toString()); + assertEquals("java.lang.Boolean", parameterSpec.type.toString()); + assertEquals("total", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(3); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$select\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("select", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(4); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$exclude\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("exclude", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(5); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$expand\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("expand", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(6); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(value = \"$orderby\", required = false)]", parameterSpec.annotations.toString()); + assertEquals("java.util.List", parameterSpec.type.toString()); + assertEquals("orderby", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(7); + assertEquals("[@org.springframework.web.bind.annotation.RequestBody, @javax.validation.Valid]", parameterSpec.annotations.toString()); + assertEquals("net.cloudappi.apigen.archetypecore.core.resource.FilterResource", parameterSpec.type.toString()); + assertEquals("body", parameterSpec.name); + + assertEquals("net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter filter = body.getFilter();\n" + + "namingTranslator.translate(select, exclude, expand, filter, orderby, the.base.package.entityname.web.EntityNameOutResource.class);\n" + + "net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult searchResult = service.search(select, exclude, expand, filter, orderby, init, limit, total);\n" + + "java.util.List result = mapper.toResource(searchResult.getSearchResult());\n" + + "return new the.base.package.entityname.web.EntityNameListResponse(result).withMetadataPagination(init, limit, searchResult.getTotal());\n", methodSpec.code.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java new file mode 100644 index 0000000..5864752 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java @@ -0,0 +1,99 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class PutEndpointBuilderTests { + + static TypeSpec typeSpec; + + @BeforeAll + static void init() { + Endpoint endPoint = EndpointObjectMother.standardPut("put", "EntityName"); + PutEndpointBuilder builderEndpoint = new PutEndpointBuilder(new Mapping("/entities"), endPoint, null, "the.base.package"); + TypeSpec.Builder builder = TypeSpec.classBuilder("PutEndpoint"); + builderEndpoint.apply(builder); + typeSpec = builder.build(); + } + + @Test + void givenPutEndpointBuilder_whenBuild_thenNameCorrect() { + assertEquals("PutEndpoint", typeSpec.name); + } + + @Test + void givenPutEndpointBuilder_whenBuild_thenModifierIsPublic() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenPutEndpointBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenPutEndpointBuilder_whenBuild_thenNoHaveAnnotation() { + assertEquals(0, typeSpec.annotations.size()); + } + + @Test + void givenPutEndpointBuilder_whenBuild_thenNoHaveModifier() { + assertEquals(0, typeSpec.modifiers.size()); + } + + @Test + void givenPutEndpointBuilder_whenBuild_thenNoHaveFieldsSpecs() { + assertEquals(0, typeSpec.fieldSpecs.size()); + } + + @Test + void givenPutEndpointBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("java.lang.Object", typeSpec.superclass.toString()); + } + + @Test + void givenPutEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertFalse(methodSpec.isConstructor()); + assertEquals("put", methodSpec.name); + + assertEquals(2, methodSpec.annotations.size()); + AnnotationSpec annotationSpec = methodSpec.annotations.get(0); + assertEquals("@org.springframework.web.bind.annotation.PutMapping(\"/{id}\")", annotationSpec.toString()); + annotationSpec = methodSpec.annotations.get(1); + assertEquals("@org.springframework.web.bind.annotation.ResponseStatus(code = org.springframework.http.HttpStatus.OK)", annotationSpec.toString()); + assertEquals(1, methodSpec.modifiers.size()); + assertEquals("[public]", methodSpec.modifiers.toString()); + + assertEquals(3, methodSpec.parameters.size()); + ParameterSpec parameterSpec = methodSpec.parameters.get(0); + assertEquals("@org.springframework.web.bind.annotation.PathVariable(\"id\") java.lang.Long id", parameterSpec.toString()); + assertEquals("java.lang.Long", parameterSpec.type.toString()); + assertEquals("id", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(1); + assertEquals("[@org.springframework.web.bind.annotation.RequestBody, @javax.validation.Valid]", parameterSpec.annotations.toString()); + assertEquals("the.base.package.entityname.web.UpdateEntityNameByIdResource", parameterSpec.type.toString()); + assertEquals("body", parameterSpec.name); + + parameterSpec = methodSpec.parameters.get(2); + assertEquals("[@org.springframework.web.bind.annotation.RequestAttribute]", parameterSpec.annotations.toString()); + assertEquals("java.util.Set", parameterSpec.type.toString()); + assertEquals("updatedFields", parameterSpec.name); + + assertEquals("the.base.package.entityname.EntityName updateRequest = mapper.toEntity(body);\n" + + "service.update(id, updateRequest, updatedFields);\n" + + "the.base.package.entityname.EntityName createResult = service.search(id, null, null, null);\n" + + "the.base.package.entityname.web.EntityNameOutResource result = mapper.toResource(createResult);\n" + + "return new the.base.package.entityname.web.EntityNameResponse(result);\n", methodSpec.code.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java new file mode 100644 index 0000000..58f2ef1 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java @@ -0,0 +1,145 @@ +package net.cloudappi.apigen.generatorcore.generator.web.controller.parameters; + +import com.squareup.javapoet.ParameterSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import net.cloudappi.apigen.generatorcore.config.parameter.ParameterObjectMother; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ParameterBuilderTest { + + @Test + void generatePathParameterTest() { + Parameter parameter = ParameterObjectMother.getBasicPathParameter(); + ParameterBuilder parameterBuilder = new PathParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("basicPathParameter", generatedParameter.name); + assertEquals("java.lang.String", generatedParameter.type.toString()); + assertEquals("[@org.springframework.web.bind.annotation.PathVariable(\"basicPathParameter\")]", generatedParameter.annotations.toString()); + } + + @Test + void generateQueryParameterTest() { + Parameter parameter = ParameterObjectMother.getBasicQueryParameter(); + ParameterBuilder parameterBuilder = new QueryParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("basicQueryParameter", generatedParameter.name); + assertEquals("java.lang.Long", generatedParameter.type.toString()); + assertEquals("[@org.springframework.web.bind.annotation.RequestParam(\"basicQueryParameter\")]", generatedParameter.annotations.toString()); + } + + @Test + void generateCompleteIntegerParameterTest() { + Parameter parameter = ParameterObjectMother.getIntegerParameter(); + ParameterBuilder parameterBuilder = new QueryParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("integerParameter", generatedParameter.name); + assertEquals("java.lang.Integer", generatedParameter.type.toString()); + assertEquals("[" + + "@org.springframework.web.bind.annotation.RequestParam(value = \"integerParameter\", required = true, defaultValue = \"5\"), " + + "@javax.validation.constraints.Min(5), " + + "@javax.validation.constraints.Max(10)" + + "]", generatedParameter.annotations.toString()); + } + + @Test + void generateCompleteLongParameterTest() { + Parameter parameter = ParameterObjectMother.getLongParameter(); + ParameterBuilder parameterBuilder = new QueryParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("longParameter", generatedParameter.name); + assertEquals("java.lang.Long", generatedParameter.type.toString()); + assertEquals("[" + + "@org.springframework.web.bind.annotation.RequestParam(value = \"longParameter\", required = true, defaultValue = \"5\"), " + + "@javax.validation.constraints.Min(5), " + + "@javax.validation.constraints.Max(10)" + + "]", generatedParameter.annotations.toString()); + } + + @Test + void generateCompleteDoubleParameterTest() { + Parameter parameter = ParameterObjectMother.getDoubleParameter(); + ParameterBuilder parameterBuilder = new QueryParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("doubleParameter", generatedParameter.name); + assertEquals("java.lang.Double", generatedParameter.type.toString()); + assertEquals("[" + + "@org.springframework.web.bind.annotation.RequestParam(value = \"doubleParameter\", required = true, defaultValue = \"5.0\"), " + + "@javax.validation.constraints.DecimalMin(value = \"5\", inclusive = false), " + + "@javax.validation.constraints.DecimalMax(value = \"10\", inclusive = false)" + + "]", generatedParameter.annotations.toString()); + + } + + @Test + void generateCompleteFloatParameterTest() { + Parameter parameter = ParameterObjectMother.getFloatParameter(); + ParameterBuilder parameterBuilder = new QueryParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("floatParameter", generatedParameter.name); + assertEquals("java.lang.Float", generatedParameter.type.toString()); + assertEquals("[" + + "@org.springframework.web.bind.annotation.RequestParam(value = \"floatParameter\", required = true, defaultValue = \"5.0\"), " + + "@javax.validation.constraints.DecimalMin(value = \"5\", inclusive = false), " + + "@javax.validation.constraints.DecimalMax(value = \"10\", inclusive = false)" + + "]", generatedParameter.annotations.toString()); + } + + @Test + void generateCompleteStringParameterTest() { + Parameter parameter = ParameterObjectMother.getStringParameter(); + ParameterBuilder parameterBuilder = new QueryParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("stringParameter", generatedParameter.name); + assertEquals("java.lang.String", generatedParameter.type.toString()); + assertEquals("[" + + "@org.springframework.web.bind.annotation.RequestParam(value = \"stringParameter\", required = true, defaultValue = \"test\"), " + + "@javax.validation.constraints.Size(min = 5, max = 10), " + + "@javax.validation.constraints.Pattern(regexp = \"\\\\d{9}\")" + + "]", generatedParameter.annotations.toString()); + } + + @Test + void generateCompleteArrayParameterTest() { + Parameter parameter = ParameterObjectMother.getArrayParameter(); + ParameterBuilder parameterBuilder = new QueryParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("arrayParameter", generatedParameter.name); + assertEquals("java.util.List", generatedParameter.type.toString()); + assertEquals("[" + + "@org.springframework.web.bind.annotation.RequestParam(value = \"arrayParameter\", required = true, defaultValue = \"primero,segundo,tercero,cuarto,quinto\"), " + + "@javax.validation.constraints.Size(min = 5, max = 10)" + + "]", generatedParameter.annotations.toString()); + } + + @Test + void generateCompleteBooleanParameterTest() { + Parameter parameter = ParameterObjectMother.getBooleanParameter(); + ParameterBuilder parameterBuilder = new QueryParameterBuilder(parameter, parameter.getName()); + + ParameterSpec generatedParameter = parameterBuilder.build(); + + assertEquals("booleanParameter", generatedParameter.name); + assertEquals("java.lang.Boolean", generatedParameter.type.toString()); + assertEquals("[" + + "@org.springframework.web.bind.annotation.RequestParam(value = \"booleanParameter\", required = true, defaultValue = \"true\")" + + "]", generatedParameter.annotations.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java new file mode 100644 index 0000000..44e1f2c --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java @@ -0,0 +1,36 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource; + +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.ControllerObjectMother; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ResourceGeneratorTest { + + @Test + void givenValidController_whenGenerateResource_thenResourceFileIsGenerated(@TempDir Path filesRootPath) throws IOException { + List controllerList = Arrays.asList(ControllerObjectMother.createControllerWithStandardEndpoints("EntityName", "/mapping")); + + ResourcesGenerator resourcesGenerator = new ResourcesGenerator(controllerList, "the.base.package"); + resourcesGenerator.generate(filesRootPath); + + File projectFolder = filesRootPath.toFile(); + + Path entityPath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "entityname", "web", "EntityNameOutResource.java" + ); + assertTrue(Files.exists(entityPath), "EntityNameOutResource.java not generated"); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java new file mode 100644 index 0000000..da6e865 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java @@ -0,0 +1,157 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.input; + +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Attribute; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.AttributeObjectMother; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AllInputResourceBuilderTests { + + private static String basePackage = "the.base.pkge"; + + private static TypeSpec entityTypeSpec; + private static TypeSpec entityNestedTypeSpec; + private static TypeSpec resourceTypeSpec; + + + @BeforeAll + static void init() { + initEntityTypeSpec(); + initEntityNestedIdTypeSpec(); + initResourceTypeSpec(); + } + + private static void initEntityTypeSpec() { + Attribute attribute = AttributeObjectMother.createSimpleAttribute("string", "json", "java"); + + Endpoint endpoint = EndpointObjectMother.customEndpoint(); + endpoint.setRelatedEntity("Entity"); + endpoint.getRequest().setAttributes(Collections.singletonList(attribute)); + + AllInputResourceBuilder builder = new AllInputResourceBuilder(new Mapping("/entity"), endpoint, basePackage); + entityTypeSpec = builder.build(); + } + + private static void initEntityNestedIdTypeSpec() { + Attribute attribute = AttributeObjectMother.createSimpleAttribute("integer", "other", "other.id"); + attribute.setRelatedEntity("Other"); + + Endpoint endpoint = EndpointObjectMother.customEndpoint(); + endpoint.setRelatedEntity("Entity"); + endpoint.getRequest().setAttributes(Collections.singletonList(attribute)); + + AllInputResourceBuilder builder = new AllInputResourceBuilder(new Mapping("/entity"), endpoint, basePackage); + entityNestedTypeSpec = builder.build(); + } + + private static void initResourceTypeSpec() { + Attribute nested = AttributeObjectMother.createSimpleAttribute("string", "nested"); + Attribute attribute = AttributeObjectMother.createSimpleAttribute("object", "parent"); + attribute.getAttributes().add(nested); + + Endpoint endpoint = EndpointObjectMother.customEndpoint(); + endpoint.getRequest().setAttributes(Collections.singletonList(attribute)); + + AllInputResourceBuilder builder = new AllInputResourceBuilder(new Mapping("/resource"), endpoint, basePackage); + resourceTypeSpec = builder.build(); + } + + @Test + void givenEntityEndpoint_whenGenerated_thenAnnotationsAreCorrect() { + assertEquals("[@lombok.Data]", entityTypeSpec.annotations.toString()); + } + + @Test + void givenEntityEndpoint_whenGenerated_thenNameIsCorrect() { + assertEquals("CreateEntityEndpointResource", entityTypeSpec.name); + } + + @Test + void givenEntityEndpoint_whenGenerated_thenFieldsAreCorrect() { + assertEquals(1, entityTypeSpec.fieldSpecs.size()); + assertEquals("" + + "@com.fasterxml.jackson.annotation.JsonProperty(\"json\")\n" + + "private java.lang.String java;\n", + entityTypeSpec.fieldSpecs.get(0).toString()); + } + + @Test + void givenEntityNestedIdEndpoint_whenGenerated_thenAnnotationsAreCorrect() { + assertEquals("[@lombok.Data]", entityNestedTypeSpec.annotations.toString()); + } + + @Test + void givenEntityNestedIdEndpoint_whenGenerated_thenNameIsCorrect() { + assertEquals("CreateEntityEndpointResource", entityNestedTypeSpec.name); + } + + @Test + void givenEntityNestedIdEndpoint_whenGenerated_thenFieldsAreCorrect() { + assertEquals(1, entityNestedTypeSpec.fieldSpecs.size()); + assertEquals("" + + "@com.fasterxml.jackson.annotation.JsonProperty(\"other\")\n" + + "@javax.validation.Valid\n" + + "private the.base.pkge.entity.web.CreateEntityEndpointResource.Other other;\n", + entityNestedTypeSpec.fieldSpecs.get(0).toString()); + } + + @Test + void givenEntityNestedIdEndpoint_whenGenerated_thenNestedClassIsCorrect() { + assertEquals(1, entityNestedTypeSpec.typeSpecs.size()); + assertEquals("" + + "@lombok.Data\n" + + "@lombok.NoArgsConstructor\n" + + "public static class Other {\n" + + " @com.fasterxml.jackson.annotation.JsonProperty(\"id\")\n" + + " private java.lang.Long id;\n" + + "\n" + + " @com.fasterxml.jackson.annotation.JsonCreator(\n" + + " mode = JsonCreator.Mode.DELEGATING\n" + + " )\n" + + " Other(java.lang.Long id) {\n" + + " this.id = id;\n" + + " }\n" + + "}\n", + entityNestedTypeSpec.typeSpecs.get(0).toString()); + } + + @Test + void givenResourceEndpoint_whenGenerated_thenAnnotationsAreCorrect() { + assertEquals("[@lombok.Data]", resourceTypeSpec.annotations.toString()); + } + + @Test + void givenResourceEndpoint_whenGenerated_thenNameIsCorrect() { + assertEquals("CreateResourceEndpointResource", resourceTypeSpec.name); + } + + @Test + void givenResourceEndpoint_whenGenerated_thenFieldsAreCorrect() { + assertEquals(1, resourceTypeSpec.fieldSpecs.size()); + assertEquals("" + + "@com.fasterxml.jackson.annotation.JsonProperty(\"parent\")\n" + + "@javax.validation.Valid\n" + + "private the.base.pkge.resource.web.CreateResourceEndpointResource.Parent parent;\n", + resourceTypeSpec.fieldSpecs.get(0).toString()); + } + + @Test + void givenResourceEndpoint_whenGenerated_thenNestedClassIsCorrect() { + assertEquals(1, resourceTypeSpec.typeSpecs.size()); + assertEquals("" + + "@lombok.Data\n" + + "public static class Parent {\n" + + " @com.fasterxml.jackson.annotation.JsonProperty(\"nested\")\n" + + " private java.lang.String nested;\n" + + "}\n", + resourceTypeSpec.typeSpecs.get(0).toString()); + } +} \ No newline at end of file diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java new file mode 100644 index 0000000..f31f1b0 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java @@ -0,0 +1,79 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.output; + +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.EndpointBaseResponseObjectMother; +import net.cloudappi.apigen.generatorcore.config.controller.Response; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EntityOutputResourceBuilderTest { + + @Test + void givenAResponseWithAttribute_whenGenerateResource_thenFileStructureIsCorrect() { + Response response = EndpointBaseResponseObjectMother.simpleResponseWithSimpleAttribute("EntityName"); + EntityOutputResourceBuilder builder = new EntityOutputResourceBuilder(response, "the.base.package"); + TypeSpec spec = builder.build(); + assertEquals("EntityNameOutResource", spec.name, "The name is wrong"); + assertEquals("CLASS", spec.kind.name(), "The file is not a class"); + assertFalse(spec.annotations.isEmpty(), "The annotations are wrong"); + assertEquals("@lombok.Data", spec.annotations.get(0).toString(), "The @Data annotation is wrong"); + assertEquals("@net.cloudappi.apigen.archetypecore.core.resource.ApigenEntityOutResource", spec.annotations.get(1).toString(), "The @ApigenEntityOutResource annotation is wrong"); + assertFalse(spec.fieldSpecs.isEmpty(), "The fields are wrong"); + } + + @Test + void givenAResponseWithoutAttributes_whenGenerateResource_thenClassHasNoFields() { + Response response = EndpointBaseResponseObjectMother.simpleResponseWithoutAttributes("EntityName"); + EntityOutputResourceBuilder builder = new EntityOutputResourceBuilder(response, "the.base.package"); + TypeSpec spec = builder.build(); + assertTrue(spec.fieldSpecs.isEmpty(), "There should not be any attribute"); + } + + @Test + void givenAResponseWithSimpleAttribute_whenGenerateResource_thenFieldIsCorrect() { + Response response = EndpointBaseResponseObjectMother.simpleResponseWithSimpleAttribute("EntityName"); + EntityOutputResourceBuilder builder = new EntityOutputResourceBuilder(response, "the.base.package"); + TypeSpec spec = builder.build(); + assertFalse(spec.fieldSpecs.isEmpty(), "The fields are wrong"); + assertEquals("entityField", spec.fieldSpecs.get(0).name, "The field name is wrong"); + assertEquals("java.lang.String", spec.fieldSpecs.get(0).type.toString(), "The field type is wrong"); + assertEquals("@com.fasterxml.jackson.annotation.JsonProperty(\"jsonField\")", spec.fieldSpecs.get(0).annotations.get(0).toString(), "The field annotation is wrong"); + } + + @Test + void givenAResponseWithRelatedAttribute_whenGenerateResource_thenFieldIsCorrect() { + Response response = EndpointBaseResponseObjectMother.simpleResponseWithRelatedAttribute("EntityName", "OtherEntity"); + EntityOutputResourceBuilder builder = new EntityOutputResourceBuilder(response, "the.base.package"); + TypeSpec spec = builder.build(); + assertFalse(spec.fieldSpecs.isEmpty(), "The fields are wrong"); + assertEquals("relatedEntityField", spec.fieldSpecs.get(0).name, "The field name is wrong"); + assertEquals("the.base.package.otherentity.web.OtherEntityOutResource", spec.fieldSpecs.get(0).type.toString(), "The field type is wrong"); + assertEquals("@com.fasterxml.jackson.annotation.JsonProperty(\"relatedJsonField\")", spec.fieldSpecs.get(0).annotations.get(0).toString(), "The field annotation is wrong"); + } + + @Test + void givenAResponseWithRelatedListAttribute_whenGenerateResource_thenFieldIsCorrect() { + Response response = EndpointBaseResponseObjectMother.simpleResponseWithRelatedListAttribute("EntityName", "OtherEntity"); + EntityOutputResourceBuilder builder = new EntityOutputResourceBuilder(response, "the.base.package"); + TypeSpec spec = builder.build(); + assertFalse(spec.fieldSpecs.isEmpty(), "The fields are wrong"); + assertEquals("relatedEntityListField", spec.fieldSpecs.get(0).name, "The field name is wrong"); + assertEquals("java.util.Set", spec.fieldSpecs.get(0).type.toString(), "The field type is wrong"); + assertEquals("@com.fasterxml.jackson.annotation.JsonProperty(\"relatedArrayJsonField\")", spec.fieldSpecs.get(0).annotations.get(0).toString(), "The field annotation is wrong"); + } + + @Test + void givenAValidResponse_whenGenerateResource_thenPackageIsCorrect() { + Response response = EndpointBaseResponseObjectMother.simpleResponseWithoutAttributes("EntityName"); + EntityOutputResourceBuilder builder = new EntityOutputResourceBuilder(response, "the.base.package"); + assertEquals("the.base.package.entityname.web", builder.getPackage(), "The package is wrong"); + } + + @Test + void givenAValidResponse_whenGenerateResource_thenTypeNameIsCorrect() { + TypeName typeName = EntityOutputResourceBuilder.getTypeName("EntityName", "the.base.package"); + assertEquals("the.base.package.entityname.web.EntityNameOutResource", typeName.toString(), "TypeName is wrong"); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java new file mode 100644 index 0000000..e0961c1 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java @@ -0,0 +1,100 @@ +package net.cloudappi.apigen.generatorcore.generator.web.resource.output; + +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.*; +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.AttributeObjectMother; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class ResourceOutputResourceBuilderTest { + + private static String basePackage = "the.base.package"; + private static Mapping rootMapping; + private static Endpoint endpoint; + private static Response response; + + @BeforeEach + void init() { + endpoint = EndpointObjectMother.customEndpoint(); + response = endpoint.getResponse(); + response.setIsCollection(false); + response.setIsStandard(false); + rootMapping = new Mapping("/resource"); + } + + @Test + void givenAResponseWithAttribute_whenGenerateResource_thenFileStructureIsCorrect() { + ResourceOutputResourceBuilder builder = new ResourceOutputResourceBuilder(rootMapping, endpoint, basePackage); + TypeSpec spec = builder.build(); + assertEquals("CreateResourceEndpointOutResource", spec.name); + assertEquals("CLASS", spec.kind.name()); + assertFalse(spec.annotations.isEmpty()); + assertEquals("@lombok.Data", spec.annotations.get(0).toString()); + } + + @Test + void givenAResponseWithoutAttributes_whenGenerateResource_thenClassHasNoFields() { + ResourceOutputResourceBuilder builder = new ResourceOutputResourceBuilder(rootMapping, endpoint, basePackage); + TypeSpec spec = builder.build(); + assertTrue(spec.fieldSpecs.isEmpty()); + } + + @Test + void givenAResponseWithSimpleAttribute_whenGenerateResource_thenFieldIsCorrect() { + response.getAttributes().add(AttributeObjectMother.createSimpleStringAttribute("test")); + ResourceOutputResourceBuilder builder = new ResourceOutputResourceBuilder(rootMapping, endpoint, basePackage); + TypeSpec spec = builder.build(); + assertFalse(spec.fieldSpecs.isEmpty()); + assertEquals("test", spec.fieldSpecs.get(0).name); + assertEquals("java.lang.String", spec.fieldSpecs.get(0).type.toString()); + assertEquals("@com.fasterxml.jackson.annotation.JsonProperty(\"test\")", spec.fieldSpecs.get(0).annotations.get(0).toString()); + } + + @Test + void givenAResponseWithNestedAttribute_whenGenerateResource_thenFieldIsCorrect() { + Attribute nested = AttributeObjectMother.createSimpleStringAttribute("name"); + Attribute object = AttributeObjectMother.createSimpleAttribute("object", "jsonObj", "obj"); + object.getAttributes().add(nested); + response.getAttributes().add(object); + + ResourceOutputResourceBuilder builder = new ResourceOutputResourceBuilder(rootMapping, endpoint, basePackage); + TypeSpec spec = builder.build(); + assertFalse(spec.fieldSpecs.isEmpty()); + assertEquals("obj", spec.fieldSpecs.get(0).name); + assertEquals("the.base.package.resource.web.CreateResourceEndpointOutResource.Obj", spec.fieldSpecs.get(0).type.toString()); + assertEquals("@com.fasterxml.jackson.annotation.JsonProperty(\"jsonObj\")", spec.fieldSpecs.get(0).annotations.get(0).toString()); + } + + @Test + void givenAResponseWithNestedListAttribute_whenGenerateResource_thenFieldIsCorrect() { + Attribute nested = AttributeObjectMother.createSimpleStringAttribute("name"); + Attribute object = AttributeObjectMother.createSimpleAttribute("object", "jsonObj", "obj"); + object.getAttributes().add(nested); + object.setCollection(true); + response.getAttributes().add(object); + + ResourceOutputResourceBuilder builder = new ResourceOutputResourceBuilder(rootMapping, endpoint, basePackage); + TypeSpec spec = builder.build(); + assertFalse(spec.fieldSpecs.isEmpty()); + assertEquals("obj", spec.fieldSpecs.get(0).name); + assertEquals("java.util.Set", spec.fieldSpecs.get(0).type.toString()); + assertEquals("@com.fasterxml.jackson.annotation.JsonProperty(\"jsonObj\")", spec.fieldSpecs.get(0).annotations.get(0).toString()); + } + + @Test + void givenAValidResponse_whenGenerateResource_thenPackageIsCorrect() { + ResourceOutputResourceBuilder builder = new ResourceOutputResourceBuilder(rootMapping, endpoint, basePackage); + assertEquals("the.base.package.resource.web", builder.getPackage()); + } + + @Test + void givenAValidResponse_whenGenerateResource_thenTypeNameIsCorrect() { + TypeName typeName = ResourceOutputResourceBuilder.getTypeName(rootMapping, endpoint, basePackage); + assertEquals("the.base.package.resource.web.CreateResourceEndpointOutResource", typeName.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java new file mode 100644 index 0000000..305fb0c --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java @@ -0,0 +1,83 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.lang.model.element.Modifier; + +import static org.junit.jupiter.api.Assertions.*; + +class EntityListResponseBuilderTests { + + static TypeSpec typeSpec; + + @BeforeAll + static void init() { + EntityListResponseBuilder builder = new EntityListResponseBuilder("EntityName", "listName", "the.base.package"); + typeSpec = builder.build(); + } + + @Test + void givenListResponseBuilder_whenBuild_thenNameCorrect() { + assertEquals("EntityNameListResponse", typeSpec.name); + } + + @Test + void givenListResponseBuilder_whenBuild_thenModifierIsPublic() { + assertTrue(typeSpec.hasModifier(Modifier.PUBLIC)); + } + + @Test + void givenListResponseBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenListResponseBuilder_whenBuild_thenHaveOneAnnotationAndValueIsCorrect() { + assertEquals("[@lombok.Data]", typeSpec.annotations.toString()); + } + + @Test + void givenListResponseBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("net.cloudappi.apigen.archetypecore.core.responses.ApiResponse>", typeSpec.superclass.toString()); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenHaveMethodSpecISConstructorAndIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertTrue(methodSpec.isConstructor()); + assertEquals("[java.util.List listName]", methodSpec.parameters.toString()); + assertEquals("super(new EntityNameListResponseContent(listName));\n", methodSpec.code.toString()); + } + + @Test + void givenListResponseBuilder_whenBuild_thenHaveTypeSpec() { + TypeSpec subTypeSpec = typeSpec.typeSpecs.get(0); + assertEquals("[@lombok.Data]", subTypeSpec.annotations.toString()); + assertEquals(2, subTypeSpec.modifiers.size()); + assertEquals("[private, static]", subTypeSpec.modifiers.toString()); + assertEquals("EntityNameListResponseContent", subTypeSpec.name); + assertEquals("net.cloudappi.apigen.archetypecore.core.responses.content.ApiListResponseContent", subTypeSpec.superclass.toString()); + assertEquals(2, subTypeSpec.methodSpecs.size()); + MethodSpec methodSpec; + methodSpec = subTypeSpec.methodSpecs.get(0); + assertTrue(methodSpec.isConstructor()); + assertEquals("[java.util.List listName]", methodSpec.parameters.toString()); + assertEquals("super(listName);\n", methodSpec.code.toString()); + methodSpec = subTypeSpec.methodSpecs.get(1); + assertFalse(methodSpec.isConstructor()); + assertEquals("getListName", methodSpec.name); + assertEquals("[@com.fasterxml.jackson.annotation.JsonProperty(\"listName\")]", methodSpec.annotations.toString()); + assertEquals("return content;\n", methodSpec.code.toString()); + } + + @Test + public void givenTypeNameOfSimpleResponseBuilder_whenGetBasePackageAndEntityName_thenClassTypeName() { + TypeName typeName = EntityListResponseBuilder.getTypeName("EntityName", "the.base.package"); + assertNotNull(typeName); + assertEquals("the.base.package.entityname.web.EntityNameListResponse", typeName.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java new file mode 100644 index 0000000..054f365 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java @@ -0,0 +1,94 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.lang.model.element.Modifier; + +import static org.junit.jupiter.api.Assertions.*; + +class ResourceListResponseBuilderTests { + + private static TypeSpec typeSpec; + private static Mapping rootMapping; + private static Endpoint endpoint; + + + @BeforeAll + static void init() { + endpoint = EndpointObjectMother.customEndpoint(); + endpoint.getResponse().setIsCollection(true); + endpoint.getResponse().setIsStandard(true); + endpoint.getResponse().setCollectionName("collectionName"); + rootMapping = new Mapping("/resource"); + ResourceListResponseBuilder builder = new ResourceListResponseBuilder(rootMapping, endpoint, "the.base.package"); + typeSpec = builder.build(); + } + + @Test + void givenResourceListResponseBuilder_whenBuild_thenNameCorrect() { + assertEquals("CreateResourceEndpointListResponse", typeSpec.name); + } + + @Test + void givenResourceListResponseBuilder_whenBuild_thenModifierIsPublic() { + assertTrue(typeSpec.hasModifier(Modifier.PUBLIC)); + } + + @Test + void givenResourceListResponseBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenResourceListResponseBuilder_whenBuild_thenHaveOneAnnotationAndValueIsCorrect() { + assertEquals("[@lombok.Data]", typeSpec.annotations.toString()); + } + + @Test + void givenResourceListResponseBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("net.cloudappi.apigen.archetypecore.core.responses.ApiResponse>", typeSpec.superclass.toString()); + } + + @Test + void givenResourceListResponseBuilder_whenBuild_thenHaveMethodSpecISConstructorAndIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertTrue(methodSpec.isConstructor()); + assertEquals("[java.util.List collectionName]", methodSpec.parameters.toString()); + assertEquals("super(new ResourceEndpointListResponseContent(collectionName));\n", methodSpec.code.toString()); + } + + @Test + void givenResourceListResponseBuilder_whenBuild_thenHaveTypeSpec() { + TypeSpec subTypeSpec = typeSpec.typeSpecs.get(0); + assertEquals("[@lombok.Data]", subTypeSpec.annotations.toString()); + assertEquals(2, subTypeSpec.modifiers.size()); + assertEquals("[private, static]", subTypeSpec.modifiers.toString()); + assertEquals("ResourceEndpointListResponseContent", subTypeSpec.name); + assertEquals("net.cloudappi.apigen.archetypecore.core.responses.content.ApiListResponseContent", subTypeSpec.superclass.toString()); + assertEquals(2, subTypeSpec.methodSpecs.size()); + MethodSpec methodSpec; + methodSpec = subTypeSpec.methodSpecs.get(0); + assertTrue(methodSpec.isConstructor()); + assertEquals("[java.util.List collectionName]", methodSpec.parameters.toString()); + assertEquals("super(collectionName);\n", methodSpec.code.toString()); + methodSpec = subTypeSpec.methodSpecs.get(1); + assertFalse(methodSpec.isConstructor()); + assertEquals("getCollectionName", methodSpec.name); + assertEquals("[@com.fasterxml.jackson.annotation.JsonProperty(\"collectionName\")]", methodSpec.annotations.toString()); + assertEquals("return content;\n", methodSpec.code.toString()); + } + + @Test + public void givenValidParameters_whenGetTypeName_thenClassTypeName() { + TypeName typeName = ResourceListResponseBuilder.getTypeName(rootMapping, endpoint, "the.base.package"); + assertNotNull(typeName); + assertEquals("the.base.package.resource.web.CreateResourceEndpointListResponse", typeName.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java new file mode 100644 index 0000000..58c43b7 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java @@ -0,0 +1,76 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; +import net.cloudappi.apigen.generatorcore.utils.Mapping; +import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.lang.model.element.Modifier; + +import static org.junit.jupiter.api.Assertions.*; + +class ResourceSimpleResponseBuilderTests { + + private static TypeSpec typeSpec; + private static Mapping rootMapping; + private static Endpoint endpoint; + + @BeforeAll + static void init() { + endpoint = EndpointObjectMother.customEndpoint(); + endpoint.getResponse().setIsCollection(false); + endpoint.getResponse().setIsStandard(true); + rootMapping = new Mapping("/resource"); + ResourceSimpleResponseBuilder builder = new ResourceSimpleResponseBuilder(rootMapping, endpoint, "the.base.package"); + typeSpec = builder.build(); + } + + @Test + void givenResourceSimpleResponseBuilder_whenBuild_thenNameCorrect() { + assertEquals("CreateResourceEndpointResponse", typeSpec.name); + } + + @Test + void givenResourceSimpleResponseBuilder_whenBuild_thenModifierIsPublic() { + assertTrue(typeSpec.hasModifier(Modifier.PUBLIC)); + } + + @Test + void givenResourceSimpleResponseBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenResourceSimpleResponseBuilder_whenBuild_thenHaveOneAnnotationAndValueIsCorrect() { + assertEquals("[@lombok.Data]", typeSpec.annotations.toString()); + } + + @Test + void givenResourceSimpleResponseBuilder_whenBuild_thenNoHaveFieldsSpecAndIsCorrect() { + assertEquals("[]", typeSpec.fieldSpecs.toString()); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("net.cloudappi.apigen.archetypecore.core.responses.ApiResponse", typeSpec.superclass.toString()); + } + + @Test + void givenResourceSimpleResponseBuilder_whenBuild_thenHaveMethodSpecISConstructorAndIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertTrue(methodSpec.isConstructor()); + assertEquals("[the.base.package.resource.web.CreateResourceEndpointOutResource data]", methodSpec.parameters.toString()); + assertEquals("super(data);\n", methodSpec.code.toString()); + } + + @Test + public void givenTypeNameOfResourceSimpleResponseBuilder_whenGetBasePackageAndEntityName_thenClassTypeName() { + TypeName typeName = ResourceSimpleResponseBuilder.getTypeName(rootMapping, endpoint, "the.base.package"); + assertNotNull(typeName); + assertEquals("the.base.package.resource.web.CreateResourceEndpointResponse", typeName.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java new file mode 100644 index 0000000..fdb71e0 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java @@ -0,0 +1,81 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import net.cloudappi.apigen.generatorcore.config.controller.Controller; +import net.cloudappi.apigen.generatorcore.config.controller.ControllerObjectMother; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ResponsesGeneratorTests { + + @Test + void givingResponseGenerator_whenSimpleController_thenCreateSimpleFile(@TempDir Path filesRootPath) throws IOException { + + Controller controllerSimple = ControllerObjectMother.createControllerWithSimpleResponse("EntityName"); + + List listController = new ArrayList<>(); + listController.add(controllerSimple); + + new ResponsesGenerator(listController, "the.base.package").generate(filesRootPath); + + File projectFolder = filesRootPath.toFile(); + + Path entityPath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "entityname", "web", "EntityNameResponse.java" + ); + assertTrue(Files.exists(entityPath), "EntityNameResponse.java not generated"); + } + + @Test + void givingResponseGenerator_whenListController_thenCreateListFile(@TempDir Path filesRootPath) throws IOException { + + Controller controllerList = ControllerObjectMother.createControllerWithListResponse("EntityName"); + + List listController = new ArrayList<>(); + listController.add(controllerList); + + new ResponsesGenerator(listController, "the.base.package").generate(filesRootPath); + + File projectFolder = filesRootPath.toFile(); + + Path entityPath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "entityname", "web", "EntityNameListResponse.java" + ); + assertTrue(Files.exists(entityPath), "EntityNameListResponse.java not generated"); + } + + @Test + void givingResponseGenerator_whenSimpleAndListController_thenCreateSimpleAndListFile(@TempDir Path filesRootPath) throws IOException { + + Controller controllerSimpleAndList = ControllerObjectMother.createControllerWithSimpleAndListResponse("EntityName"); + + List listController = new ArrayList<>(); + listController.add(controllerSimpleAndList); + + new ResponsesGenerator(listController, "the.base.package").generate(filesRootPath); + + File projectFolder = filesRootPath.toFile(); + + Path entitySimplePath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "entityname", "web", "EntityNameResponse.java" + ); + Path entityListPath = Paths.get( + projectFolder.getPath(), + "the", "base", "package", "entityname", "web", "EntityNameListResponse.java" + ); + assertTrue(Files.exists(entitySimplePath), "EntityNameResponse.java not generated"); + assertTrue(Files.exists(entityListPath), "EntityNameListResponse.java not generated"); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java new file mode 100644 index 0000000..c4bcc26 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java @@ -0,0 +1,68 @@ +package net.cloudappi.apigen.generatorcore.generator.web.response; + +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import javax.lang.model.element.Modifier; + +import static org.junit.jupiter.api.Assertions.*; + +class SimpleResponseBuilderTests { + + static TypeSpec typeSpec; + + @BeforeAll + static void init() { + SimpleResponseBuilder builder = new SimpleResponseBuilder("EntityName", "the.base.package"); + typeSpec = builder.build(); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenNameCorrect() { + assertEquals("EntityNameResponse", typeSpec.name); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenModifierIsPublic() { + assertTrue(typeSpec.hasModifier(Modifier.PUBLIC)); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenKindIsClass() { + assertEquals("CLASS", typeSpec.kind.toString()); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenHaveOneAnnotationAndValueIsCorrect() { + assertEquals("[@lombok.Data]", typeSpec.annotations.toString()); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenNoHaveFieldsSpecAndIsCorrect() { + assertEquals("[]", typeSpec.fieldSpecs.toString()); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { + assertEquals("net.cloudappi.apigen.archetypecore.core.responses.ApiResponse", typeSpec.superclass.toString()); + } + + @Test + void givenSimpleResponseBuilder_whenBuild_thenHaveMethodSpecISConstructorAndIsCorrect() { + MethodSpec methodSpec = typeSpec.methodSpecs.get(0); + assertTrue(methodSpec.isConstructor()); + assertEquals("[the.base.package.entityname.web.EntityNameOutResource data]", methodSpec.parameters.toString()); + assertEquals("super(data);\n", methodSpec.code.toString()); + } + + + @Test + public void givenTypeNameOfSimpleResponseBuilder_whenGetBasePackageAndEntityName_thenClassTypeName() { + TypeName typeName = SimpleResponseBuilder.getTypeName("EntityName", "the.base.package"); + assertNotNull(typeName); + assertEquals("the.base.package.entityname.web.EntityNameResponse", typeName.toString()); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtilsTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtilsTest.java new file mode 100644 index 0000000..4d92074 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtilsTest.java @@ -0,0 +1,89 @@ +package net.cloudappi.apigen.generatorcore.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CustomStringUtilsTest { + + @Test + void givenSnakeCase_whenConvertsSnakeCaseToCamelCase_thenSuccess() { + assertEquals("snakeCase", CustomStringUtils.snakeCaseToCamelCase("snake_case")); + } + + @Test + void givenSnakeCaseLowerAndUpper_whenConvertsSnakeCaseToCamelCase_thenSuccess() { + assertEquals("snakeCase", CustomStringUtils.snakeCaseToCamelCase("sNAkE_cAsE")); + } + + @Test + void givenSnakeCaseSimple_whenConvertsSnakeCaseToCamelCase_thenSuccess() { + assertEquals("snake", CustomStringUtils.snakeCaseToCamelCase("snake")); + } + + @Test + void givenEmpty_whenConvertsSnakeCaseToCamelCase_thenSuccess() { + assertEquals("", CustomStringUtils.snakeCaseToCamelCase("")); + } + + @Test + void givenNull_whenConvertsSnakeCaseToCamelCase_thenSuccess() { + assertEquals(null, CustomStringUtils.snakeCaseToCamelCase(null)); + } + + @Test + void givenSnakeCase_whenConvertsKebabCaseToCamelCase_thenSuccess() { + assertEquals("kebabCase", CustomStringUtils.kebabCaseToCamelCase("kebab-case")); + } + + @Test + void givenSnakeCaseLowerAndUpper_whenConvertsKebabCaseToCamelCase_thenSuccess() { + assertEquals("kebabCase", CustomStringUtils.kebabCaseToCamelCase("kEbAb-cAsE")); + } + + @Test + void givenSnakeCaseSimple_whenConvertsKebabCaseToCamelCase_thenSuccess() { + assertEquals("kebab", CustomStringUtils.kebabCaseToCamelCase("kebab")); + } + + @Test + void givenEmpty_whenConvertsKebabCaseToCamelCase_thenSuccess() { + assertEquals("", CustomStringUtils.kebabCaseToCamelCase("")); + } + + @Test + void givenNull_whenConvertsKebabCaseToCamelCase_thenSuccess() { + assertEquals(null, CustomStringUtils.kebabCaseToCamelCase(null)); + } + + @Test + void givenCamelCase_whenConvertsCamelCaseToSnakeCase_thenSuccess() { + assertEquals("camel_case", CustomStringUtils.camelCaseToSnakeCase("camelCase")); + } + + @Test + void givenCamelCaseWithMoreThan1UpperCase_whenConvertsCamelCaseToSnakeCase_thenSuccess() { + assertEquals("ca_mel_ca_se", CustomStringUtils.camelCaseToSnakeCase("caMelCaSe")); + } + + @Test + void givenCamelCaseWithFirstUpperCase_whenConvertsCamelCaseToSnakeCase_thenSuccess() { + assertEquals("camel_case", CustomStringUtils.camelCaseToSnakeCase("CamelCase")); + } + + @Test + void givenCamelCaseSimple_whenConvertsCamelCaseToSnakeCase_thenSuccess() { + assertEquals("camel", CustomStringUtils.camelCaseToSnakeCase("camel")); + } + + @Test + void givenEmpty_whenConvertsCamelCaseToSnakeCase_thenSuccess() { + assertEquals("", CustomStringUtils.camelCaseToSnakeCase("")); + } + + @Test + void givenNull_whenConvertsCamelCaseToSnakeCase_thenSuccess() { + assertEquals(null, CustomStringUtils.camelCaseToSnakeCase(null)); + } + +} \ No newline at end of file diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/ZipUtilsTest.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/ZipUtilsTest.java new file mode 100644 index 0000000..fb5fe27 --- /dev/null +++ b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/ZipUtilsTest.java @@ -0,0 +1,35 @@ +package net.cloudappi.apigen.generatorcore.utils; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ZipUtilsTest { + + @Test + void givenAFolderWithFiles_whenConvertToZip_thenSuccess(@TempDir Path path) throws IOException { + Files.createFile(Paths.get(path.toString(), "file.txt")); + Files.createDirectory(Paths.get(path.toString(), "folder")); + Files.createFile(Paths.get(path.toString(), "folder", "file.txt")); + Path zipPath = ZipUtils.zip(path); + assertTrue(Files.exists(zipPath), "Zip file not generated"); + } + + @Test + void givenAFolderWithFiles_whenConvertToZip_thenSuccess(@TempDir Path pathSource, @TempDir Path pathTarget) throws IOException { + Files.createFile(Paths.get(pathSource.toString(), "file.txt")); + Files.createDirectory(Paths.get(pathSource.toString(), "folder")); + Files.createFile(Paths.get(pathSource.toString(), "folder", "file.txt")); + Path zipPath = ZipUtils.zip(pathSource); + ZipUtils.unzip(Files.readAllBytes(zipPath), pathTarget); + assertTrue(Files.exists(Paths.get(pathTarget.toString(), "file.txt"))); + assertTrue(Files.exists(Paths.get(pathTarget.toString(), "folder"))); + assertTrue(Files.exists(Paths.get(pathTarget.toString(), "folder", "file.txt"))); + } +} \ No newline at end of file diff --git a/generator-core/src/test/resources/api_fragments/common.yaml b/generator-core/src/test/resources/api_fragments/common.yaml new file mode 100644 index 0000000..e69de29 diff --git a/generator-core/src/test/resources/api_fragments/standard_request.yaml b/generator-core/src/test/resources/api_fragments/standard_request.yaml new file mode 100644 index 0000000..9db8492 --- /dev/null +++ b/generator-core/src/test/resources/api_fragments/standard_request.yaml @@ -0,0 +1,92 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: standard_request + +x-apigen-project: + name: test + description: test + version: 1.0.0 + java-properties: + group-id: the.test + artifact-id: test + +paths: + /sample_resource: + x-apigen-binding: + model: ResOne + post: + operationId: createResOne + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/create_res_one" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_res_one" + +components: + schemas: + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + + standard_response_res_one: + x-apigen-mapping: + model: ResOne + type: object + properties: + name: + type: string + + create_res_one: + x-apigen-mapping: + model: ResOne + type: object + properties: + name: + type: string + + x-apigen-models: + ResOne: + relational-persistence: + table: res_one + attributes: + - name: name + type: String + relational-persistence: + column: id + primary-key: true \ No newline at end of file diff --git a/generator-core/src/test/resources/api_fragments/testApi.yaml b/generator-core/src/test/resources/api_fragments/testApi.yaml new file mode 100644 index 0000000..0a85096 --- /dev/null +++ b/generator-core/src/test/resources/api_fragments/testApi.yaml @@ -0,0 +1,267 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT + +paths: + /owners: + x-apigen-binding: + model: Owner + post: + operationId: createOwner + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /owners/{id}: + x-apigen-binding: + model: Owner + get: + parameters: + - $ref: "#/components/parameters/stringIdParam" + - $ref: "#/components/parameters/integer_param" + - $ref: "#/components/parameters/double_param" + - $ref: "#/components/parameters/string_param" + - $ref: "#/components/parameters/list_param" + - $ref: "#/components/parameters/boolean_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + put: + parameters: + - $ref: "#/components/parameters/stringIdParam" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + delete: + parameters: + - $ref: "#/components/parameters/stringIdParam" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + +components: + parameters: + integerIdParam: + name: id + in: path + required: true + schema: + type: integer + format: int64 + stringIdParam: + name: id + in: path + required: true + schema: + type: string + integer_param: + in: query + name: integer + schema: + type: integer + format: int32 + default: 10 + minimum: 10 + maximum: 20 + double_param: + in: query + name: double + schema: + type: number + format: double + default: 10.5 + minimum: 10.2 + maximum: 20.3 + exclusiveMinimum: true + exclusiveMaximum: false + string_param: + in: query + name: string + schema: + type: string + default: test + minLength: 3 + maxLength: 7 + pattern: '\\d{9}' + nullable: true + list_param: + in: query + name: list + schema: + type: array + items: + type: string + minItems: 2 + maxItems: 5 + boolean_param: + in: query + name: boolean + schema: + type: boolean + nullable: true + + schemas: + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + + standard_response_owner: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + medicos: + type: array + items: + $ref: "#/components/schemas/owner" + + owner: + x-apigen-mapping: + model: Owner + type: READ + type: object + properties: + id: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + pet: + x-apigen-mapping: + model: Pet + type: READ + type: object + properties: + owner: + $ref: "#/components/schemas/owner" + tags: + type: array + items: + type: string +# ------------------------------------------------- + x-apigen-models: + Owner: + relational-persistence: + table: owners + attributes: + - name: id + type: String + relational-persistence: + column: id + primary-key: true + validations: + - type: NotNull + - type: Size + min: 1 + max: 2 + - type: Min + value: 1 + - type: Max + value: 2 + - type: Email + - type: NotEmpty + - type: NotBlank + - type: Positive + - type: PositiveOrZero + - type: Negative + - type: NegativeOrZero + - type: Past + - type: PastOrPresent + - type: Future + - type: FutureOrPresent + - type: Pattern + regex: '[^i*&2@]' + - type: Digits + integer: 4 + fraction: 2 + - type: DecimalMin + value: 0.1 + - type: DecimalMax + value: 0.2 + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: owner_id + + Pet: + relational-persistence: + table: pets + attributes: + - name: owner + type: Owner + relational-persistence: + column: owner_id + - name: tags + type: Array + items-type: Tag + relational-persistence: + column: pet_id + foreign-column: tag_id + intermediate-table: pet_tags + owner: true + + +# ------------------------------------------------- + +x-apigen-project: + name: test + description: test + version: 1.0.0 + java-properties: + group-id: the.test + artifact-id: test \ No newline at end of file diff --git a/generator-rest/Dockerfile b/generator-rest/Dockerfile new file mode 100644 index 0000000..97804f3 --- /dev/null +++ b/generator-rest/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:8-jre-alpine +VOLUME /tmp +ARG JAR_FILE +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java","-Xms256m","-Xmx512m","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] \ No newline at end of file diff --git a/generator-rest/documentation.yaml b/generator-rest/documentation.yaml new file mode 100644 index 0000000..2d540bb --- /dev/null +++ b/generator-rest/documentation.yaml @@ -0,0 +1,482 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen API + +paths: + /generator/config: + post: + summary: Create a project from a config file + operationId: generateFromConfig + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/config" + responses: + 201: + description: Created +components: + schemas: + config: + type: object + properties: + name: + type: string + description: The name of the project + example: my-project + description: + type: string + description: The description of the project + example: This is my project + group: + type: string + description: The group for the maven project + example: org.domain.examples + artifact: + type: string + description: The artifact for the maven project + example: myproject + version: + type: string + description: The version for the maven project + example: 1.0.0-SNAPSHOT + partial: + type: boolean + default: false + description: Flag to generate all the components or only the models/controllers related + example: false + entities: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/entity' + description: Array with the schema of the entities + controllers: + type: array + minItems: 1 + nullable: false + items: + $ref: '#/components/schemas/controller' + description: Array with the schema of the controllers + required: + - name + - group + - artifact + - version + - partial + - entities + - controllers + + entity: + type: object + properties: + name: + type: string + description: Name of the entity + example: Pet + table: + type: string + description: Name of the table in the databasem, if it's not defined, then the name of the entity transformed to kebab_case + example: pets + attributes: + type: array + items: + $ref: '#/components/schemas/entity-attribute' + description: Attributes of the entity + required: + - name + - attributes + + entity-attribute: + type: object + properties: + name: + type: string + description: Name of the attribute in Java + example: firstName + type: + type: string + description: Type of the attribute in Java, can be a basic type (String, Boolean, Double, Float, Integer, Long, LocalDate, OffsetDateTime) a ComposedID or an Entity name + example: Pet + columns: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/attribute-column' + description: Data about the columns related with this attribute that are in the entity table, if not provided the name of the column is the name of the property transformed to kebab_case + foreign_columns: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/attribute-column' + description: Data about the columns related with this attribute that are in other entity table + relation: + $ref: '#/components/schemas/attribute-relation' + validations: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/validation' + description: Validations to apply to the attribute + attributes: + type: array + items: + $ref: '#/components/schemas/entity-attribute' + description: Attributes of the attribute, only used when the attribute type is ComposedID + is_collection: + type: boolean + description: Flag used to indicate if the attribute is a collection or a object + example: false + required: + - name + - type + - columns + - foreign_columns + - is_collection + + attribute-column: + type: object + properties: + name: + type: string + description: Name of the column in the database + example: first_name + primary_key: + type: boolean + description: Flag used to indicate if the column is the primary key + example: true + autogenerated: + type: boolean + description: Flag used to indicate if the column is autogenerated + example: true + sequence: + type: string + description: Name of the sequence used to generate the value, only used if provided and is a primary key autogenerated + example: main_seq + unique: + type: boolean + description: Flag used to indicate if the column is unique #TODO analyze only used in DDL creation + example: true + reference_column: + type: string + description: Name of the column that this column references, only required when the attribute is a Entity with ComposedID + example: main_id + required: + - primary_key + + attribute-relation: + type: object + description: Data about the relation of this attribute with other Entity + properties: + related_entity: + type: string + description: Name of the other Entity related with the attribute + example: Owner + intermediate_table: + type: string + description: Name of the intermediate table in database, only required when define many to many relations + example: pets_tags + owner: + type: boolean + description: Flag used to indicate what part of the relation is the owner (who can update the data) + example: true + columns: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/attribute-column' + description: Data about the columns in the intermediate table that are related with this Entity table + reverse_columns: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/attribute-column' + description: Data about the columns in the intermediate table that are related with the other Entity table + required: + - related_entity + + controller: + type: object + properties: + entity: + type: string + description: Name of the entity to with this controler belongs, if not provided asumes that is a nonentity controller + example: Pet + mapping: + type: string + description: Base path of the mapping + example: /pets + endpoints: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/endpoint' + required: + - mapping + - endpoints + + endpoint: + type: object + properties: + method: + type: string + enum: [GET, POST, PUT, DELETE] + description: The http method of the endpoint + example: GET + mapping: + type: string + description: Path of the endpoint mapping + example: /{id} + name: + type: string + description: Name of the method that implements the controller + example: getOwnerById + parameters: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/parameter' + request: + $ref: '#/components/schemas/request-body' + response: + $ref: '#/components/schemas/response-body' + related_entity: + type: string + description: Entity related with this endpoint, if not provided asumes that is a nonentity endpoint + example: Pet + required: + - method + - name + + request-body: + type: object + properties: + related_entity: + type: string + description: Entity related with this request, if not provided asumes that is a nonentity request + example: Pet + attributes: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/body-attribute' + description: List of attributes of the request + required: + - attributes + + response-body: + type: object + properties: + is_standard: + type: boolean + description: Flag used to indicate if is a standard response + example: false + is_collection: + type: boolean + description: Flag used to indicate if is a collection response + example: false + collection_name: + type: string + description: Name of the collection in the standard response, only required when is collection and standard + related_entity: + type: string + description: Entity related with this response, if not provided asumes that is a nonentity response + attributes: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/body-attribute' + description: List of attributes of the response + default_status_code: + type: integer + format: int32 + description: Status code of the response, only if it is not standard entity out resource + required: + - is_standard + - is_collection + - attributes + + parameter: + type: object + properties: + required: + type: boolean + description: Flag used to indicate if the parameter is required + example: false + in: + type: string + enum: [path, query] + description: Type of the parameter location, in the path of the mapping or in the query parameters + example: path + default_value: + type: object + description: Default value of the parameter, accepts any type + example: 23 + name: + type: string + description: Name of the parameter + example: pet_id + validations: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/validation' + description: List of validations of the request + type: + type: string + description: Type of the parameter, values accepted are the OpenAPI types + example: string + format: + type: string + description: Format of the parameter, values accepted are the OpenAPI formats for the given type + example: in64 + is_collection: + type: boolean + description: Flag used to indicate if the parameter is a collection + example: false + required: + - required + - in + - name + - type + - is_collection + + body-attribute: + type: object + properties: + name: + type: string + description: Name of the field in the json + example: first_name + validations: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/validation' + description: List of validations of the attribute + attributes: + type: array + minItems: 0 + items: + $ref: '#/components/schemas/body-attribute' + description: List of attributes of the attribute + type: + type: string + description: Type of the attribute, values accepted are the OpenAPI types + example: string + format: + type: string + description: Format of the attribute, values accepted are the OpenAPI formats for the given type + example: int64 + is_collection: + type: boolean + description: Flag used to indicate if the parameter is a collection + example: false + related_entity: + type: string + description: Name of the entity that represents that attribute + example: Pet + entity_field_name: + type: string + description: Name of the entity field that represents that attribute, if not given, by default uses the name transformed to snakeCase + example: firstName + required: + - name + - type + - is_collection + + validation: + anyOf: + - $ref: '#/components/schemas/void-validation' + - $ref: '#/components/schemas/long-validation' + - $ref: '#/components/schemas/decimal-validation' + - $ref: '#/components/schemas/size-validation' + - $ref: '#/components/schemas/digits-validation' + - $ref: '#/components/schemas/pattern-validation' + + void-validation: + type: object + properties: + type: + type: string + enum: [NOT_NULL, EMAIL, NOT_EMPTY, NOT_BLANK, POSITIVE, POSITIVE_OR_ZERO, NEGATIVE, NEGATIVE_OR_ZERO, PAST, PAST_OR_PRESENT, FUTURE, FUTURE_OR_PRESENT] + example: NOT_NULL + + long-validation: + type: object + properties: + type: + type: string + enum: [MIN, MAX] + example: MIN + long_value: + type: integer + format: int64 + example: 20 + inclusive: + type: boolean + example: false + + decimal-validation: + type: object + properties: + type: + type: string + enum: [DECIMAL_MIN, DECIMAL_MAX] + example: DECIMAL_MIN + decimal_value: + type: number + format: double + example: 5.25 + inclusive: + type: boolean + example: false + + size-validation: + type: object + properties: + type: + type: string + enum: [SIZE] + example: SIZE + integer_value_one: + type: integer + format: int32 + description: Minimal size + example: 2 + integer_value_two: + type: integer + format: int32 + description: Maximal size + example: 5 + + digits-validation: + type: object + properties: + type: + type: string + enum: [DIGITS] + example: DIGITS + integer_value_one: + type: integer + format: int32 + description: Max number of digits in integer part + example: 10 + integer_value_two: + type: integer + format: int32 + description: Max number of digits in decimal part + example: 20 + + pattern-validation: + type: object + properties: + type: + type: string + enum: [PATTERN] + example: PATTERN + string_value: + type: string + description: Regex pattern + example: [^i*&2@] diff --git a/generator-rest/pom.xml b/generator-rest/pom.xml new file mode 100644 index 0000000..fe62edd --- /dev/null +++ b/generator-rest/pom.xml @@ -0,0 +1,74 @@ + + + + 4.0.0 + + + apigen + net.cloudappi.apigen + 0.0.1-SNAPSHOT + ../pom.xml + + + generator-rest + generator-rest + ${revision} + + + 1.4.13 + + + + + net.cloudappi.apigen + generator-core + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + io.springfox + springfox-swagger-ui + ${springfox.version} + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + com.spotify + dockerfile-maven-plugin + ${dockerfile-maven-plugin.version} + + apigen-generator-rest + + target/${project.build.finalName}.jar + + + + + + + \ No newline at end of file diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/App.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/App.java new file mode 100644 index 0000000..f771283 --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/App.java @@ -0,0 +1,11 @@ +package net.cloudappi.apigen.generatorrest; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App { + public static void main(String[] args) { + SpringApplication.run(App.class, args); + } +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenCoreConfig.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenCoreConfig.java new file mode 100644 index 0000000..ca0c466 --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenCoreConfig.java @@ -0,0 +1,18 @@ +package net.cloudappi.apigen.generatorrest.core.config; + +import net.cloudappi.apigen.generatorcore.generator.ApigenProjectGenerator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ApigenCoreConfig { + + @Bean + public ApigenProjectGenerator apigenProjectGenerator(ApigenProperties properties) { + return new ApigenProjectGenerator( + properties.getParent().getGroup(), + properties.getParent().getArtifact(), + properties.getParent().getVersion() + ); + } +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenProperties.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenProperties.java new file mode 100644 index 0000000..9839361 --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenProperties.java @@ -0,0 +1,20 @@ +package net.cloudappi.apigen.generatorrest.core.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "apigen") +public class ApigenProperties { + + private Parent parent; + + @Data + public static class Parent { + private String group; + private String artifact; + private String version; + } +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java new file mode 100644 index 0000000..694dd2e --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java @@ -0,0 +1,12 @@ +package net.cloudappi.apigen.generatorrest.core.config; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableAutoConfiguration(exclude = {HibernateJpaAutoConfiguration.class, DataSourceAutoConfiguration.class}) +public class AutoconfigureExclusionsConfig { + // Intentional blank +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/DocumentationConfig.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/DocumentationConfig.java new file mode 100644 index 0000000..cc01998 --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/DocumentationConfig.java @@ -0,0 +1,38 @@ +package net.cloudappi.apigen.generatorrest.core.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.RestController; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@Configuration +@EnableSwagger2 +public class DocumentationConfig { + + @Value("${spring.application.name}") + private String name; + + @Value("${spring.application.description}") + private String description; + + @Value("${spring.application.version}") + private String version; + + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) + .build() + .apiInfo( + new ApiInfoBuilder() + .description(description).title(name).version(version).build()) + .forCodeGeneration(true) + .useDefaultResponseMessages(false); + } +} \ No newline at end of file diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java new file mode 100644 index 0000000..c80c0ba --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java @@ -0,0 +1,16 @@ +package net.cloudappi.apigen.generatorrest.core.exceptions; + +public enum GeneratorRestErrors { + ZIP_EMPTY(1000, "Zip empty"), + ZIP_MULTIPLE_ROOT(1001, "Zip file with multiple root files and none named 'api'"), + UNEXPECTED_ERROR(2000, "Unexpected error") + ; + + public final int code; + public final String message; + + GeneratorRestErrors(Integer code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestException.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestException.java new file mode 100644 index 0000000..e0e3008 --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestException.java @@ -0,0 +1,16 @@ +package net.cloudappi.apigen.generatorrest.core.exceptions; + +import java.util.Collections; +import java.util.List; + +public class GeneratorRestException extends RuntimeException { + private final List errors; + + public GeneratorRestException(GeneratorRestErrors error) { + this.errors = Collections.singletonList(error); + } + + public List getErrors() { + return errors; + } +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/web/ExceptionAdvice.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/web/ExceptionAdvice.java new file mode 100644 index 0000000..656a54f --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/web/ExceptionAdvice.java @@ -0,0 +1,78 @@ +package net.cloudappi.apigen.generatorrest.core.web; + +import lombok.extern.slf4j.Slf4j; +import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; +import net.cloudappi.apigen.generatorcore.exceptions.DefinitionException; +import net.cloudappi.apigen.generatorcore.exceptions.ExtractorException; +import net.cloudappi.apigen.generatorcore.exceptions.InvalidValuesException; +import net.cloudappi.apigen.generatorrest.core.exceptions.GeneratorRestException; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static net.cloudappi.apigen.generatorcore.exceptions.GeneratorErrors.CONFIGURATION_NOT_VALID; +import static net.cloudappi.apigen.generatorcore.exceptions.GeneratorErrors.EXTRACTOR_ERROR; +import static net.cloudappi.apigen.generatorrest.core.exceptions.GeneratorRestErrors.UNEXPECTED_ERROR; + +@Slf4j +@ControllerAdvice +public class ExceptionAdvice { + + @ResponseBody + @ExceptionHandler(GeneratorRestException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handle(GeneratorRestException e) { + List errors = e.getErrors().stream() + .map(ec -> new ApiError(ec.code, ec.message, null)) + .collect(Collectors.toList()); + return new ApiResponse().withResultErrors(errors); + } + + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(ExtractorException.class) + public ApiResponse handle(ExtractorException e) { + List errors = e.getErrors().stream() + .map(ec -> new ApiError(ec.getCode(), ec.getMessage(), ec.getReference())) + .collect(Collectors.toList()); + return new ApiResponse().withResultErrors(errors); + } + + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(DefinitionException.class) + public ApiResponse handle(DefinitionException e) { + ApiError error = new ApiError(EXTRACTOR_ERROR.code, EXTRACTOR_ERROR.message, null); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(InvalidValuesException.class) + public ApiResponse handle(InvalidValuesException e) { + List errors = e.getViolations().stream() + .map(v -> new ApiError(CONFIGURATION_NOT_VALID.code, CONFIGURATION_NOT_VALID.message, v.getPropertyPath().toString())) + .collect(Collectors.toList()); + return new ApiResponse().withResultErrors(errors); + } + + @ResponseBody + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResponse handle(Exception e) { + log.error("Unexpected error", e); + ApiError error = new ApiError(UNEXPECTED_ERROR.code, UNEXPECTED_ERROR.message, null); + return new ApiResponse().withResultErrors(errors(error)); + } + + private List errors(ApiError... apiErrors) { + return Arrays.asList(apiErrors); + } +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/uitls/OpenAPIZipUtils.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/uitls/OpenAPIZipUtils.java new file mode 100644 index 0000000..e1c5a04 --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/uitls/OpenAPIZipUtils.java @@ -0,0 +1,38 @@ +package net.cloudappi.apigen.generatorrest.uitls; + +import net.cloudappi.apigen.generatorrest.core.exceptions.GeneratorRestErrors; +import net.cloudappi.apigen.generatorrest.core.exceptions.GeneratorRestException; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class OpenAPIZipUtils { + + private OpenAPIZipUtils() { + // Intentional blank + } + + public static File getZipRootFile(MultipartFile zip) throws IOException { + Path tempDir = net.cloudappi.apigen.generatorcore.utils.ZipUtils.unzip(zip.getBytes()); + try (Stream stream = Files.walk(tempDir).filter(Files::isRegularFile).map(Path::toFile)) { + List files = stream.collect(Collectors.toList()); + if (files.isEmpty()) { + throw new GeneratorRestException(GeneratorRestErrors.ZIP_EMPTY); + } else { + if (files.size() > 1) { + return files.stream() + .filter(f -> f.getName().equals("api.json") || f.getName().equals("api.yaml")) + .findFirst().orElseThrow(() -> new GeneratorRestException(GeneratorRestErrors.ZIP_MULTIPLE_ROOT)); + } else { + return files.get(0); + } + } + } + } +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/ConfigController.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/ConfigController.java new file mode 100644 index 0000000..1bb5546 --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/ConfigController.java @@ -0,0 +1,34 @@ +package net.cloudappi.apigen.generatorrest.web; + +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.generator.ApigenProjectGenerator; +import net.cloudappi.apigen.generatorcore.utils.ZipUtils; +import net.cloudappi.apigen.generatorrest.uitls.OpenAPIZipUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; + +@RestController +@RequestMapping("/configuration") +public class ConfigController { + + private final ApigenProjectGenerator generator; + + public ConfigController(ApigenProjectGenerator generator) { + this.generator = generator; + } + + @PostMapping(value = "/file") + public Configuration fromFile(@RequestParam("file") MultipartFile file) throws IOException { + if (ZipUtils.isZip(file)) { + File rootFile = OpenAPIZipUtils.getZipRootFile(file); + return generator.getConfiguration(rootFile.toPath()); + } else { + return generator.getConfiguration(file.getBytes()); + } + } + + +} diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/GeneratorController.java b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/GeneratorController.java new file mode 100644 index 0000000..c154aa7 --- /dev/null +++ b/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/GeneratorController.java @@ -0,0 +1,59 @@ +package net.cloudappi.apigen.generatorrest.web; + +import net.cloudappi.apigen.generatorcore.config.Configuration; +import net.cloudappi.apigen.generatorcore.generator.ApigenProjectGenerator; +import net.cloudappi.apigen.generatorcore.generator.Project; +import net.cloudappi.apigen.generatorcore.utils.ZipUtils; +import net.cloudappi.apigen.generatorrest.uitls.OpenAPIZipUtils; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; + +@RestController +@RequestMapping("/generator") +public class GeneratorController { + + private final ApigenProjectGenerator generator; + + public GeneratorController(ApigenProjectGenerator generator) { + this.generator = generator; + } + + @PostMapping(value = "/file", produces = "application/zip") + public ResponseEntity generateFromFile(@RequestParam("file") MultipartFile file) throws IOException { + Project project; + if (ZipUtils.isZip(file)) { + File rootFile = OpenAPIZipUtils.getZipRootFile(file); + project = generator.generate(rootFile.toPath()); + } else { + project = generator.generate(file.getBytes()); + } + return toResponse(project); + } + + @PostMapping(value = "/config", produces = "application/zip") + public ResponseEntity generateFromConfig(@RequestBody Configuration configuration) throws IOException { + Project project = generator.generate(configuration); + return toResponse(project); + } + + private ResponseEntity toResponse(Project project) { + return new ResponseEntity<>( + project.getContent(), + generateHeaderWithAttachment(project.getName()), + HttpStatus.CREATED + ); + } + + private HttpHeaders generateHeaderWithAttachment(String fileName) { + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", fileName)); + return headers; + } +} diff --git a/generator-rest/src/main/resources/application.properties b/generator-rest/src/main/resources/application.properties new file mode 100644 index 0000000..68451a9 --- /dev/null +++ b/generator-rest/src/main/resources/application.properties @@ -0,0 +1,13 @@ +logging.level.net.cloudappi.apigen=debug +spring.servlet.multipart.enabled=true +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=15MB +spring.jackson.default-property-inclusion=non_null + +spring.application.name=@name@ +spring.application.description=@description@ +spring.application.version=@version@ + +apigen.parent.group=net.cloudappi.apigen +apigen.parent.artifact=archetype-parent-spring-boot +apigen.parent.version=0.0.1-SNAPSHOT \ No newline at end of file diff --git a/generator-rest/src/test/java/net/cloudappi/apigen/generatorrest/AppTests.java b/generator-rest/src/test/java/net/cloudappi/apigen/generatorrest/AppTests.java new file mode 100644 index 0000000..510108e --- /dev/null +++ b/generator-rest/src/test/java/net/cloudappi/apigen/generatorrest/AppTests.java @@ -0,0 +1,13 @@ +package net.cloudappi.apigen.generatorrest; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class AppTests { + + @Test + void thatContextLoads() { + // Intentional blank + } +} diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000..74da825 --- /dev/null +++ b/lombok.config @@ -0,0 +1 @@ +lombok.addLombokGeneratedAnnotation=true \ No newline at end of file diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..a16b543 --- /dev/null +++ b/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c8d4337 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..bc6ffb1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,162 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.3.3.RELEASE + + + + net.cloudappi.apigen + apigen + 0.0.1-SNAPSHOT + pom + + apigen + Spring Boot Archetype Generator + + + + 0.0.1-SNAPSHOT + + 1.8 + 1.8 + 1.8 + + UTF-8 + UTF-8 + + 1.13.0 + 3.6.3 + 2.7 + 2.9.2 + 2.0.21 + 1.9.4 + 1.3.1.Final + 0.9.11 + + 3.7.0.1746 + 0.8.5 + + jacoco + ${project.basedir}/../target/jacoco.exec + java + + + + archetype-core + generator-core + generator-rest + archetype-parent-spring-boot + + + + + + net.cloudappi.apigen + archetype-core + ${revision} + + + net.cloudappi.apigen + generator-core + ${revision} + + + io.springfox + springfox-swagger2 + ${springfox.version} + + + commons-beanutils + commons-beanutils + ${beanutils.version} + + + org.reflections + reflections + ${reflections.version} + + + com.squareup + javapoet + ${javapoet.version} + + + org.apache.maven + maven-model + ${maven-model.version} + + + commons-io + commons-io + ${commons-io.version} + + + io.swagger.parser.v3 + swagger-parser + ${swagger-parser.version} + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + ${sonar.maven.plugin.version} + + + org.jacoco + jacoco-maven-plugin + ${jacoco.maven.plugin.version} + + ${sonar.jacoco.reportPath} + true + + + + agent + + prepare-agent + + + + + + + + From 5b600e83be153690f9052510ff032c923991fdb2 Mon Sep 17 00:00:00 2001 From: admin-cloudappi Date: Thu, 3 Dec 2020 16:40:09 +0100 Subject: [PATCH 2/7] Update README.md --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0585319..f444631 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,19 @@ # OpenAPI Apigen Extension +

+ + + +

+ +# contributors +## CloudAPPi +CloudAppi is one leader in APIs in global word. See the [CloudAPPi Services](https://cloudappi.net) + +## Madrid Digital +Madrid Digital is a public administration in Spain. See the [Comunidad de Madrid website](https://www.comunidad.madrid/) + + ## Proyecto ### Esquema @@ -291,4 +305,4 @@ La extensión de Apigen para OpenAPI nos obliga a tener en cuenta una serie de c name: type: string forms: - $ref: "#/components/schemas/forms" \ No newline at end of file + $ref: "#/components/schemas/forms" From 62f2e6acb9f1745304ed5888d1528244dba464d5 Mon Sep 17 00:00:00 2001 From: admin-cloudappi Date: Thu, 3 Dec 2020 16:40:51 +0100 Subject: [PATCH 3/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f444631..cb25e16 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- +

From c2395057d2e7da1492cb81e04a766f8445ab2c67 Mon Sep 17 00:00:00 2001 From: admin-cloudappi Date: Thu, 3 Dec 2020 16:41:08 +0100 Subject: [PATCH 4/7] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb25e16..b8aee23 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -# OpenAPI Apigen Extension +

+# OpenAPI Apigen Extension # contributors ## CloudAPPi From ad110059bdee2d88936e1ef5ecea234b29f65337 Mon Sep 17 00:00:00 2001 From: admin-cloudappi Date: Thu, 3 Dec 2020 16:41:36 +0100 Subject: [PATCH 5/7] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index b8aee23..fe739d1 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,6 @@ ## CloudAPPi CloudAppi is one leader in APIs in global word. See the [CloudAPPi Services](https://cloudappi.net) -## Madrid Digital -Madrid Digital is a public administration in Spain. See the [Comunidad de Madrid website](https://www.comunidad.madrid/) - - ## Proyecto ### Esquema From e3a110eed46b6fc5e269f1729622e1afdf5c8ac3 Mon Sep 17 00:00:00 2001 From: Adrian Palanques Date: Mon, 5 Jul 2021 11:37:33 +0200 Subject: [PATCH 6/7] Update namespace --- README.md | 2 +- apigen-examples/other/subclassesTestApi.yaml | 865 ++++++++++++++++++ apigen-examples/relations/many-to-many.yaml | 52 ++ .../relations/many-to-many__many-to-many.yaml | 59 ++ apigen-examples/relations/one-to-many.yaml | 48 + .../relations/one-to-many__many-to-one.yaml | 53 ++ .../relations/one-to-one__one-to-one.yaml | 53 ++ .../simple-id-models/api-test-reduced.yaml | 12 +- .../simple-id-models/api-test.yaml | 10 - archetype-core/pom.xml | 34 +- .../core/AbstractRelationsManager.java | 19 - .../core/persistence/filter/PropType.java | 73 -- .../autoconfigure/ApigenApplication.java | 4 +- .../autoconfigure/ApigenConfiguration.java | 20 +- .../ApigenDocumentationConfiguration.java | 2 +- .../autoconfigure/ApigenProperties.java | 4 +- .../core/AbstractCrudService.java | 19 +- .../core/AbstractReadService.java | 25 +- .../core/AbstractRelationsManager.java | 51 ++ .../archetypecore/core/ApigenMapper.java | 2 +- .../core/advice/ApigenControllerAdvice.java | 39 +- .../core/errors/ApigenError.java | 2 +- .../core/errors/ApigenErrorManager.java | 4 +- .../core/errors/DefaultApigenError.java | 7 +- .../errors/DefaultApigenErrorManager.java | 6 +- .../persistence/ApigenAbstractAudited.java | 27 + .../ApigenAbstractPersistable.java | 11 +- .../ApigenAbstractTimeAudited.java | 26 + .../core/persistence/ApigenRepository.java | 2 +- .../persistence/ApigenRepositoryImpl.java | 6 +- .../core/persistence/ApigenSearch.java | 6 +- .../core/persistence/ApigenSearchResult.java | 2 +- .../persistence/executor/ApigenConverter.java | 6 + .../executor/ApigenSearchExecutor.java | 75 +- .../ApigenSearchExecutorTypeConverters.java | 64 ++ .../persistence/executor/AttributeInfo.java | 6 +- .../core/persistence/executor/EntityInfo.java | 15 +- .../persistence/executor/EntityLookup.java | 4 +- .../persistence/executor/TupleMapper.java | 4 +- .../core/persistence/filter/Filter.java | 2 +- .../persistence/filter/FilterOperation.java | 2 +- .../core/persistence/filter/Value.java | 7 +- ...enFunctionsMetadataBuilderInitializer.java | 2 +- .../persistence/pagination/Pagination.java | 2 +- .../resource/ApigenEntityOutResource.java | 2 +- .../core/resource/FilterResource.java | 4 +- .../resource/ResourceNamingTranslator.java | 4 +- .../ResourceNamingTranslatorByReflection.java | 6 +- .../core/responses/ApiResponse.java | 10 +- .../content/ApiListResponseContent.java | 2 +- .../core/responses/metadata/Links.java | 12 +- .../core/responses/metadata/Metadata.java | 2 +- .../core/responses/metadata/Paging.java | 2 +- .../core/responses/result/ApiError.java | 2 +- .../core/responses/result/ApiResult.java | 2 +- .../exceptions/CustomApigenException.java | 2 +- .../EntityIdAlreadyInUseException.java | 15 + .../exceptions/EntityNotFoundException.java | 2 +- .../exceptions/InvalidPropertyPath.java | 2 +- .../exceptions/NotImplementedException.java | 2 +- .../exceptions/RelationalErrors.java | 2 +- .../exceptions/RelationalErrorsException.java | 2 +- .../interceptors/ApigenContext.java | 2 +- .../archetypecore/interceptors/WebConfig.java | 10 +- .../interceptors/expand/ApigenExpand.java | 2 +- .../expand/ExpandAnnotationInterceptor.java | 2 +- .../expand/ExpandInterceptor.java | 4 +- .../expand/ExpandPathInterceptor.java | 22 +- .../response/ApiResponseBodyAdvice.java | 8 +- .../response/ApiResponseEnhancer.java | 6 +- .../trace/TraceIdInterceptor.java | 4 +- .../update/CachingRequestBodyFilter.java | 2 +- .../update/UpdateRequestBodyAdvice.java | 4 +- ...ernate.boot.spi.MetadataBuilderInitializer | 2 +- .../stubs/FakeEntityDatesRepository.java | 8 - .../stubs/FakeEntityNodeRepository.java | 8 - .../apigen/archetypecore/FakeApplication.java | 4 +- .../core/AbstractCrudServiceTest.java | 17 +- .../core/AbstractReadServiceTest.java | 39 +- .../advice/ApigenControllerAdviceTests.java | 58 +- .../FakeApiResponseErrorsController.java | 47 +- .../persistence/ApigenRepositoryTest.java | 43 +- ...pigenSearchExecutorTypeConvertersTest.java | 82 ++ .../persistence/stubs/FakeEntityDates.java | 4 +- .../stubs/FakeEntityDatesRepository.java | 8 + .../persistence/stubs/FakeEntityNode.java | 4 +- .../stubs/FakeEntityNodeRepository.java | 8 + ...urceNamingTranslatorByReflectionTests.java | 16 +- .../resource/stubs/FakeResourceColor.java | 4 +- .../core/resource/stubs/FakeResourceForm.java | 4 +- .../responses/ApiResponseObjectMother.java | 2 +- .../ApiResponseBodyAdviceTest.java | 2 +- .../interceptors/ExpandInterceptorTests.java | 2 +- .../interceptors/TraceIdInterceptorTests.java | 2 +- .../controller/FakeApiResultController.java | 8 +- .../controller/FakeExpandController.java | 4 +- .../controller/FakePaginationController.java | 6 +- .../controller/FakeTraceIdController.java | 4 +- .../src/test/resources/application.properties | 2 +- archetype-parent-spring-boot/pom.xml | 50 +- generator-core/pom.xml | 40 +- .../exceptions/DefinitionException.java | 4 - .../web/resource/ResourceBuilder.java | 7 - .../generator/web/resource/ResourcesData.java | 23 - .../output/OutputResourceBuilder.java | 7 - .../web/response/ResponseBuilder.java | 7 - .../generatorcore/config/Configuration.java | 6 +- .../config/controller/Attribute.java | 4 +- .../config/controller/Controller.java | 2 +- .../config/controller/Endpoint.java | 2 +- .../config/controller/Parameter.java | 2 +- .../config/controller/Request.java | 3 +- .../config/controller/Response.java | 2 +- .../config/entity/Attribute.java | 4 +- .../generatorcore/config/entity/Column.java | 2 +- .../generatorcore/config/entity/Entity.java | 2 +- .../generatorcore/config/entity/Relation.java | 2 +- .../config/entity/RelationType.java | 2 +- .../config/extractors/AbstractExtractor.java | 4 +- .../extractors/AttributesExtractor.java | 10 +- .../extractors/ConfigurationExtractor.java | 14 +- .../extractors/ControllersExtractor.java | 12 +- .../config/extractors/EntitiesExtractor.java | 18 +- .../extractors/ParametersExtractor.java | 4 +- .../config/extractors/RequestExtractor.java | 8 +- .../config/extractors/ResponseExtractor.java | 8 +- .../extractors/ValidationsExtractor.java | 8 +- .../extractors/context/ExtractorContext.java | 6 +- .../config/validation/Validation.java | 2 +- .../config/validation/ValidationType.java | 44 +- .../exceptions/DefinitionException.java | 4 + .../exceptions/ExtractorException.java | 4 +- .../exceptions/GeneratorErrors.java | 2 +- .../exceptions/InvalidValuesException.java | 2 +- .../generator/ApigenProjectGenerator.java | 46 +- .../generatorcore/generator/Project.java | 2 +- .../generator/base/ApplicationBuilder.java | 6 +- .../generator/base/ApplicationGenerator.java | 6 +- .../generator/base/GitIgnoreGenerator.java | 54 ++ .../generator/base/LombokConfigGenerator.java | 28 + .../generator/base/PomGenerator.java | 4 +- .../base/ProjectStructureGenerator.java | 4 +- .../generator/base/PropertiesGenerator.java | 4 +- .../base/SpringBootBaseGenerator.java | 4 +- .../base/SpringBootContextTestGenerator.java | 10 +- .../common/AbstractClassBuilder.java | 2 +- .../generator/common/AbstractGenerator.java | 2 +- .../common/ApigenExt2JavapoetType.java | 2 +- .../generator/common/Formats.java | 2 +- .../generator/common/Members.java | 2 +- .../common/Openapi2JavapoetType.java | 2 +- .../generator/common/Validator.java | 4 +- .../generator/mapper/MapperBuilder.java | 72 +- .../generator/mapper/MappersGenerator.java | 17 +- .../persistence/ComposedIdBuilder.java | 27 +- .../generator/persistence/EntitiesData.java | 2 +- .../persistence/EntitiesGenerator.java | 8 +- .../generator/persistence/EntityBuilder.java | 61 +- .../persistence/EntityRelationManager.java | 110 +-- .../persistence/relations/ColumnRelation.java | 2 +- .../relations/ManyToManyBuilder.java | 6 +- .../relations/ManyToManyOwnerBuilder.java | 8 +- .../relations/ManyToOneBuilder.java | 2 +- .../relations/OneToManyBuilder.java | 6 +- .../relations/OneToOneBuilder.java | 6 +- .../relations/OneToOneOwnerBuilder.java | 2 +- .../relations/RelatedFieldBuilder.java | 8 +- .../repository/RepositoriesGenerator.java | 10 +- .../repository/RepositoryBuilder.java | 8 +- .../service/RelationManagerBuilder.java | 81 +- .../service/RelationManagersGenerator.java | 10 +- .../generator/service/ServiceBuilder.java | 14 +- .../generator/service/ServicesGenerator.java | 10 +- .../web/controller/ControllerBuilder.java | 22 +- .../controller/ControllerBuilderFactory.java | 6 +- .../web/controller/ControllersGenerator.java | 10 +- .../controller/EntityControllerBuilder.java | 12 +- .../controller/ResourceControllerBuilder.java | 6 +- .../endpoints/CustomEndpointBuilder.java | 22 +- .../endpoints/DeleteEndpointBuilder.java | 6 +- .../controller/endpoints/EndpointBuilder.java | 24 +- .../endpoints/EndpointBuilderFactory.java | 8 +- .../endpoints/GetAllEndpointBuilder.java | 14 +- .../endpoints/GetByIdEndpointBuilder.java | 12 +- .../endpoints/PostEndpointBuilder.java | 14 +- .../endpoints/PostSearchEndpointBuilder.java | 18 +- .../endpoints/PutEndpointBuilder.java | 14 +- .../parameters/ParameterBuilder.java | 6 +- .../parameters/PathParameterBuilder.java | 8 +- .../parameters/QueryParameterBuilder.java | 10 +- .../web/resource/ResourceBuilder.java | 7 + .../generator/web/resource/ResourcesData.java | 34 + .../web/resource/ResourcesGenerator.java | 24 +- .../input/AllInputResourceBuilder.java | 31 +- .../resource/input/InputResourceBuilder.java | 4 +- .../input/InputResourceBuilderFactory.java | 8 +- .../output/EntityOutputResourceBuilder.java | 24 +- .../output/OutputResourceBuilder.java | 10 + .../output/OutputResourceBuilderFactory.java | 10 +- .../output/ResourceOutputResourceBuilder.java | 12 +- .../response/EntityListResponseBuilder.java | 8 +- .../web/response/ListResponseBuilder.java | 35 +- .../response/ResourceListResponseBuilder.java | 8 +- .../ResourceSimpleResponseBuilder.java | 10 +- .../web/response/ResponseBuilder.java | 7 + .../web/response/ResponseBuilderFactory.java | 10 +- .../web/response/ResponsesGenerator.java | 12 +- .../web/response/SimpleResponseBuilder.java | 6 +- .../generatorcore/spec/OpenAPIExtended.java | 10 +- .../spec/components/ApigenBinding.java | 2 +- .../spec/components/ApigenModel.java | 2 +- .../spec/components/ApigenProject.java | 2 +- .../spec/components/Extensions.java | 4 +- .../utils/CustomStringUtils.java | 2 +- .../apigen/generatorcore/utils/Mapping.java | 2 +- .../apigen/generatorcore/utils/ZipUtils.java | 2 +- .../mapper/MapperBuilderObjectMother.java | 18 - .../config/ConfigurationObjectMother.java | 2 +- .../controller/ControllerObjectMother.java | 4 +- .../EndpointBaseResponseObjectMother.java | 2 +- .../EndpointRequestObjectMother.java | 2 +- .../config/entity/EntityObjectMother.java | 93 +- .../ConfigurationExtractorTest.java | 22 +- .../mapper/MapperBuilderObjectMother.java | 29 + .../parameter/ParameterObjectMother.java | 8 +- .../base/GitIgnoreFileGeneratorTest.java | 26 + .../base/LombokConfigFileGeneratorTest.java | 26 + .../generator/base/PomFileGeneratorTest.java | 10 +- .../base/ProjectStructureGeneratorTest.java | 6 +- .../base/SpringBootBaseGeneratorTest.java | 6 +- .../SpringBootContextTestGeneratorTest.java | 6 +- .../common/ApigenExt2JavapoetTypeTests.java | 2 +- .../common/Openapi2JavapoetTypeTest.java | 2 +- .../generator/mapper/MapperBuilderTest.java | 68 +- .../generator/mapper/MapperGeneratorTest.java | 14 +- .../persistence/ComposedIdBuilderTest.java | 95 ++ .../persistence/EntitiesGeneratorTest.java | 113 ++- .../EntityRelationManagerTest.java | 31 + .../repository/RepositoriesGeneratorTest.java | 8 +- .../repository/RepositoryBuilderTests.java | 10 +- .../service/RelationManagerBuilderTests.java | 61 +- .../service/ServiceBuilderTests.java | 14 +- .../service/ServicesGeneratorTest.java | 8 +- .../controller/ControllersGeneratorTest.java | 8 +- .../EntityControllerBuilderTests.java | 10 +- .../endpoints/AttributeObjectMother.java | 4 +- .../endpoints/CustomEndpointBuilderTests.java | 8 +- .../endpoints/DeleteEndpointBuilderTests.java | 6 +- .../EndpointBuilderFactoryTests.java | 8 +- .../endpoints/EndpointObjectMother.java | 12 +- .../endpoints/GetAllEndpointBuilderTests.java | 8 +- .../GetByIdEndpointBuilderTests.java | 6 +- .../endpoints/PostEndpointBuilderTests.java | 6 +- .../PostSearchEndpointBuilderTests.java | 12 +- .../endpoints/PutEndpointBuilderTests.java | 6 +- .../parameters/ParameterBuilderTest.java | 6 +- .../web/resource/ResourceGeneratorTest.java | 6 +- .../input/AllInputResourceBuilderTests.java | 58 +- .../EntityOutputResourceBuilderTest.java | 8 +- .../ResourceOutputResourceBuilderTest.java | 10 +- .../EntityListResponseBuilderTests.java | 12 +- .../ResourceListResponseBuilderTests.java | 18 +- .../ResourceSimpleResponseBuilderTests.java | 10 +- .../web/response/ResponsesGeneratorTests.java | 6 +- .../response/SimpleResponseBuilderTests.java | 4 +- .../utils/CustomStringUtilsTest.java | 2 +- .../generatorcore/utils/ZipUtilsTest.java | 2 +- generator-rest/pom.xml | 36 +- .../apitools}/apigen/generatorrest/App.java | 2 +- .../core/config/ApigenCoreConfig.java | 4 +- .../core/config/ApigenProperties.java | 2 +- .../config/AutoconfigureExclusionsConfig.java | 2 +- .../core/config/DocumentationConfig.java | 2 +- .../core/exceptions/GeneratorRestErrors.java | 2 +- .../exceptions/GeneratorRestException.java | 2 +- .../core/web/ExceptionAdvice.java | 20 +- .../generatorrest/uitls/OpenAPIZipUtils.java | 8 +- .../generatorrest/web/ConfigController.java | 10 +- .../web/GeneratorController.java | 12 +- .../src/main/resources/application.properties | 6 +- .../apigen/generatorrest/AppTests.java | 2 +- pom.xml | 125 ++- 282 files changed, 3515 insertions(+), 1378 deletions(-) create mode 100644 apigen-examples/other/subclassesTestApi.yaml create mode 100644 apigen-examples/relations/many-to-many.yaml create mode 100644 apigen-examples/relations/many-to-many__many-to-many.yaml create mode 100644 apigen-examples/relations/one-to-many.yaml create mode 100644 apigen-examples/relations/one-to-many__many-to-one.yaml create mode 100644 apigen-examples/relations/one-to-one__one-to-one.yaml delete mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractRelationsManager.java delete mode 100644 archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/PropType.java rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/autoconfigure/ApigenApplication.java (71%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/autoconfigure/ApigenConfiguration.java (66%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java (97%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/autoconfigure/ApigenProperties.java (85%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/AbstractCrudService.java (91%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/AbstractReadService.java (72%) create mode 100644 archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractRelationsManager.java rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/ApigenMapper.java (57%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/advice/ApigenControllerAdvice.java (90%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/errors/ApigenError.java (81%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/errors/ApigenErrorManager.java (65%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/errors/DefaultApigenError.java (88%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java (88%) create mode 100644 archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractAudited.java rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java (82%) create mode 100644 archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractTimeAudited.java rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/ApigenRepository.java (84%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java (82%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/ApigenSearch.java (76%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/ApigenSearchResult.java (83%) create mode 100644 archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenConverter.java rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java (83%) create mode 100644 archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutorTypeConverters.java rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/executor/AttributeInfo.java (78%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/executor/EntityInfo.java (92%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/executor/EntityLookup.java (80%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/executor/TupleMapper.java (97%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/filter/Filter.java (79%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/filter/FilterOperation.java (89%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/filter/Value.java (79%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java (97%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/pagination/Pagination.java (70%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/resource/ApigenEntityOutResource.java (69%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/resource/FilterResource.java (52%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/resource/ResourceNamingTranslator.java (76%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java (97%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/responses/ApiResponse.java (85%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/responses/content/ApiListResponseContent.java (79%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/responses/metadata/Links.java (87%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/responses/metadata/Metadata.java (50%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/responses/metadata/Paging.java (94%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/responses/result/ApiError.java (76%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/responses/result/ApiResult.java (86%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/exceptions/CustomApigenException.java (95%) create mode 100644 archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/EntityIdAlreadyInUseException.java rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/exceptions/EntityNotFoundException.java (81%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/exceptions/InvalidPropertyPath.java (92%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/exceptions/NotImplementedException.java (69%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/exceptions/RelationalErrors.java (95%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/exceptions/RelationalErrorsException.java (84%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/ApigenContext.java (92%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/WebConfig.java (83%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/expand/ApigenExpand.java (83%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java (95%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java (94%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java (59%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java (81%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java (91%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java (83%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java (93%) rename archetype-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java (95%) delete mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java delete mode 100644 archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/FakeApplication.java (68%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/AbstractCrudServiceTest.java (88%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/AbstractReadServiceTest.java (80%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java (93%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java (90%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java (71%) create mode 100644 archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutorTypeConvertersTest.java rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java (78%) create mode 100644 archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java (86%) create mode 100644 archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java (95%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java (71%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java (60%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/core/responses/ApiResponseObjectMother.java (91%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java (99%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/ExpandInterceptorTests.java (99%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java (94%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/controller/FakeApiResultController.java (83%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/controller/FakeExpandController.java (89%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/controller/FakePaginationController.java (88%) rename archetype-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java (71%) delete mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/DefinitionException.java delete mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceBuilder.java delete mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesData.java delete mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java delete mode 100644 generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilder.java rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/Configuration.java (81%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/Attribute.java (80%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/Controller.java (81%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/Endpoint.java (91%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/Parameter.java (80%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/Request.java (67%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/Response.java (88%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/entity/Attribute.java (86%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/entity/Column.java (94%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/entity/Entity.java (88%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/entity/Relation.java (95%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/entity/RelationType.java (67%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/AbstractExtractor.java (52%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/AttributesExtractor.java (92%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/ConfigurationExtractor.java (77%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/ControllersExtractor.java (92%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/EntitiesExtractor.java (91%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/ParametersExtractor.java (90%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/RequestExtractor.java (80%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/ResponseExtractor.java (91%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/ValidationsExtractor.java (95%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/context/ExtractorContext.java (77%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/validation/Validation.java (95%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/validation/ValidationType.java (82%) create mode 100644 generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/DefinitionException.java rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/exceptions/ExtractorException.java (64%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/exceptions/GeneratorErrors.java (92%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/exceptions/InvalidValuesException.java (84%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/ApigenProjectGenerator.java (68%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/Project.java (76%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/ApplicationBuilder.java (90%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/ApplicationGenerator.java (62%) create mode 100644 generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/GitIgnoreGenerator.java create mode 100644 generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/LombokConfigGenerator.java rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/PomGenerator.java (95%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/ProjectStructureGenerator.java (86%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/PropertiesGenerator.java (94%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java (85%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java (77%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/AbstractClassBuilder.java (96%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/AbstractGenerator.java (93%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java (96%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/Formats.java (76%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/Members.java (96%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/Openapi2JavapoetType.java (97%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/Validator.java (78%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/mapper/MapperBuilder.java (69%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/mapper/MappersGenerator.java (57%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java (86%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/EntitiesData.java (98%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/EntitiesGenerator.java (79%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/EntityBuilder.java (84%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/EntityRelationManager.java (62%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java (66%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java (67%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java (85%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java (87%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java (69%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java (67%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java (87%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java (70%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java (65%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java (84%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/service/RelationManagerBuilder.java (66%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/service/RelationManagersGenerator.java (62%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/service/ServiceBuilder.java (87%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/service/ServicesGenerator.java (66%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/ControllerBuilder.java (69%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java (66%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/ControllersGenerator.java (60%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java (80%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java (73%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java (76%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java (79%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java (81%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java (86%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java (79%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java (79%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java (74%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java (78%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java (79%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java (83%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java (71%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java (78%) create mode 100644 generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourceBuilder.java create mode 100644 generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourcesData.java rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java (61%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java (86%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java (53%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java (55%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java (78%) create mode 100644 generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java (62%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java (91%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java (70%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/ListResponseBuilder.java (69%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java (83%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java (84%) create mode 100644 generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponseBuilder.java rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java (72%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/ResponsesGenerator.java (63%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java (88%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/spec/OpenAPIExtended.java (86%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/spec/components/ApigenBinding.java (54%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/spec/components/ApigenModel.java (95%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/spec/components/ApigenProject.java (84%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/spec/components/Extensions.java (81%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/utils/CustomStringUtils.java (96%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/utils/Mapping.java (96%) rename generator-core/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/utils/ZipUtils.java (98%) delete mode 100644 generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/ConfigurationObjectMother.java (88%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/ControllerObjectMother.java (93%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java (97%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java (87%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/entity/EntityObjectMother.java (77%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java (94%) create mode 100644 generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/config/parameter/ParameterObjectMother.java (96%) create mode 100644 generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/GitIgnoreFileGeneratorTest.java create mode 100644 generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/LombokConfigFileGeneratorTest.java rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/PomFileGeneratorTest.java (76%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java (84%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java (88%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java (86%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java (95%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java (98%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/mapper/MapperBuilderTest.java (51%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java (74%) create mode 100644 generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/ComposedIdBuilderTest.java rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java (81%) create mode 100644 generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityRelationManagerTest.java rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java (79%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java (82%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java (64%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/service/ServiceBuilderTests.java (85%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/service/ServicesGeneratorTest.java (81%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java (77%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java (90%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java (86%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java (84%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java (92%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java (91%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java (88%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java (92%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java (95%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java (93%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java (88%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java (94%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java (96%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java (81%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java (69%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java (91%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java (91%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java (78%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java (75%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java (83%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java (92%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java (89%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/utils/CustomStringUtilsTest.java (97%) rename generator-core/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorcore/utils/ZipUtilsTest.java (96%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/App.java (82%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/core/config/ApigenCoreConfig.java (75%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/core/config/ApigenProperties.java (86%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java (88%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/core/config/DocumentationConfig.java (94%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java (84%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/core/exceptions/GeneratorRestException.java (84%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/core/web/ExceptionAdvice.java (74%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/uitls/OpenAPIZipUtils.java (77%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/web/ConfigController.java (68%) rename generator-rest/src/main/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/web/GeneratorController.java (79%) rename generator-rest/src/test/java/{net/cloudappi => org/apiaddicts/apitools}/apigen/generatorrest/AppTests.java (78%) diff --git a/README.md b/README.md index fe739d1..38def82 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- +

# OpenAPI Apigen Extension diff --git a/apigen-examples/other/subclassesTestApi.yaml b/apigen-examples/other/subclassesTestApi.yaml new file mode 100644 index 0000000..328b714 --- /dev/null +++ b/apigen-examples/other/subclassesTestApi.yaml @@ -0,0 +1,865 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /owners: + x-apigen-binding: + model: Owner + get: + operationId: getOwners + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owners" + + post: + operationId: postOwner + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newOwner" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /owners/{id}: + x-apigen-binding: + model: Owner + get: + operationId: getOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + put: + operationId: putOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updateOwner" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + delete: + operationId: deleteOwner + parameters: + - $ref: "#/components/parameters/string_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_owner" + + /tags: + x-apigen-binding: + model: Tag + get: + operationId: getTags + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_tags" + + + /pets: + x-apigen-binding: + model: Pet + get: + operationId: getPets + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + post: + operationId: postPet + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/newPet" + responses: + '201': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pets" + + /pets/{id}: + x-apigen-binding: + model: Pet + get: + operationId: getPet + parameters: + - $ref: "#/components/parameters/integer_id_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + put: + operationId: putPet + parameters: + - $ref: "#/components/parameters/integer_id_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/updatePet" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + delete: + operationId: deletePet + parameters: + - $ref: "#/components/parameters/integer_id_param" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /pets/search: + x-apigen-binding: + model: Pet + post: + operationId: searchPet + parameters: + - $ref: "#/components/parameters/init_param" + - $ref: "#/components/parameters/limit_param" + - $ref: "#/components/parameters/total_param" + - $ref: "#/components/parameters/select_param" + - $ref: "#/components/parameters/exclude_param" + - $ref: "#/components/parameters/expand_param" + - $ref: "#/components/parameters/order_by_param" + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/standard_search" + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/standard_response_pet" + + /status: + get: + operationId: getStatus + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other: + get: + operationId: getStatus2 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /status/other/nested/nested/end: + get: + operationId: getStatus3 + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/statusResponse' + + /entities-related: + get: + operationId: getEntities + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pets' + /entities-related/{id}: + get: + parameters: + - $ref: '#/components/parameters/integer_id_param' + operationId: getEntity + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pet' + /standard: + get: + operationId: getStandardCollection + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojos' + /standard/{id}: + get: + parameters: + - $ref: '#/components/parameters/integer_id_param' + operationId: getStandardElement + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/standard_response_pojo' + /reconfig: + post: + operationId: reconfig + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}: + post: + operationId: reconfig2 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/configResponse' + + /reconfig/{id}/nested/level: + post: + operationId: reconfig3 + parameters: + - $ref: '#/components/parameters/id' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/configRequest' + responses: + '200': + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/configResponse' + /nothing: + get: + operationId: nothing + responses: + '204': + description: Ok + +components: + parameters: + integer_id_param: + in: path + name: id + required: true + schema: + type: integer + format: int64 + string_id_param: + in: path + name: id + required: true + schema: + type: string + init_param: + in: query + name: $init + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + limit_param: + in: query + name: $limit + required: true + schema: + type: integer + format: int32 + minimum: 1 + default: 25 + total_param: + in: query + name: $total + required: true + schema: + type: boolean + default: false + select_param: + in: query + name: $select + required: false + schema: + type: array + items: + type: string + maxItems: 100 + exclude_param: + in: query + name: $exclude + required: false + schema: + type: array + items: + type: string + maxItems: 100 + expand_param: + in: query + name: $expand + required: false + schema: + type: array + items: + type: string + maxItems: 100 + order_by_param: + in: query + name: $orderby + required: false + schema: + type: array + items: + type: string + maxItems: 100 + id: + name: id + in: path + required: true + schema: + type: integer + format: int32 + minimum: 0 + default: 23 + schemas: + statusResponse: + type: object + properties: + value: + type: string + configRequest: + type: object + properties: + general: + type: object + properties: + name: + type: string + timeout: + type: integer + subgeneral: + type: object + properties: + name: + type: string + count: + type: integer + custom: + type: array + items: + type: object + properties: + key: + type: string + value: + type: string + configResponse: + type: object + properties: + values: + type: array + items: + type: object + properties: + name: + type: string + status: + type: boolean + standard_response_result: + properties: + result: + type: object + properties: + status: + type: boolean + http_code: + type: integer + errors: + type: array + items: + $ref: '#/components/schemas/standard_error' + info: + type: string + trace_id: + type: string + num_elements: + type: integer + required: + - status + - http_code + - trace_id + standard_error: + type: object + properties: + code: + type: integer + message: + type: string + standard_search: + type: object + properties: + operation: + type: string + enum: [AND, OR, GT, LT, GTEQ, LTEQ, EQ, NEQ, IN, BETWEEN, SUBSTRING, LIKE, ILIKE, NLIKE, REGEXP] + values: + type: array + items: + type: object + properties: + property: + type: string + type: + type: string + enum: [STRING, INTEGER, FLOAT, DATE, DATETIME, BOOLEAN] + value: + type: string + values: + type: array + items: + type: string + filter: + $ref: '#/components/schemas/standard_search' + + + standard_response_pets: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + standard_response_pet: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pet" + + standard_response_owners: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + owners: + type: array + items: + $ref: "#/components/schemas/owner" + + standard_response_owner: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/owner" + + standard_response_tags: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + tags: + type: array + items: + $ref: "#/components/schemas/tag" + + standard_response_tag: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/tag" + + standard_response_pojos: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + type: object + properties: + pojos: + type: array + items: + $ref: "#/components/schemas/pojo" + + standard_response_pojo: + allOf: + - $ref: "#/components/schemas/standard_response_result" + - type: object + properties: + data: + $ref: "#/components/schemas/pojo" + + pojo: + type: object + properties: + values: + type: array + items: + type: object + properties: + level: + type: number + format: int64 + name: + type: string + enabled: + type: boolean + pet: + x-apigen-mapping: + model: Pet + type: object + properties: + json_id: + type: integer + x-apigen-mapping: + field: id + name: + type: string + tags: + type: array + items: + $ref: "#/components/schemas/tag" + parent: + $ref: "#/components/schemas/pet" + children: + type: array + items: + $ref: "#/components/schemas/pet" + main_tag: + $ref: "#/components/schemas/tag" + owner: + $ref: "#/components/schemas/owner" + + tag: + x-apigen-mapping: + model: Tag + type: object + properties: + id: + type: integer + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + owner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + name: + type: string + pets: + type: array + items: + $ref: "#/components/schemas/pet" + + newPet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + minLength: 4 + owner: + type: string + x-apigen-mapping: # Required + model: Owner + field: owner.id + parent: + x-apigen-mapping: + model: Pet + type: object + properties: + id: + type: integer + tags: + type: array + items: + type: object + properties: + id: + type: integer + name: + type: string + maxItems: 10 + main_tag: + x-apigen-mapping: + field: mainTag + type: object + properties: + id: + type: integer + name: + type: string + required: + - name + + newOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + id: + type: string + x-apigen-mapping: # Optional + name: id + name: + type: string + x-apigen-mapping: # Optional + name: name + + updatePet: + x-apigen-mapping: + model: Pet + type: object + properties: + name: + type: string + + updateOwner: + x-apigen-mapping: + model: Owner + type: object + properties: + name: + type: string + + + # ------------------------------------------------- + + + x-apigen-models: + Owner: + relational-persistence: + table: owners + attributes: + - name: id + type: String + relational-persistence: + column: id + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: owner_id + + Pet: + relational-persistence: + table: pets + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + - name: owner + type: Owner + relational-persistence: + column: owner_id + - name: tags + type: Array + items-type: Tag + relational-persistence: + column: pet_id + foreign-column: tag_id + intermediate-table: pet_tags + owner: true + - name: parent + type: Pet + relational-persistence: + column: parent_id + - name: children + type: Array + items-type: Pet + relational-persistence: + foreign-column: parent_id + - name: mainTag + type: Tag + relational-persistence: + column: tag_id + + Tag: + relational-persistence: + table: tags + attributes: + - name: id + type: Long + relational-persistence: + column: id + primary-key: true + autogenerated: true + validations: + - type: NotNull + - name: name + type: String + relational-persistence: + column: name + validations: + - type: Size + min: 2 + max: 10 + - name: pets + type: Array + items-type: Pet + relational-persistence: + foreign-column: tag_id + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/relations/many-to-many.yaml b/apigen-examples/relations/many-to-many.yaml new file mode 100644 index 0000000..cd56b23 --- /dev/null +++ b/apigen-examples/relations/many-to-many.yaml @@ -0,0 +1,52 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /test: + get: + operationId: test + responses: + 200: + description: Ok + + + # ------------------------------------------------- +components: + x-apigen-models: + Category: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + - name: labels + type: Array + items-type: Label + relational-persistence: + column: category_id + foreign-column: label_id + intermediate-table: categories_labels + owner: true + + Label: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/relations/many-to-many__many-to-many.yaml b/apigen-examples/relations/many-to-many__many-to-many.yaml new file mode 100644 index 0000000..ab37855 --- /dev/null +++ b/apigen-examples/relations/many-to-many__many-to-many.yaml @@ -0,0 +1,59 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /test: + get: + operationId: test + responses: + 200: + description: Ok + + + # ------------------------------------------------- +components: + x-apigen-models: + Category: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + - name: labels + type: Array + items-type: Label + relational-persistence: + column: category_id + foreign-column: label_id + intermediate-table: categories_labels + owner: true + + Label: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + - name: categories + type: Array + items-type: Category + relational-persistence: + column: label_id + foreign-column: category_id + intermediate-table: categories_labels + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/relations/one-to-many.yaml b/apigen-examples/relations/one-to-many.yaml new file mode 100644 index 0000000..8580c91 --- /dev/null +++ b/apigen-examples/relations/one-to-many.yaml @@ -0,0 +1,48 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /test: + get: + operationId: test + responses: + 200: + description: Ok + + + # ------------------------------------------------- +components: + x-apigen-models: + Item: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + - name: category + type: Category + relational-persistence: + column: category_id + + Category: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/relations/one-to-many__many-to-one.yaml b/apigen-examples/relations/one-to-many__many-to-one.yaml new file mode 100644 index 0000000..6acc23e --- /dev/null +++ b/apigen-examples/relations/one-to-many__many-to-one.yaml @@ -0,0 +1,53 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /test: + get: + operationId: test + responses: + 200: + description: Ok + + + # ------------------------------------------------- +components: + x-apigen-models: + Item: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + - name: category + type: Category + relational-persistence: + column: category_id + + Category: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + - name: items + type: Array + items-type: Item + relational-persistence: + foreign-column: category_id + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/relations/one-to-one__one-to-one.yaml b/apigen-examples/relations/one-to-one__one-to-one.yaml new file mode 100644 index 0000000..032ab4e --- /dev/null +++ b/apigen-examples/relations/one-to-one__one-to-one.yaml @@ -0,0 +1,53 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Apigen Pets + +paths: + /test: + get: + operationId: test + responses: + 200: + description: Ok + + + # ------------------------------------------------- +components: + x-apigen-models: + Passport: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: code + type: String + - name: owner + type: Person + relational-persistence: + foreign-column: passport_id + + Person: + attributes: + - name: id + type: Long + relational-persistence: + primary-key: true + autogenerated: true + - name: name + type: String + - name: passport + type: Passport + relational-persistence: + column: passport_id + owner: true + +x-apigen-project: + name: name + description: description + version: 1.0.0 + java-properties: + group-id: the.group + artifact-id: app \ No newline at end of file diff --git a/apigen-examples/simple-id-models/api-test-reduced.yaml b/apigen-examples/simple-id-models/api-test-reduced.yaml index 59a68f9..95b5f64 100644 --- a/apigen-examples/simple-id-models/api-test-reduced.yaml +++ b/apigen-examples/simple-id-models/api-test-reduced.yaml @@ -674,7 +674,7 @@ components: type: object properties: id: - type: integer + type: string name: type: string pets: @@ -729,10 +729,6 @@ components: model: Owner type: object properties: - id: - type: string - x-apigen-mapping: # Optional - name: id name: type: string x-apigen-mapping: # Optional @@ -766,8 +762,6 @@ components: relational-persistence: primary-key: true autogenerated: true - validations: - - type: NotNull - name: name type: String - name: pets @@ -783,8 +777,6 @@ components: relational-persistence: primary-key: true autogenerated: true - validations: - - type: NotNull - name: name type: String - name: owner @@ -820,8 +812,6 @@ components: relational-persistence: primary-key: true autogenerated: true - validations: - - type: NotNull - name: name type: String validations: diff --git a/apigen-examples/simple-id-models/api-test.yaml b/apigen-examples/simple-id-models/api-test.yaml index 48fe4b7..c278a2a 100644 --- a/apigen-examples/simple-id-models/api-test.yaml +++ b/apigen-examples/simple-id-models/api-test.yaml @@ -729,10 +729,6 @@ components: model: Owner type: object properties: - id: - type: string - x-apigen-mapping: # Optional - name: id name: type: string x-apigen-mapping: # Optional @@ -769,8 +765,6 @@ components: column: id primary-key: true autogenerated: true - validations: - - type: NotNull - name: name type: String relational-persistence: @@ -791,8 +785,6 @@ components: column: id primary-key: true autogenerated: true - validations: - - type: NotNull - name: name type: String relational-persistence: @@ -833,8 +825,6 @@ components: column: id primary-key: true autogenerated: true - validations: - - type: NotNull - name: name type: String relational-persistence: diff --git a/archetype-core/pom.xml b/archetype-core/pom.xml index 84b245d..08f518e 100644 --- a/archetype-core/pom.xml +++ b/archetype-core/pom.xml @@ -5,15 +5,45 @@ 4.0.0 - net.cloudappi.apigen + org.apiaddicts.apitools.apigen apigen - 0.0.1-SNAPSHOT + 0.0.3-SNAPSHOT ../pom.xml archetype-core archetype-core ${revision} + https://github.com/apiaddicts/apigen + + + + GNU Lesser General Public License (LGPLV3+) + http://www.gnu.org/licenses/lgpl-3.0.html + + + + + Apiaddicts + https://apiaddicts.org + + + + scm:git:git://github.com/apiaddicts/apigen.git + scm:git:ssh://github.com:apiaddicts/apigen.git + https://github.com/apiaddicts/apigen/tree/master + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractRelationsManager.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractRelationsManager.java deleted file mode 100644 index 3870409..0000000 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractRelationsManager.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.cloudappi.apigen.archetypecore.core; - -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Set; - -public class AbstractRelationsManager { - - @Transactional(propagation = Propagation.MANDATORY) - public void createOrRetrieveRelations(E entity) { - // Override if required - } - - @Transactional(propagation = Propagation.MANDATORY) - public void updateRelations(E persistedEntity, E entity, Set fields) { - // Override if required - } -} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/PropType.java b/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/PropType.java deleted file mode 100644 index fe5aace..0000000 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/PropType.java +++ /dev/null @@ -1,73 +0,0 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.filter; - -import java.time.LocalDate; -import java.time.OffsetDateTime; -import java.util.ArrayList; -import java.util.List; - -public enum PropType { - STRING("STRING") { - @Override - public String convert(String value) { - return value; - } - }, - INTEGER("INTEGER") { - @Override - public Long convert(String value) { - if (value == null) return null; - return Long.parseLong(value); - } - }, - FLOAT("FLOAT") { - @Override - public Double convert(String value) { - if (value == null) return null; - return Double.parseDouble(value); - } - }, - DATE("DATE") { - @Override - public LocalDate convert(String value) { - if (value == null) return null; - return LocalDate.parse(value); - } - }, - DATETIME("DATETIME") { - @Override - public OffsetDateTime convert(String value) { - if (value == null) return null; - return OffsetDateTime.parse(value); - } - }, - BOOLEAN("BOOLEAN") { - @Override - public Boolean convert(String value) { - if (value == null) return null; - return Boolean.parseBoolean(value); - } - }; - - private final String value; - - PropType(final String value) { - this.value = value; - } - - @Override - public String toString() { - return this.value; - } - - public abstract T convert(String value); - - public List convert(List values) { - List convertedValues = new ArrayList<>(); - for (String v : values) { - T cast = this.convert(v); - convertedValues.add(cast); - } - return convertedValues; - } - -} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenApplication.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenApplication.java similarity index 71% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenApplication.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenApplication.java index c00a0bf..ef26c9e 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenApplication.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenApplication.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.archetypecore.autoconfigure; +package org.apiaddicts.apitools.apigen.archetypecore.autoconfigure; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepositoryImpl; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepositoryImpl; import org.springframework.context.annotation.Import; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenConfiguration.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenConfiguration.java similarity index 66% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenConfiguration.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenConfiguration.java index 372cad5..f2cb888 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenConfiguration.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenConfiguration.java @@ -1,15 +1,15 @@ -package net.cloudappi.apigen.archetypecore.autoconfigure; +package org.apiaddicts.apitools.apigen.archetypecore.autoconfigure; import com.fasterxml.jackson.databind.ObjectMapper; -import net.cloudappi.apigen.archetypecore.core.advice.ApigenControllerAdvice; -import net.cloudappi.apigen.archetypecore.core.errors.ApigenErrorManager; -import net.cloudappi.apigen.archetypecore.core.errors.DefaultApigenErrorManager; -import net.cloudappi.apigen.archetypecore.core.resource.ResourceNamingTranslator; -import net.cloudappi.apigen.archetypecore.core.resource.ResourceNamingTranslatorByReflection; -import net.cloudappi.apigen.archetypecore.interceptors.response.ApiResponseBodyAdvice; -import net.cloudappi.apigen.archetypecore.interceptors.update.CachingRequestBodyFilter; -import net.cloudappi.apigen.archetypecore.interceptors.update.UpdateRequestBodyAdvice; -import net.cloudappi.apigen.archetypecore.interceptors.WebConfig; +import org.apiaddicts.apitools.apigen.archetypecore.core.advice.ApigenControllerAdvice; +import org.apiaddicts.apitools.apigen.archetypecore.core.errors.ApigenErrorManager; +import org.apiaddicts.apitools.apigen.archetypecore.core.errors.DefaultApigenErrorManager; +import org.apiaddicts.apitools.apigen.archetypecore.core.resource.ResourceNamingTranslator; +import org.apiaddicts.apitools.apigen.archetypecore.core.resource.ResourceNamingTranslatorByReflection; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.response.ApiResponseBodyAdvice; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.update.CachingRequestBodyFilter; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.update.UpdateRequestBodyAdvice; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.WebConfig; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java similarity index 97% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java index eb41a31..fd91a17 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenDocumentationConfiguration.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.autoconfigure; +package org.apiaddicts.apitools.apigen.archetypecore.autoconfigure; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenProperties.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenProperties.java similarity index 85% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenProperties.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenProperties.java index b065b8f..f978ca2 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/autoconfigure/ApigenProperties.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/autoconfigure/ApigenProperties.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.archetypecore.autoconfigure; +package org.apiaddicts.apitools.apigen.archetypecore.autoconfigure; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.errors.ApigenError; +import org.apiaddicts.apitools.apigen.archetypecore.core.errors.ApigenError; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.HashMap; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudService.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractCrudService.java similarity index 91% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudService.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractCrudService.java index c0e3a57..63c3234 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudService.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractCrudService.java @@ -1,8 +1,9 @@ -package net.cloudappi.apigen.archetypecore.core; +package org.apiaddicts.apitools.apigen.archetypecore.core; + +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepository; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.EntityIdAlreadyInUseException; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; -import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; import org.springframework.lang.Nullable; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -12,6 +13,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -39,6 +41,10 @@ public E create(E entity) { preCreateBeforeManageRelations(entity); if (nonNull(relationsManager)) relationsManager.createOrRetrieveRelations(entity); preCreateAfterManageRelations(entity); + if(nonNull(entity.getId())) { + Optional existingId = getOne(entity.getId()); + if(existingId.isPresent()) throw new EntityIdAlreadyInUseException(entity.getId(),clazz); + } entity = save(entity); postCreate(entity); return entity; @@ -65,8 +71,7 @@ public E update(K id, E entity) { public E update(K id, E entity, Set fields) { Assert.notNull(id, "The argument id cannot be null."); Assert.notNull(entity, "The argument entity cannot be null."); - E persistedEntity = getOne(id) - .orElseThrow(() -> new EntityNotFoundException(id, clazz)); + E persistedEntity = safeGetOne(id); return update(persistedEntity, entity, fields); } @@ -160,7 +165,7 @@ protected E save(E entity) { @Transactional public void delete(K id) { Assert.notNull(id, "The argument id cannot be null."); - E persistedEntity = getOne(id).orElseThrow(() -> new EntityNotFoundException(id, clazz)); + E persistedEntity = safeGetOne(id); delete(persistedEntity); } diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractReadService.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractReadService.java similarity index 72% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractReadService.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractReadService.java index f23e45e..401905e 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/AbstractReadService.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractReadService.java @@ -1,12 +1,12 @@ -package net.cloudappi.apigen.archetypecore.core; +package org.apiaddicts.apitools.apigen.archetypecore.core; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearch; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.core.persistence.pagination.Pagination; -import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepository; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearch; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearchResult; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.pagination.Pagination; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.EntityNotFoundException; import org.springframework.core.GenericTypeResolver; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -37,6 +37,12 @@ public Optional getOne(K id) { return repository.findById(id); } + @Transactional(readOnly = true) + public E safeGetOne(K id) { + Assert.notNull(id, "The argument id cannot be null"); + return this.repository.findById(id).orElseThrow(() -> new EntityNotFoundException(id, clazz)); + } + @Transactional(readOnly = true) public List getAll(Iterable ids) { Assert.notNull(ids, "The argument ids cannot be null"); @@ -68,7 +74,7 @@ public E search(K id, List select, List exclude, List ex } @Transactional(readOnly = true) - public ApigenSearchResult search(List select, List exclude, List expand, Filter filter, List orderBy, Integer init, Integer limit, Boolean total) { + public ApigenSearchResult search(List select, List exclude, List expand, Filter filter, List orderBy, Integer init, Integer limit, Boolean total) { // NOSONAR Pagination pagination = null; if (init != null && limit != null) { pagination = new Pagination(init, limit); @@ -78,4 +84,5 @@ public ApigenSearchResult search(List select, List exclude, L search.setTotal(total); return repository.search(search); } + } diff --git a/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractRelationsManager.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractRelationsManager.java new file mode 100644 index 0000000..844ec26 --- /dev/null +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractRelationsManager.java @@ -0,0 +1,51 @@ +package org.apiaddicts.apitools.apigen.archetypecore.core; + +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrors; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrorsException; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.io.Serializable; +import java.util.Set; + +public class AbstractRelationsManager { + + @Transactional(propagation = Propagation.MANDATORY) + public void createOrRetrieveRelations(E entity) { + // Override if required + } + + @Transactional(propagation = Propagation.MANDATORY) + public void updateRelations(E persistedEntity, E entity, Set fields) { + // Override if required + } + + protected , K extends Serializable> T retrieve(T entity, AbstractReadService service, RelationalErrors errors) { + K id = entity.getId(); + T retrieved = (id == null) ? null : service.getOne(id).orElse(null); + if (retrieved == null) errors.register(entity.getClass(), id); + return retrieved; + } + + protected > T create(T entity, AbstractCrudService service) { + return service.create(entity); + } + + protected > void delete(T entity, AbstractCrudService service) { + service.delete(entity); + } + + protected , K extends Serializable> T createOrRetrieve(T entity, AbstractCrudService service, RelationalErrors errors) { + if (entity.isReference()) { + return retrieve(entity, service, errors); + } else { + try { + return create(entity, service); + } catch (RelationalErrorsException e) { + errors.merge(e.getRelationalErrors()); + return null; + } + } + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/ApigenMapper.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/ApigenMapper.java similarity index 57% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/ApigenMapper.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/ApigenMapper.java index 3a3fe9e..f5adf0f 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/ApigenMapper.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/ApigenMapper.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core; +package org.apiaddicts.apitools.apigen.archetypecore.core; public interface ApigenMapper { void updateBasicData(E source, E target); diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdvice.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/ApigenControllerAdvice.java similarity index 90% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdvice.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/ApigenControllerAdvice.java index f537f98..934a752 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdvice.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/ApigenControllerAdvice.java @@ -1,20 +1,21 @@ -package net.cloudappi.apigen.archetypecore.core.advice; +package org.apiaddicts.apitools.apigen.archetypecore.core.advice; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.core.errors.ApigenErrorManager; -import net.cloudappi.apigen.archetypecore.core.errors.DefaultApigenError; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; -import net.cloudappi.apigen.archetypecore.exceptions.*; +import org.apiaddicts.apitools.apigen.archetypecore.core.errors.ApigenErrorManager; +import org.apiaddicts.apitools.apigen.archetypecore.core.errors.DefaultApigenError; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.result.ApiError; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.*; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.FieldError; import org.springframework.web.HttpMediaTypeNotAcceptableException; +import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingServletRequestParameterException; @@ -121,6 +122,8 @@ private DefaultApigenError getError(String code, Object[] args) { switch (code) { case "NotNull": return DefaultApigenError.VALIDATION_NOT_NULL; + case "Null": + return DefaultApigenError.VALIDATION_NULL; case "Email": return DefaultApigenError.VALIDATION_EMAIL; case "NotEmpty": @@ -222,6 +225,14 @@ public ApiResponse entityNotFound(EntityNotFoundException ex) { ApiError error = errorManager.getError(DefaultApigenError.ELEMENT_NOT_FOUND, ex.getId().toString(), ex.getClazz().getSimpleName()); return new ApiResponse().withResultErrors(errors(error)); } + + @ResponseBody + @ExceptionHandler(EntityIdAlreadyInUseException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse existingId(EntityIdAlreadyInUseException ex) { + ApiError error = errorManager.getError(DefaultApigenError.EXISTING_ID_ERROR, ex.getId().toString(), ex.getClazz().getSimpleName()); + return new ApiResponse().withResultErrors(errors(error)); + } @ResponseBody @ExceptionHandler(RelationalErrorsException.class) @@ -250,8 +261,16 @@ public ResponseEntity customException(CustomApigenException ex) { @ResponseBody @ExceptionHandler(HttpMediaTypeNotAcceptableException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiResponse mediaTypeNotSupported(HttpMediaTypeNotAcceptableException ex) { - ApiError error = errorManager.getError(DefaultApigenError.UNSUPPORTED_FORMAT, null, ex.getSupportedMediaTypes()); + public ApiResponse acceptMediaTypeNotSupported(HttpMediaTypeNotAcceptableException ex) { + ApiError error = errorManager.getError(DefaultApigenError.UNSUPPORTED_ACCEPT_FORMAT, null, ex.getSupportedMediaTypes()); + return new ApiResponse().withResultErrors(errors(error)); + } + + @ResponseBody + @ExceptionHandler(HttpMediaTypeNotSupportedException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse contentTypeMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex) { + ApiError error = errorManager.getError(DefaultApigenError.UNSUPPORTED_CONTENT_TYPE_FORMAT, null, ex.getContentType().toString()); return new ApiResponse().withResultErrors(errors(error)); } @@ -371,7 +390,7 @@ private Map getDictionary(Class clazz) { fieldClazz = (Class) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; } Package pkg = fieldClazz.getPackage(); - if (pkg != null && !pkg.getName().startsWith("java") && !fieldClazz.equals(Filter.class)) { + if (pkg != null && !pkg.getName().startsWith("java") && !fieldClazz.equals(Filter.class) && !fieldClazz.isEnum()) { Map nestedNames = getDictionary(fieldClazz); String resName = resourceName; nestedNames.forEach((key, value) -> levelDictionary.put(javaName + "." + key, resName + "." + value)); diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenError.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/ApigenError.java similarity index 81% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenError.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/ApigenError.java index e99f599..51e1584 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenError.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/ApigenError.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.errors; +package org.apiaddicts.apitools.apigen.archetypecore.core.errors; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenErrorManager.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/ApigenErrorManager.java similarity index 65% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenErrorManager.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/ApigenErrorManager.java index f3a7f7c..b10454b 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/ApigenErrorManager.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/ApigenErrorManager.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.archetypecore.core.errors; +package org.apiaddicts.apitools.apigen.archetypecore.core.errors; -import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.result.ApiError; public interface ApigenErrorManager { ApiError getError(DefaultApigenError key); diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenError.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/DefaultApigenError.java similarity index 88% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenError.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/DefaultApigenError.java index 11c6708..6d72279 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenError.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/DefaultApigenError.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.errors; +package org.apiaddicts.apitools.apigen.archetypecore.core.errors; import lombok.Getter; @@ -21,6 +21,7 @@ public enum DefaultApigenError { VALIDATION_FUTURE_DATE(1012, "Property ''{0}'' must be a future"), VALIDATION_FUTURE_NOW_DATE(1013, "Property ''{0}'' must be a future or present"), VALIDATION_DECIMAL(1014, "Property ''{0}'' must be a decimal with ''{1}'' integer digits and ''{2}'' decimal digits"), + VALIDATION_NULL(1015, "Property ''{0}'' must be null"), VALIDATION_ERROR(1099, "Property ''{0}'' must be valid"), // 110x : Property path error @@ -37,14 +38,16 @@ public enum DefaultApigenError { QUERY_PARAM_REQUIRED(1110, "Query parameter ''{0}'' is required"), INVALID_REGEX_EXPRESSION(1111, "Invalid regex expression ''{0}''"), ILLEGAL_ARGUMENT(1112, "Illegal argument: {0}"), + EXISTING_ID_ERROR(1113,"Element with id ''{0}'' of type ''{1}'' already exists"), // 12xx : General errors METHOD_NOT_IMPLEMENTED(1200, "Method ''{0}'' not implemented"), PATH_NOT_IMPLEMENTED(1201, "Path ''{0}'' not implemented"), EMPTY_REQUEST_BODY(1202, "Request body required"), - UNSUPPORTED_FORMAT(1203, "Format not supported, supported formats: ''{1}''"), + UNSUPPORTED_ACCEPT_FORMAT(1203, "Accept format not supported, supported formats: ''{1}''"), UNSUPPORTED_VALUE(1204, "Unsupported value ''{0}'', accepted values: ''{1}''"), + UNSUPPORTED_CONTENT_TYPE_FORMAT(1205, "Content-type format not supported: ''{1}''"), // 13xx : Unexpected errors UNEXPECTED_ERROR(1300, "Unexpected error"); diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java similarity index 88% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java index 466da1b..cda9105 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/errors/DefaultApigenErrorManager.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.archetypecore.core.errors; +package org.apiaddicts.apitools.apigen.archetypecore.core.errors; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenProperties; -import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; +import org.apiaddicts.apitools.apigen.archetypecore.autoconfigure.ApigenProperties; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.result.ApiError; import java.text.MessageFormat; import java.util.*; diff --git a/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractAudited.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractAudited.java new file mode 100644 index 0000000..c56f294 --- /dev/null +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractAudited.java @@ -0,0 +1,27 @@ +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; + +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import java.io.Serializable; + +@Getter +@Setter +@MappedSuperclass +public abstract class ApigenAbstractAudited extends ApigenAbstractTimeAudited { // NOSONAR + + @CreatedBy + @ManyToOne + @JoinColumn(name = "created_by") + private U createdBy; + + @ManyToOne + @JoinColumn(name = "last_updated_by") + @LastModifiedBy + private U lastUpdatedBy; +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java similarity index 82% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java index 8ef751a..e1b63f2 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractPersistable.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence; import org.springframework.data.domain.Persistable; import org.springframework.data.util.ProxyUtils; @@ -12,9 +12,6 @@ public abstract class ApigenAbstractPersistable implements Persistable { private static final long serialVersionUID = -5554308939380869754L; - public ApigenAbstractPersistable() { - } - @Nullable @Transient public abstract K getId(); @@ -43,13 +40,15 @@ public boolean equals(Object obj) { return false; } else { ApigenAbstractPersistable that = (ApigenAbstractPersistable) obj; - return null != this.getId() && this.getId().equals(that.getId()); + K id = this.getId(); + return null != id && id.equals(that.getId()); } } public int hashCode() { int hashCode = 17; - if (this.getId() != null) hashCode += this.getId().hashCode() * 31; + K id = this.getId(); + if (id != null) hashCode += id.hashCode() * 31; return hashCode; } } diff --git a/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractTimeAudited.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractTimeAudited.java new file mode 100644 index 0000000..9861654 --- /dev/null +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenAbstractTimeAudited.java @@ -0,0 +1,26 @@ +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; + +import javax.persistence.Column; +import javax.persistence.MappedSuperclass; +import java.io.Serializable; +import java.time.Instant; + +@Getter +@Setter +@MappedSuperclass +public abstract class ApigenAbstractTimeAudited extends ApigenAbstractPersistable { // NOSONAR + private static final long serialVersionUID = -5554308939380869754L; + + @CreatedDate + @Column(name = "created_at") + protected Instant createdAt; + + @LastModifiedDate + @Column(name = "last_updated_at") + protected Instant lastUpdatedAt; +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepository.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepository.java similarity index 84% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepository.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepository.java index 54c37be..eaf1096 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepository.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepository.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.NoRepositoryBean; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java similarity index 82% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java index 173f5a0..803c331 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepositoryImpl.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.archetypecore.core.persistence; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence; -import net.cloudappi.apigen.archetypecore.core.persistence.executor.ApigenSearchExecutor; -import net.cloudappi.apigen.archetypecore.core.persistence.executor.EntityInfo; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor.ApigenSearchExecutor; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor.EntityInfo; import org.springframework.data.jpa.repository.support.JpaEntityInformation; import org.springframework.data.jpa.repository.support.SimpleJpaRepository; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearch.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenSearch.java similarity index 76% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearch.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenSearch.java index 93e15af..605cbba 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearch.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenSearch.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.archetypecore.core.persistence; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.core.persistence.pagination.Pagination; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.pagination.Pagination; import java.util.List; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearchResult.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenSearchResult.java similarity index 83% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearchResult.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenSearchResult.java index 6bc61eb..0011953 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenSearchResult.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenSearchResult.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence; import lombok.Data; diff --git a/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenConverter.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenConverter.java new file mode 100644 index 0000000..9592045 --- /dev/null +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenConverter.java @@ -0,0 +1,6 @@ +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor; + +@FunctionalInterface +public interface ApigenConverter { + Comparable convert(String value); +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java similarity index 83% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java index 2b724d6..6e8d1e8 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutor.java @@ -1,29 +1,25 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.executor; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Value; -import net.cloudappi.apigen.archetypecore.core.persistence.pagination.Pagination; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearchResult; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.FilterOperation; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Value; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.pagination.Pagination; import javax.persistence.EntityManager; import javax.persistence.Tuple; import javax.persistence.TypedQuery; import javax.persistence.criteria.*; import java.io.Serializable; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.time.Instant; -import java.time.OffsetDateTime; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.util.Objects.isNull; -import static net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation.AND; -import static net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation.OR; -import static net.cloudappi.apigen.archetypecore.core.persistence.functions.ApigenFunctionsMetadataBuilderInitializer.REGEXP_FUNCTION; +import static org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.FilterOperation.AND; +import static org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.FilterOperation.OR; +import static org.apiaddicts.apitools.apigen.archetypecore.core.persistence.functions.ApigenFunctionsMetadataBuilderInitializer.REGEXP_FUNCTION; @Slf4j public class ApigenSearchExecutor { @@ -64,7 +60,7 @@ public Optional searchById(K id, List sel return Optional.of(found.get(0)); } - public ApigenSearchResult search(List select, List exclude, List orderBy, List expand, Filter filter, Pagination pagination, boolean total, Class clazz) { + public ApigenSearchResult search(List select, List exclude, List orderBy, List expand, Filter filter, Pagination pagination, boolean total, Class clazz) { // NOSONAR CriteriaBuilder builder = em.getCriteriaBuilder(); @@ -72,6 +68,11 @@ public ApigenSearchResult search(List select, List exclud Root root = query.from(clazz); + String idAttribute = entityData.getIdAttribute(clazz); + if(orderBy == null || orderBy.isEmpty()) { + orderBy = new LinkedList<>(Collections.singletonList(idAttribute)); + } + Map joins = join(expand, root); List enhancedFields = getFields(select, exclude, root, joins); List> selections = select(enhancedFields, root, joins); @@ -90,7 +91,7 @@ public ApigenSearchResult search(List select, List exclud return new ApigenSearchResult<>(new TupleMapper(enhancedFields, entityData, clazz).map(result), count); } - private void addPaginationPredicate(CriteriaQuery query, Root root, List orderBy, List expand, Filter filter, Pagination pagination, Class clazz, CriteriaBuilder builder) { + private void addPaginationPredicate(CriteriaQuery query, Root root, List orderBy, List expand, Filter filter, Pagination pagination, Class clazz, CriteriaBuilder builder) { // NOSONAR List ids = getMatchingIds(orderBy, expand, filter, pagination, clazz, builder); String idAttribute = entityData.getIdAttribute(clazz); Path path = root.get(idAttribute); @@ -105,7 +106,7 @@ private List getMatchingIds(List orderBy, List expand, F String idAttribute = entityData.getIdAttribute(clazz); List allFields = new LinkedList<>(orderBy); if (!allFields.contains(idAttribute)) allFields.add(idAttribute); - List> orderSelect = orderFields(allFields, builder, root, joins); + List> orderSelect = orderFields(allFields, root, joins); query.multiselect(orderSelect); query.distinct(true); addPredicate(query, filter, root, joins, builder); @@ -125,8 +126,7 @@ private Long count(Class clazz, List expand, Filter filter, CriteriaB CriteriaQuery query = builder.createQuery(Long.class); Root root = query.from(clazz); Map joins = join(expand, root); - query.select(builder.count(root)); - query.distinct(true); + query.select(builder.countDistinct(root)); addPredicate(query, filter, root, joins, builder); return em.createQuery(query).getSingleResult(); } @@ -179,14 +179,12 @@ private List> select(List fields, Root root, Map getPath(f, root, joins)).collect(Collectors.toList()); } - private List> orderFields(List orderBy, CriteriaBuilder builder, Root root, Map joins) { + private List> orderFields(List orderBy, Root root, Map joins) { if (orderBy == null || orderBy.isEmpty()) return Collections.emptyList(); return orderBy.stream().map(o -> { - boolean asc = true; if (o.charAt(0) == '+') { o = o.substring(1); } else if (o.charAt(0) == '-') { - asc = false; o = o.substring(1); } return getPath(o, root, joins); @@ -281,19 +279,11 @@ private Predicate getPredicateOfConditions(Filter filter, CriteriaBuilder builde @SuppressWarnings("unchecked") private Predicate getSingleValueCondition(FilterOperation operation, Value rootValue, Expression expression, CriteriaBuilder builder) { if (operation != FilterOperation.EQ && operation != FilterOperation.NEQ && isNull(rootValue.getValue())) throw new IllegalArgumentException(operation + " requires a single value"); - String valueStr = rootValue.getValue(); - Comparable value = rootValue.getType().convert(rootValue.getValue()); + String valueStr = rootValue.getValue(); Class attributeType = expression.getJavaType(); - if (entityData.isEmbeddedId(attributeType)) value = getComposedId(attributeType, rootValue.getValue()); - if (attributeType.isEnum()) { - value = getEnum(attributeType, rootValue.getValue()); - } - - if (attributeType.equals(Instant.class) && value instanceof OffsetDateTime) { - value = ((OffsetDateTime) value).toInstant(); - } + Comparable value = ApigenSearchExecutorTypeConverters.convert(attributeType, valueStr); switch (operation) { case GT: @@ -330,14 +320,10 @@ private Predicate getListValueCondition(FilterOperation operation, Value rootVal if (isNull(rootValue.getValues()) || rootValue.getValues().isEmpty()) throw new IllegalArgumentException(operation + " requires a list of values"); - List values = rootValue.getType().convert(rootValue.getValues()); - Class attributeType = expression.getJavaType(); - if (entityData.isEmbeddedId(attributeType)) values = rootValue.getValues().stream().map(v -> getComposedId(attributeType, v)).collect(Collectors.toList()); - if (attributeType.isEnum()) { - values = rootValue.getValues().stream().map(v -> getEnum(attributeType, v)).collect(Collectors.toList()); - } + List values = rootValue.getValues().stream() + .map(v -> ApigenSearchExecutorTypeConverters.convert(attributeType, v)).collect(Collectors.toList()); switch (operation) { case IN: @@ -351,19 +337,4 @@ private Predicate getListValueCondition(FilterOperation operation, Value rootVal throw new IllegalArgumentException("Operation " + operation + " not supported"); } } - - private Comparable getComposedId(Class clazz, String value) { - try { - Method m = clazz.getMethod("from", String.class); - Comparable composedId = (Comparable) m.invoke(null, (Object) value); - return composedId; - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - log.error("Error: ", e); - return null; - } - } - - private Comparable getEnum(Class clazz, String value) { - return Enum.valueOf(clazz, value); - } } diff --git a/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutorTypeConverters.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutorTypeConverters.java new file mode 100644 index 0000000..c38ee88 --- /dev/null +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutorTypeConverters.java @@ -0,0 +1,64 @@ +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor; + +import lombok.extern.slf4j.Slf4j; + +import javax.persistence.Embeddable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +public class ApigenSearchExecutorTypeConverters { + + private static ConcurrentHashMap converters = new ConcurrentHashMap<>(); + + static { + register(String.class, s -> s); + register(Integer.class, s -> s == null ? null : Integer.parseInt(s)); + register(Long.class, s -> s == null ? null : Long.parseLong(s)); + register(Float.class, s -> s == null ? null : Float.parseFloat(s)); + register(Double.class, s -> s == null ? null : Double.parseDouble(s)); + register(LocalDate.class, s -> s == null ? null : LocalDate.parse(s)); + register(OffsetDateTime.class, s -> s == null ? null : OffsetDateTime.parse(s)); + register(Instant.class, s -> s == null ? null : OffsetDateTime.parse(s).toInstant()); + register(Boolean.class, s -> s == null ? null : Boolean.parseBoolean(s)); + register(BigInteger.class, s -> s == null? null : new BigInteger(s)); + register(BigDecimal.class, s -> s == null? null : new BigDecimal(s)); + } + + private ApigenSearchExecutorTypeConverters() { + // Intentional blank + } + + + public static void register(Class clazz, ApigenConverter converter) { + converters.put(clazz, converter); + } + + public static Comparable convert(Class clazz, String value) { + if (clazz.isEnum()) { + return Enum.valueOf(clazz, value); + } + if (clazz.isAnnotationPresent(Embeddable.class)) { + return convertEmbedded(clazz, value); + } + ApigenConverter converter = converters.get(clazz); + if (converter == null) throw new IllegalArgumentException("No converter found for type: " + clazz); + return converter.convert(value); + } + + private static Comparable convertEmbedded(Class clazz, String value) { + try { + Method m = clazz.getMethod("from", String.class); + return (Comparable) m.invoke(null, (Object) value); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + log.error("Error: ", e); + return null; + } + } +} \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/AttributeInfo.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/AttributeInfo.java similarity index 78% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/AttributeInfo.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/AttributeInfo.java index b60c082..7a8864c 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/AttributeInfo.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/AttributeInfo.java @@ -1,4 +1,6 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.executor; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor; + +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; import java.util.List; import java.util.Set; @@ -15,7 +17,7 @@ public class AttributeInfo { AttributeInfo(Class type, Class itemType, boolean isPrimaryKey, boolean isEmbedded) { this.type = type; this.itemType = itemType; - isBasic = itemType.equals(type) && type.getCanonicalName().startsWith("java"); + isBasic = itemType.equals(type) && !ApigenAbstractPersistable.class.isAssignableFrom(type); this.isPrimaryKey = isPrimaryKey; this.isEmbedded = isEmbedded; this.isEnum = itemType.equals(type) && Enum.class.isAssignableFrom(type); diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityInfo.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/EntityInfo.java similarity index 92% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityInfo.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/EntityInfo.java index 8e7ab9c..739f319 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityInfo.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/EntityInfo.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.executor; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor; import org.springframework.stereotype.Component; @@ -13,21 +13,20 @@ @Component public class EntityInfo { - private static EntityInfo INSTANCE = null; + private static EntityInfo instance = null; private Map> entityAttributesInfo = new HashMap<>(); private Map entityId = new HashMap<>(); - private Set embeddedIds = new HashSet<>(); private EntityInfo(EntityManager em) { discoverEntities(em); } public static synchronized EntityInfo getInstance(EntityManager em) { - if (INSTANCE == null) { - INSTANCE = new EntityInfo(em); + if (instance == null) { + instance = new EntityInfo(em); } - return INSTANCE; + return instance; } private void discoverEntities(EntityManager em) { @@ -53,7 +52,6 @@ private void discoverEntities(EntityManager em) { isEmbedded = true; } params.put(name, new AttributeInfo(type, itemType, isId, isEmbedded)); - if (isId && isEmbedded) embeddedIds.add(itemType); } } } @@ -145,7 +143,4 @@ public List getBasicAttributes(Class entityClass, String path) { .collect(Collectors.toList()); } - public boolean isEmbeddedId(Class idClass) { - return embeddedIds.contains(idClass); - } } \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityLookup.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/EntityLookup.java similarity index 80% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityLookup.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/EntityLookup.java index c6a5c5f..e225e22 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/EntityLookup.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/EntityLookup.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.executor; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; import java.io.Serializable; import java.util.HashMap; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/TupleMapper.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/TupleMapper.java similarity index 97% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/TupleMapper.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/TupleMapper.java index 104e7e4..32b4bae 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/executor/TupleMapper.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/TupleMapper.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.executor; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; import org.springframework.util.StringUtils; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Filter.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/Filter.java similarity index 79% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Filter.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/Filter.java index 426d7a0..6e8360a 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Filter.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/Filter.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.filter; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter; import lombok.Data; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/FilterOperation.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/FilterOperation.java similarity index 89% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/FilterOperation.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/FilterOperation.java index 05f04b8..dd561fb 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/FilterOperation.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/FilterOperation.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.filter; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter; public enum FilterOperation { AND("AND", false), diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Value.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/Value.java similarity index 79% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Value.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/Value.java index 89643e3..f9c2ebc 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/filter/Value.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/filter/Value.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.filter; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; @@ -13,8 +13,7 @@ @Data public class Value { private String property; - private PropType type; - private String value; + private String value; // NOSONAR private List values; @Valid private Filter filter; @@ -30,7 +29,7 @@ private boolean isFilter() { } private boolean isExpression() { - return nonNull(property) && nonNull(type) && hasOnlyOneValue(); + return nonNull(property) && hasOnlyOneValue(); } private boolean hasOnlyOneValue() { diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java similarity index 97% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java index 5e6765e..964f1cc 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/functions/ApigenFunctionsMetadataBuilderInitializer.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.functions; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.functions; import ch.qos.logback.core.db.dialect.OracleDialect; import ch.qos.logback.core.db.dialect.PostgreSQLDialect; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/pagination/Pagination.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/pagination/Pagination.java similarity index 70% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/pagination/Pagination.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/pagination/Pagination.java index bebb016..a8e25be 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/persistence/pagination/Pagination.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/pagination/Pagination.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.pagination; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.pagination; import lombok.Data; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ApigenEntityOutResource.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ApigenEntityOutResource.java similarity index 69% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ApigenEntityOutResource.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ApigenEntityOutResource.java index 44b7798..c7f432b 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ApigenEntityOutResource.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ApigenEntityOutResource.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.resource; +package org.apiaddicts.apitools.apigen.archetypecore.core.resource; import java.lang.annotation.*; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/FilterResource.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/FilterResource.java similarity index 52% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/FilterResource.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/FilterResource.java index 49cf1a6..b89f13e 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/FilterResource.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/FilterResource.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.archetypecore.core.resource; +package org.apiaddicts.apitools.apigen.archetypecore.core.resource; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; import javax.validation.Valid; import javax.validation.constraints.NotNull; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslator.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslator.java similarity index 76% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslator.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslator.java index 13d882b..43b637a 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslator.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslator.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.archetypecore.core.resource; +package org.apiaddicts.apitools.apigen.archetypecore.core.resource; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; import java.util.List; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java similarity index 97% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java index abafde1..38cbeeb 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflection.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.archetypecore.core.resource; +package org.apiaddicts.apitools.apigen.archetypecore.core.resource; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.exceptions.InvalidPropertyPath; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.InvalidPropertyPath; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import org.reflections.scanners.TypeAnnotationsScanner; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponse.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/ApiResponse.java similarity index 85% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponse.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/ApiResponse.java index 703176a..9a53266 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponse.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/ApiResponse.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.archetypecore.core.responses; +package org.apiaddicts.apitools.apigen.archetypecore.core.responses; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.responses.metadata.Metadata; -import net.cloudappi.apigen.archetypecore.core.responses.metadata.Paging; -import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; -import net.cloudappi.apigen.archetypecore.core.responses.result.ApiResult; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.metadata.Metadata; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.metadata.Paging; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.result.ApiError; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.result.ApiResult; import org.springframework.util.Assert; import java.util.List; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/content/ApiListResponseContent.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/content/ApiListResponseContent.java similarity index 79% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/content/ApiListResponseContent.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/content/ApiListResponseContent.java index 11f306e..83e6fb9 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/content/ApiListResponseContent.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/content/ApiListResponseContent.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.responses.content; +package org.apiaddicts.apitools.apigen.archetypecore.core.responses.content; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Links.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Links.java similarity index 87% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Links.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Links.java index d55793b..95d0719 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Links.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Links.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.responses.metadata; +package org.apiaddicts.apitools.apigen.archetypecore.core.responses.metadata; import lombok.Data; import org.springframework.util.LinkedMultiValueMap; @@ -28,7 +28,7 @@ public static Links buildLinksMetadata(Integer init, Integer limit, Long total, String query = getUrl(uriComponents); Links links = new Links(); - long nextInit = init + limit; + long nextInit = (long) init + limit; uriComponents = updateQueryParams(uriComponents, baseLink, String.valueOf(nextInit)); links.next = new Link(getUrl(uriComponents)); @@ -59,10 +59,12 @@ public static Links buildLinksMetadata(Integer init, Integer limit, Long total, } private static String getUrl(UriComponents uriComponents) { - if(uriComponents.getQuery() == null || uriComponents.getQuery().isEmpty()){ - return uriComponents.getPath(); + String query = uriComponents.getQuery(); + String path = uriComponents.getPath(); + if(query == null || query.isEmpty()){ + return path; }else{ - return uriComponents.getPath() + "?" + uriComponents.getQuery(); + return path + "?" + query; } } diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Metadata.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Metadata.java similarity index 50% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Metadata.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Metadata.java index a3ee66a..48fd907 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Metadata.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Metadata.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.responses.metadata; +package org.apiaddicts.apitools.apigen.archetypecore.core.responses.metadata; import lombok.Data; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Paging.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Paging.java similarity index 94% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Paging.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Paging.java index 2a17524..aa574d1 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/metadata/Paging.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/metadata/Paging.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.responses.metadata; +package org.apiaddicts.apitools.apigen.archetypecore.core.responses.metadata; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiError.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/result/ApiError.java similarity index 76% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiError.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/result/ApiError.java index 3b58b8d..e3473cb 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiError.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/result/ApiError.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.responses.result; +package org.apiaddicts.apitools.apigen.archetypecore.core.responses.result; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiResult.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/result/ApiResult.java similarity index 86% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiResult.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/result/ApiResult.java index ed76dbb..9609059 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/core/responses/result/ApiResult.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/result/ApiResult.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.responses.result; +package org.apiaddicts.apitools.apigen.archetypecore.core.responses.result; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/CustomApigenException.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/CustomApigenException.java similarity index 95% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/CustomApigenException.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/CustomApigenException.java index aae2143..bbec21f 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/CustomApigenException.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/CustomApigenException.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.exceptions; +package org.apiaddicts.apitools.apigen.archetypecore.exceptions; import lombok.Getter; import org.springframework.http.HttpStatus; diff --git a/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/EntityIdAlreadyInUseException.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/EntityIdAlreadyInUseException.java new file mode 100644 index 0000000..ccd2456 --- /dev/null +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/EntityIdAlreadyInUseException.java @@ -0,0 +1,15 @@ +package org.apiaddicts.apitools.apigen.archetypecore.exceptions; + +import java.io.Serializable; +import lombok.Getter; + +@Getter +public class EntityIdAlreadyInUseException extends RuntimeException { + private Serializable id; + private Class clazz; + + public EntityIdAlreadyInUseException(Serializable id, Class clazz) { + this.id = id; + this.clazz = clazz; + } +} diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/EntityNotFoundException.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/EntityNotFoundException.java similarity index 81% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/EntityNotFoundException.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/EntityNotFoundException.java index 76ebdb1..cbf6904 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/EntityNotFoundException.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/EntityNotFoundException.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.exceptions; +package org.apiaddicts.apitools.apigen.archetypecore.exceptions; import lombok.Getter; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/InvalidPropertyPath.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/InvalidPropertyPath.java similarity index 92% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/InvalidPropertyPath.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/InvalidPropertyPath.java index bb3e9f1..7186127 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/InvalidPropertyPath.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/InvalidPropertyPath.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.exceptions; +package org.apiaddicts.apitools.apigen.archetypecore.exceptions; import lombok.Getter; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/NotImplementedException.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/NotImplementedException.java similarity index 69% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/NotImplementedException.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/NotImplementedException.java index ffdeed8..890e22d 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/NotImplementedException.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/NotImplementedException.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.exceptions; +package org.apiaddicts.apitools.apigen.archetypecore.exceptions; public class NotImplementedException extends RuntimeException { diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrors.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/RelationalErrors.java similarity index 95% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrors.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/RelationalErrors.java index a40740e..db6bdce 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrors.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/RelationalErrors.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.exceptions; +package org.apiaddicts.apitools.apigen.archetypecore.exceptions; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrorsException.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/RelationalErrorsException.java similarity index 84% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrorsException.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/RelationalErrorsException.java index 2ff3d10..d071f18 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/exceptions/RelationalErrorsException.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/exceptions/RelationalErrorsException.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.exceptions; +package org.apiaddicts.apitools.apigen.archetypecore.exceptions; public class RelationalErrorsException extends RuntimeException { private final RelationalErrors relationalErrors; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/ApigenContext.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ApigenContext.java similarity index 92% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/ApigenContext.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ApigenContext.java index 6c3c437..dd9b3f7 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/ApigenContext.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ApigenContext.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.interceptors; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/WebConfig.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/WebConfig.java similarity index 83% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/WebConfig.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/WebConfig.java index f775b9e..7110322 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/WebConfig.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/WebConfig.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.archetypecore.interceptors; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenProperties; -import net.cloudappi.apigen.archetypecore.interceptors.expand.ExpandPathInterceptor; -import net.cloudappi.apigen.archetypecore.interceptors.expand.ExpandAnnotationInterceptor; -import net.cloudappi.apigen.archetypecore.interceptors.trace.TraceIdInterceptor; +import org.apiaddicts.apitools.apigen.archetypecore.autoconfigure.ApigenProperties; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.expand.ExpandPathInterceptor; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.expand.ExpandAnnotationInterceptor; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.trace.TraceIdInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ApigenExpand.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ApigenExpand.java similarity index 83% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ApigenExpand.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ApigenExpand.java index 9befd76..066aca3 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ApigenExpand.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ApigenExpand.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.interceptors.expand; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.expand; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java similarity index 95% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java index e530a8c..e38eef3 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandAnnotationInterceptor.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.interceptors.expand; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.expand; import lombok.extern.slf4j.Slf4j; import org.springframework.web.method.HandlerMethod; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java similarity index 94% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java index 0d60e57..6b8326b 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandInterceptor.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.archetypecore.interceptors.expand; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.expand; -import net.cloudappi.apigen.archetypecore.exceptions.InvalidPropertyPath; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.InvalidPropertyPath; import org.springframework.util.StringUtils; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java similarity index 59% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java index e54e1c4..d01f5fb 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/expand/ExpandPathInterceptor.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.interceptors.expand; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.expand; import lombok.extern.slf4j.Slf4j; import org.springframework.web.method.HandlerMethod; @@ -23,17 +23,19 @@ public ExpandPathInterceptor(Integer maxLevel, Set allowed, Set @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + if (!isAnnotated(handler)) { + validate(request, allowed, excluded, maxLevel); + } + return true; + } - if (handler instanceof HandlerMethod) { - - HandlerMethod handlerMethod = (HandlerMethod) handler; - Method method = handlerMethod.getMethod(); - ApigenExpand expandAnnotation = method.getAnnotation(ApigenExpand.class); + private boolean isAnnotated(Object handler) { + if (!(handler instanceof HandlerMethod)) return false; - if (expandAnnotation != null) return true; - } + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + ApigenExpand expandAnnotation = method.getAnnotation(ApigenExpand.class); - validate(request, allowed, excluded, maxLevel); - return true; + return expandAnnotation != null; } } \ No newline at end of file diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java similarity index 81% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java index c23d155..6bd18e0 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/response/ApiResponseBodyAdvice.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.archetypecore.interceptors.response; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.response; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenProperties; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.archetypecore.autoconfigure.ApigenProperties; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -30,7 +30,7 @@ public boolean supports(MethodParameter methodParameter, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { - if (ApiResponse.class.isAssignableFrom(maybeApiResponse.getClass())) { + if (maybeApiResponse != null && ApiResponse.class.isAssignableFrom(maybeApiResponse.getClass())) { ApiResponse apiResponse = (ApiResponse) maybeApiResponse; return enhance(apiResponse, serverHttpRequest, serverHttpResponse, traceHeader); } diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java similarity index 91% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java index 65a1034..449064a 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/response/ApiResponseEnhancer.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.archetypecore.interceptors.response; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.response; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.archetypecore.core.responses.result.ApiResult; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.result.ApiResult; import org.springframework.http.HttpStatus; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java similarity index 83% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java index d76554f..00c277c 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/trace/TraceIdInterceptor.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.archetypecore.interceptors.trace; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.trace; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.interceptors.ApigenContext; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.ApigenContext; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.servlet.http.HttpServletRequest; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java similarity index 93% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java index ebec606..a364212 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/update/CachingRequestBodyFilter.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.interceptors.update; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.update; import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; diff --git a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java similarity index 95% rename from archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java rename to archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java index fb8e421..56c167e 100644 --- a/archetype-core/src/main/java/net/cloudappi/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java +++ b/archetype-core/src/main/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/update/UpdateRequestBodyAdvice.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.archetypecore.interceptors.update; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.update; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; -import net.cloudappi.apigen.archetypecore.interceptors.ApigenContext; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.ApigenContext; import org.springframework.core.MethodParameter; import org.springframework.http.HttpInputMessage; import org.springframework.http.converter.HttpMessageConverter; diff --git a/archetype-core/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer b/archetype-core/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer index b1653a2..d145bb7 100644 --- a/archetype-core/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer +++ b/archetype-core/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer @@ -1 +1 @@ -net.cloudappi.apigen.archetypecore.core.persistence.functions.ApigenFunctionsMetadataBuilderInitializer \ No newline at end of file +org.apiaddicts.apitools.apigen.archetypecore.core.persistence.functions.ApigenFunctionsMetadataBuilderInitializer \ No newline at end of file diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java deleted file mode 100644 index 2e5624f..0000000 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.stubs; - -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface FakeEntityDatesRepository extends ApigenRepository { -} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java b/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java deleted file mode 100644 index 39af06d..0000000 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.stubs; - -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface FakeEntityNodeRepository extends ApigenRepository { -} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/FakeApplication.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/FakeApplication.java similarity index 68% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/FakeApplication.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/FakeApplication.java index d1193dd..da22a3e 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/FakeApplication.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/FakeApplication.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.archetypecore; +package org.apiaddicts.apitools.apigen.archetypecore; -import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenApplication; +import org.apiaddicts.apitools.apigen.archetypecore.autoconfigure.ApigenApplication; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudServiceTest.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractCrudServiceTest.java similarity index 88% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudServiceTest.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractCrudServiceTest.java index cd37aaf..e32438a 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractCrudServiceTest.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractCrudServiceTest.java @@ -1,8 +1,10 @@ -package net.cloudappi.apigen.archetypecore.core; +package org.apiaddicts.apitools.apigen.archetypecore.core; + +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepository; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.EntityNotFoundException; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.EntityIdAlreadyInUseException; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; -import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -36,6 +38,13 @@ void givenNonPersistedEntity_whenSave_thenSuccess() { verify(relationsManager).createOrRetrieveRelations(any(Entity.class)); assertSame(entity, result); } + + @Test + void givenNonPersistedEntityWithId_whenCreated_thenError() { + Entity entity = new Entity(1L); + when(repository.findById(entity.getId())).thenReturn(Optional.of(entity)); + assertThrows(EntityIdAlreadyInUseException.class, () -> service.create(entity)); + } @Test void givenNonPersistedEntitySet_whenSave_thenSuccess() { diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractReadServiceTest.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractReadServiceTest.java similarity index 80% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractReadServiceTest.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractReadServiceTest.java index bf41d8a..392075c 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/AbstractReadServiceTest.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/AbstractReadServiceTest.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.archetypecore.core; - -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearch; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; +package org.apiaddicts.apitools.apigen.archetypecore.core; + +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepository; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearch; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearchResult; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.EntityNotFoundException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -18,8 +18,7 @@ import java.util.List; import java.util.Optional; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.*; @@ -112,6 +111,26 @@ void givenPersistedEntity_whenSearchOne_thenException() { assertThrows(EntityNotFoundException.class, () -> service.search(1L, SELECT, EXCLUDE, EXPAND)); } + @Test + void givenPersistedEntity_whenSafeGetOne_thenSuccess() { + Entity entity = new Entity(1L); + when(repository.findById(1L)).thenReturn(Optional.of(entity)); + + Entity entityReturned = service.safeGetOne(1L); + verify(repository).findById(1L); + assertEquals(entityReturned, entity); + } + + @Test + void givenNonPersistedEntity_whenSafeGetOne_thenError() { + assertThrows(EntityNotFoundException.class, () -> service.safeGetOne(1L)); + } + + @Test + void givenNonPersistedEntity_whenSafeGetOne_thenInvalidId() { + assertThrows(IllegalArgumentException.class, () -> service.safeGetOne(null)); + } + private void assertApigenSearch(List select, List exclude, List expand, Filter filter, List orderBy, Integer init, Integer limit, Boolean total, ApigenSearch search) { assertSame(select, search.getSelect()); assertSame(exclude, search.getExclude()); diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java similarity index 93% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java index 18d11da..800b1ef 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/ApigenControllerAdviceTests.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.advice; +package org.apiaddicts.apitools.apigen.archetypecore.core.advice; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -94,6 +94,24 @@ void givenValidationNotNull_whenValueIsNotNull_thenReturnErrorWithCodeAndMessage assertEquals(400, response.getStatus()); } + @Test + void givenValidationNull_whenValueNull_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/error-null") + .contentType(MediaType.APPLICATION_JSON) + .content("{\"name_property\": \"aaa\"}") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1015))) + .andExpect(jsonPath("$.result.errors[0].message", is("Property 'name_property' must be null"))) + .andExpect(jsonPath("$.result.errors[0].element", is("name_property"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + @Test void givenValidationEmail_whenValueIsErrorNotEmail_thenReturnErrorWithCodeAndMessage() throws Exception { // given @@ -524,6 +542,22 @@ void givenEntityNotFound_whenEntityNotFound_thenReturnErrorWithCodeAndMessage() assertEquals(404, response.getStatus()); } + @Test + void givenEntityIdAlreadyExists_whenEntityIdAlreadyExists_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + get("/advice/entity-id-already-exists") + .accept(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.result.errors[0].code", is(1113))) + .andExpect(jsonPath("$.result.errors[0].message", is("Element with id 'id' of type 'String' already exists"))) + .andExpect(jsonPath("$.result.errors[0].element", is("id"))) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + @Test void givenEntityNotFound_whenRelatedEntityNotFound_thenReturnErrorWithCodeAndMessage() throws Exception { // given @@ -608,14 +642,30 @@ void givenException_whenNotKnowException_thenReturnErrorWithCodeAndMessage() thr } @Test - void givenMediaType_whenBodyIsNotCorrect_thenReturnErrorWithCodeAndMessage() throws Exception { + void givenAcceptMediaType_whenBodyIsNotCorrect_thenReturnErrorWithCodeAndMessage() throws Exception { // given // when MockHttpServletResponse response = mvc.perform( - get("/advice/media-type") + get("/advice/accept-media-type") .accept(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.result.errors[0].code", is(1203))) - .andExpect(jsonPath("$.result.errors[0].message", is("Format not supported, supported formats: '[text/xml]'"))) + .andExpect(jsonPath("$.result.errors[0].message", is("Accept format not supported, supported formats: '[text/xml]'"))) + .andExpect(jsonPath("$.result.errors[0].element").doesNotExist()) + .andReturn().getResponse(); + + // then + assertEquals(400, response.getStatus()); + } + + @Test + void givenContentMediaType_whenBodyIsNotCorrect_thenReturnErrorWithCodeAndMessage() throws Exception { + // given + // when + MockHttpServletResponse response = mvc.perform( + post("/advice/content-media-type") + .contentType(MediaType.APPLICATION_XML)) + .andExpect(jsonPath("$.result.errors[0].code", is(1205))) + .andExpect(jsonPath("$.result.errors[0].message", is("Content-type format not supported: 'application/xml;charset=UTF-8'"))) .andExpect(jsonPath("$.result.errors[0].element").doesNotExist()) .andReturn().getResponse(); diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java similarity index 90% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java index 7a65315..505bce2 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/advice/controller/FakeApiResponseErrorsController.java @@ -1,19 +1,16 @@ -package net.cloudappi.apigen.archetypecore.core.advice.controller; +package org.apiaddicts.apitools.apigen.archetypecore.core.advice.controller; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.archetypecore.exceptions.*; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.*; import org.springframework.http.MediaType; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; -import javax.validation.constraints.AssertTrue; -import javax.validation.constraints.DecimalMax; -import javax.validation.constraints.DecimalMin; -import javax.validation.constraints.NotNull; +import javax.validation.constraints.*; import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; @@ -37,7 +34,20 @@ public static class NotNullBody{ @PostMapping(value = "/error-not-null") public @ResponseBody - ApiResponse postNotNullExpection(@Valid @RequestBody NotNullBody body) { + ApiResponse postNotNullException(@Valid @RequestBody NotNullBody body) { + throw new RuntimeException(); + } + + @Data + public static class NullBody{ + @Null + @JsonProperty("name_property") + private String name; + } + + @PostMapping(value = "/error-null") + public @ResponseBody + ApiResponse postNullException(@Valid @RequestBody NullBody body) { throw new RuntimeException(); } @@ -319,6 +329,11 @@ ApiResponse postErrorException(@Valid @RequestBody Error body) { throw new EntityNotFoundException("id", String.class); } + @GetMapping(value = "/entity-id-already-exists") + public @ResponseBody ApiResponse getEntityIdAlreadyExistsException() { + throw new EntityIdAlreadyInUseException("id", String.class); + } + @GetMapping(value = "/related-entity-not-found") public @ResponseBody ApiResponse getRelatedEntityNotFoundException() { RelationalErrors errors = new RelationalErrors(); @@ -349,11 +364,21 @@ ApiResponse postErrorException(@Valid @RequestBody Error body) { throw new RuntimeException(); } - @GetMapping(value = "/media-type", produces = MediaType.TEXT_XML_VALUE) - public @ResponseBody ApiResponse getMediaTypeException() { + @GetMapping(value = "/accept-media-type", produces = MediaType.TEXT_XML_VALUE) + public @ResponseBody ApiResponse getContentMediaTypeException() { + throw new RuntimeException(); + } + + @PostMapping(value = "/content-media-type") + public @ResponseBody ApiResponse getAcceptMediaTypeException(@RequestBody ContentBody body) { throw new RuntimeException(); } + @Data + public static class ContentBody { + private String name; + } + @GetMapping(value = "/custom-apigen-exception") public @ResponseBody ApiResponse getCustomApigenException() { throw new CustomApigenException("CUSTOM_ERROR", "color"); diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java similarity index 71% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java index b69942f..3cf8217 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/ApigenRepositoryTest.java @@ -1,14 +1,13 @@ -package net.cloudappi.apigen.archetypecore.core.persistence; - -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.PropType; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Value; -import net.cloudappi.apigen.archetypecore.core.persistence.pagination.Pagination; -import net.cloudappi.apigen.archetypecore.core.persistence.stubs.FakeEntityDates; -import net.cloudappi.apigen.archetypecore.core.persistence.stubs.FakeEntityDatesRepository; -import net.cloudappi.apigen.archetypecore.core.persistence.stubs.FakeEntityNode; -import net.cloudappi.apigen.archetypecore.core.persistence.stubs.FakeEntityNodeRepository; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence; + +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.FilterOperation; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Value; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.pagination.Pagination; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.stubs.FakeEntityDates; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.stubs.FakeEntityDatesRepository; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.stubs.FakeEntityNode; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.stubs.FakeEntityNodeRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -57,13 +56,32 @@ void givenPersistedRecords_whenSearchedPaginatedSmall_thenSearchPage() { assertEquals(TOTAL_DATES_ENTITIES, result.getTotal()); } + @Test + void givenPersistedRecords_whenSearchedPaginatedSmallWithoutOrder_thenSearchPageOrderById() { + Pagination pagination = new Pagination(0, 1); + ApigenSearch search = new ApigenSearch(EMPTY, EMPTY, EMPTY, null, null, pagination, true); + ApigenSearchResult result = datesRepository.search(search); + assertEquals(1, result.getSearchResult().size()); + assertEquals(PAST_DATE_ENTITY_ID, result.getSearchResult().get(0).getId()); + assertEquals(TOTAL_DATES_ENTITIES, result.getTotal()); + } + + @Test + void givenPersistedRecords_whenSearchedPaginatedSmallWithEmptyOrder_thenSearchPageOrderById() { + Pagination pagination = new Pagination(0, 1); + ApigenSearch search = new ApigenSearch(EMPTY, EMPTY, EMPTY, null, EMPTY, pagination, true); + ApigenSearchResult result = datesRepository.search(search); + assertEquals(1, result.getSearchResult().size()); + assertEquals(PAST_DATE_ENTITY_ID, result.getSearchResult().get(0).getId()); + assertEquals(TOTAL_DATES_ENTITIES, result.getTotal()); + } + @Test void givenPersistedRecords_whenSearchedDatetimeFilter_thenSuccess() { Pagination pagination = new Pagination(0, 20); Filter filter = new Filter(); filter.setOperation(FilterOperation.GT); Value value = new Value(); - value.setType(PropType.DATETIME); value.setProperty("dateTime"); value.setValue("2020-02-02T12:00:00.000+01:00"); filter.setValues(Collections.singletonList(value)); @@ -79,7 +97,6 @@ void givenPersistedRecords_whenSearchedDateFilter_thenSuccess() { Filter filter = new Filter(); filter.setOperation(FilterOperation.LT); Value value = new Value(); - value.setType(PropType.DATE); value.setProperty("date"); value.setValue("2020-02-02"); filter.setValues(Collections.singletonList(value)); diff --git a/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutorTypeConvertersTest.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutorTypeConvertersTest.java new file mode 100644 index 0000000..387e604 --- /dev/null +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/executor/ApigenSearchExecutorTypeConvertersTest.java @@ -0,0 +1,82 @@ +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Instant; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ApigenSearchExecutorTypeConvertersTest { + + @MethodSource("converterProvider") + @ParameterizedTest(name = "{index} => class={0} strValue={1} -> value={2}") + void givenDefaultTypes_whenConvert_thenSuccess(Class clazz, String strValue, Comparable value) { + Comparable result = ApigenSearchExecutorTypeConverters.convert(clazz, strValue); + assertEquals(value, result); + } + + @Test + void givenRegisteredType_whenConvert_thenSuccess() { + ApigenSearchExecutorTypeConverters.register(CustomType.class, s -> new CustomType(s)); + Comparable result = ApigenSearchExecutorTypeConverters.convert(CustomType.class, "red"); + assertEquals(new CustomType("red"), result); + } + + @Test + void givenUnregisteredType_whenConvert_thenError() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> ApigenSearchExecutorTypeConverters.convert(OtherType.class, "red")); + assertEquals("No converter found for type: class org.apiaddicts.apitools.apigen.archetypecore.core.persistence.executor.ApigenSearchExecutorTypeConvertersTest$OtherType", ex.getMessage()); + } + + private static Stream converterProvider() { + return Stream.of( + Arguments.of(String.class, "a", "a"), + Arguments.of(Integer.class, "1", 1), + Arguments.of(Long.class, "1", 1L), + Arguments.of(Float.class, "1.1", 1.1F), + Arguments.of(Double.class, "1.1", 1.1D), + Arguments.of(LocalDate.class, "2020-02-01", LocalDate.of(2020, 2, 1)), + Arguments.of(OffsetDateTime.class, "2021-01-28T12:44:27.688Z", OffsetDateTime.of(2021, 1, 28, 12, 44, 27, 688000000, ZoneOffset.UTC)), + Arguments.of(OffsetDateTime.class, "2021-01-28T12:44:27.688+01:00", OffsetDateTime.of(2021, 1, 28, 12, 44, 27, 688000000, ZoneOffset.ofHours(1))), + Arguments.of(Instant.class, "2021-01-28T12:44:27.688Z", OffsetDateTime.of(2021, 1, 28, 12, 44, 27, 688000000, ZoneOffset.UTC).toInstant()), + Arguments.of(Instant.class, "2021-01-28T12:44:27.688+01:00", OffsetDateTime.of(2021, 1, 28, 12, 44, 27, 688000000, ZoneOffset.ofHours(1)).toInstant()), + Arguments.of(Boolean.class, "true", true), + Arguments.of(BigInteger.class, "1", new BigInteger("1")), + Arguments.of(BigDecimal.class, "1.1", new BigDecimal("1.1")) + ); + } + + @Data + @AllArgsConstructor + private static class CustomType implements Comparable { + private String value; + + @Override + public int compareTo(CustomType o) { + return value.compareTo(o.value); + } + } + + @Data + @AllArgsConstructor + private static class OtherType implements Comparable { + private String value; + + @Override + public int compareTo(OtherType o) { + return value.compareTo(o.value); + } + } +} \ No newline at end of file diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java similarity index 78% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java index 5e5b911..a72b05a 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityDates.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.stubs; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.stubs; import lombok.Getter; import lombok.Setter; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; import javax.persistence.*; import java.time.LocalDate; diff --git a/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java new file mode 100644 index 0000000..ef8b125 --- /dev/null +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityDatesRepository.java @@ -0,0 +1,8 @@ +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.stubs; + +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FakeEntityDatesRepository extends ApigenRepository { +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java similarity index 86% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java index cf74858..89e38c3 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityNode.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.archetypecore.core.persistence.stubs; +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.stubs; import lombok.Getter; import lombok.Setter; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; import javax.persistence.*; import java.util.Set; diff --git a/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java new file mode 100644 index 0000000..3ac7139 --- /dev/null +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/persistence/stubs/FakeEntityNodeRepository.java @@ -0,0 +1,8 @@ +package org.apiaddicts.apitools.apigen.archetypecore.core.persistence.stubs; + +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface FakeEntityNodeRepository extends ApigenRepository { +} diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java similarity index 95% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java index adefd6f..3fe9505 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/ResourceNamingTranslatorByReflectionTests.java @@ -1,11 +1,10 @@ -package net.cloudappi.apigen.archetypecore.core.resource; +package org.apiaddicts.apitools.apigen.archetypecore.core.resource; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.FilterOperation; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.PropType; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Value; -import net.cloudappi.apigen.archetypecore.core.resource.stubs.FakeResourceColor; -import net.cloudappi.apigen.archetypecore.exceptions.InvalidPropertyPath; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.FilterOperation; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Value; +import org.apiaddicts.apitools.apigen.archetypecore.core.resource.stubs.FakeResourceColor; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.InvalidPropertyPath; import org.junit.jupiter.api.Test; import java.util.Arrays; @@ -216,7 +215,6 @@ void givenFilterWithoutNestedId_whenTranslate_thenSuccess(){ Filter filter = new Filter(); filter.setOperation(FilterOperation.LT); Value value = new Value(); - value.setType(PropType.INTEGER); value.setProperty("json_id"); value.setValue("10"); filter.setValues(Collections.singletonList(value)); @@ -231,7 +229,6 @@ void givenFilterWithNestedId_whenTranslate_thenSuccess(){ Filter filter = new Filter(); filter.setOperation(FilterOperation.LT); Value value = new Value(); - value.setType(PropType.INTEGER); value.setProperty("json_form.property_id"); value.setValue("10"); filter.setValues(Collections.singletonList(value)); @@ -246,7 +243,6 @@ void givenFilterWithSetNestedId_whenTranslate_thenSuccess(){ Filter filter = new Filter(); filter.setOperation(FilterOperation.LT); Value value = new Value(); - value.setType(PropType.INTEGER); value.setProperty("json_forms.property_id"); value.setValue("10"); filter.setValues(Collections.singletonList(value)); diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java similarity index 71% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java index 9aa7145..9867b32 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/stubs/FakeResourceColor.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.archetypecore.core.resource.stubs; +package org.apiaddicts.apitools.apigen.archetypecore.core.resource.stubs; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.resource.ApigenEntityOutResource; +import org.apiaddicts.apitools.apigen.archetypecore.core.resource.ApigenEntityOutResource; import java.util.Set; diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java similarity index 60% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java index 0ada4b6..5895762 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/resource/stubs/FakeResourceForm.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.archetypecore.core.resource.stubs; +package org.apiaddicts.apitools.apigen.archetypecore.core.resource.stubs; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.resource.ApigenEntityOutResource; +import org.apiaddicts.apitools.apigen.archetypecore.core.resource.ApigenEntityOutResource; @Data @ApigenEntityOutResource diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponseObjectMother.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/ApiResponseObjectMother.java similarity index 91% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponseObjectMother.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/ApiResponseObjectMother.java index c6ae7ee..54ed49c 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/core/responses/ApiResponseObjectMother.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/core/responses/ApiResponseObjectMother.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.core.responses; +package org.apiaddicts.apitools.apigen.archetypecore.core.responses; public class ApiResponseObjectMother { diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java similarity index 99% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java index 2241393..c15640c 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ApiResponseBodyAdviceTest.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.interceptors; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ExpandInterceptorTests.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ExpandInterceptorTests.java similarity index 99% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ExpandInterceptorTests.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ExpandInterceptorTests.java index c62199d..a32e1ad 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/ExpandInterceptorTests.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/ExpandInterceptorTests.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.interceptors; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java similarity index 94% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java index 21cf439..8c7e58e 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/TraceIdInterceptorTests.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.archetypecore.interceptors; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeApiResultController.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeApiResultController.java similarity index 83% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeApiResultController.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeApiResultController.java index 4dba8fa..248d449 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeApiResultController.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeApiResultController.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.archetypecore.interceptors.controller; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.controller; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponseObjectMother; -import net.cloudappi.apigen.archetypecore.exceptions.EntityNotFoundException; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponseObjectMother; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.EntityNotFoundException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.*; diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeExpandController.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeExpandController.java similarity index 89% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeExpandController.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeExpandController.java index 5d164db..3ec82ed 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeExpandController.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeExpandController.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.archetypecore.interceptors.controller; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.controller; -import net.cloudappi.apigen.archetypecore.interceptors.expand.ApigenExpand; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.expand.ApigenExpand; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakePaginationController.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakePaginationController.java similarity index 88% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakePaginationController.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakePaginationController.java index 59ddf8c..33102e6 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakePaginationController.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakePaginationController.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.archetypecore.interceptors.controller; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.controller; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponseObjectMother; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponseObjectMother; import org.springframework.web.bind.annotation.*; @RestController diff --git a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java similarity index 71% rename from archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java rename to archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java index a915e6c..9d4425f 100644 --- a/archetype-core/src/test/java/net/cloudappi/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java +++ b/archetype-core/src/test/java/org/apiaddicts/apitools/apigen/archetypecore/interceptors/controller/FakeTraceIdController.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.archetypecore.interceptors.controller; +package org.apiaddicts.apitools.apigen.archetypecore.interceptors.controller; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.interceptors.ApigenContext; +import org.apiaddicts.apitools.apigen.archetypecore.interceptors.ApigenContext; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/archetype-core/src/test/resources/application.properties b/archetype-core/src/test/resources/application.properties index 69f2598..23da153 100644 --- a/archetype-core/src/test/resources/application.properties +++ b/archetype-core/src/test/resources/application.properties @@ -1,4 +1,4 @@ -logging.level.net.cloudappi.apigen=DEBUG +logging.level.org.apiaddicts.apitools.apigen=DEBUG spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=create-drop diff --git a/archetype-parent-spring-boot/pom.xml b/archetype-parent-spring-boot/pom.xml index 4b855b9..7abfb90 100644 --- a/archetype-parent-spring-boot/pom.xml +++ b/archetype-parent-spring-boot/pom.xml @@ -6,20 +6,50 @@ org.springframework.boot spring-boot-starter-parent - 2.3.3.RELEASE + 2.4.0 4.0.0 - net.cloudappi.apigen + org.apiaddicts.apitools.apigen archetype-parent-spring-boot - 0.0.1-SNAPSHOT + 0.0.3-SNAPSHOT pom + https://github.com/apiaddicts/apigen + + + + GNU Lesser General Public License (LGPLV3+) + http://www.gnu.org/licenses/lgpl-3.0.html + + + + + Apiaddicts + https://apiaddicts.org + + + + scm:git:git://github.com/apiaddicts/apigen.git + scm:git:ssh://github.com:apiaddicts/apigen.git + https://github.com/apiaddicts/apigen/tree/master + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + - 0.0.1-SNAPSHOT + 0.0.3-SNAPSHOT 1.8 1.8 @@ -28,14 +58,15 @@ UTF-8 UTF-8 - 0.0.1-SNAPSHOT - 1.3.1.Final + 0.0.3-SNAPSHOT + 1.4.2.Final 2.9.2 + 0.2.0 - net.cloudappi.apigen + org.apiaddicts.apitools.apigen archetype-core ${apigen.version} @@ -124,6 +155,11 @@ lombok ${lombok.version} + + org.projectlombok + lombok-mapstruct-binding + ${lombok-mapstruct-binding.version} + diff --git a/generator-core/pom.xml b/generator-core/pom.xml index 71a6ca4..9230fed 100644 --- a/generator-core/pom.xml +++ b/generator-core/pom.xml @@ -6,8 +6,8 @@ apigen - net.cloudappi.apigen - 0.0.1-SNAPSHOT + org.apiaddicts.apitools.apigen + 0.0.3-SNAPSHOT ../pom.xml @@ -15,6 +15,36 @@ generator-core generator-core ${revision} + https://github.com/apiaddicts/apigen + + + + GNU Lesser General Public License (LGPLV3+) + http://www.gnu.org/licenses/lgpl-3.0.html + + + + + Apiaddicts + https://apiaddicts.org + + + + scm:git:git://github.com/apiaddicts/apigen.git + scm:git:ssh://github.com:apiaddicts/apigen.git + https://github.com/apiaddicts/apigen/tree/master + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + @@ -40,17 +70,13 @@ - net.cloudappi.apigen + org.apiaddicts.apitools.apigen archetype-core org.springframework spring-web - - org.springframework.boot - spring-boot-test - org.springframework.boot spring-boot-starter-validation diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/DefinitionException.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/DefinitionException.java deleted file mode 100644 index 7cc3c37..0000000 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/DefinitionException.java +++ /dev/null @@ -1,4 +0,0 @@ -package net.cloudappi.apigen.generatorcore.exceptions; - -public class DefinitionException extends RuntimeException { -} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceBuilder.java deleted file mode 100644 index 706cff7..0000000 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceBuilder.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource; - -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; - -public abstract class ResourceBuilder extends AbstractClassBuilder { - -} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesData.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesData.java deleted file mode 100644 index 07a10e8..0000000 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesData.java +++ /dev/null @@ -1,23 +0,0 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource; - -import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.generator.web.resource.input.InputResourceBuilder; - -import java.util.*; - -public class ResourcesData { - private Map> resourcesToEntity = new HashMap<>(); - - public ResourcesData(List inputResourceBuilders) { - inputResourceBuilders.forEach(builder -> { - String entityName = builder.getEntityName(); - resourcesToEntity.putIfAbsent(entityName, new HashSet<>()); - resourcesToEntity.get(entityName).add(builder.getTypeName()); - }); - } - - - public Set getInputResources(String entityName) { - return resourcesToEntity.getOrDefault(entityName, Collections.emptySet()); - } -} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java deleted file mode 100644 index 74ac802..0000000 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.output; - -import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourceBuilder; - -public abstract class OutputResourceBuilder extends ResourceBuilder { - -} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilder.java b/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilder.java deleted file mode 100644 index 225da5f..0000000 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilder.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; - -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; - -public abstract class ResponseBuilder extends AbstractClassBuilder { - -} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/Configuration.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/Configuration.java similarity index 81% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/Configuration.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/Configuration.java index 43a41a2..7d1eafe 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/Configuration.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/Configuration.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.config; +package org.apiaddicts.apitools.apigen.generatorcore.config; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Attribute.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Attribute.java similarity index 80% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Attribute.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Attribute.java index 2ee0304..87484d8 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Attribute.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Attribute.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import net.cloudappi.apigen.generatorcore.config.validation.Validation; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.Validation; import java.util.List; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Controller.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Controller.java similarity index 81% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Controller.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Controller.java index ee6712a..003b594 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Controller.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Controller.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Endpoint.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Endpoint.java similarity index 91% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Endpoint.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Endpoint.java index 373d1b7..9515967 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Endpoint.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Endpoint.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Parameter.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Parameter.java similarity index 80% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Parameter.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Parameter.java index 0af9a03..0451693 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Parameter.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Parameter.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Request.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Request.java similarity index 67% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Request.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Request.java index ca156c4..c3d1e2f 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Request.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Request.java @@ -1,8 +1,7 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import net.cloudappi.apigen.generatorcore.config.validation.Validation; import java.util.List; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Response.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Response.java similarity index 88% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Response.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Response.java index af91822..d16ef17 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/controller/Response.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/Response.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Attribute.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Attribute.java similarity index 86% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Attribute.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Attribute.java index 6f55166..757525c 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Attribute.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Attribute.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.config.entity; +package org.apiaddicts.apitools.apigen.generatorcore.config.entity; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; -import net.cloudappi.apigen.generatorcore.config.validation.Validation; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.Validation; import java.util.ArrayList; import java.util.List; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Column.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Column.java similarity index 94% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Column.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Column.java index d071768..b29ac1c 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Column.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Column.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.entity; +package org.apiaddicts.apitools.apigen.generatorcore.config.entity; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Entity.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Entity.java similarity index 88% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Entity.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Entity.java index 5fc37d7..4bd7268 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Entity.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Entity.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.entity; +package org.apiaddicts.apitools.apigen.generatorcore.config.entity; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Relation.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Relation.java similarity index 95% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Relation.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Relation.java index 9448a82..3e772f2 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/Relation.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/Relation.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.entity; +package org.apiaddicts.apitools.apigen.generatorcore.config.entity; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/RelationType.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/RelationType.java similarity index 67% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/RelationType.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/RelationType.java index 15392e1..ae32d79 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/entity/RelationType.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/RelationType.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.entity; +package org.apiaddicts.apitools.apigen.generatorcore.config.entity; public enum RelationType { ONE_TO_ONE, diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AbstractExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/AbstractExtractor.java similarity index 52% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AbstractExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/AbstractExtractor.java index 5cb10cd..37e30e2 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AbstractExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/AbstractExtractor.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; -import net.cloudappi.apigen.generatorcore.config.extractors.context.ExtractorContext; +import org.apiaddicts.apitools.apigen.generatorcore.config.extractors.context.ExtractorContext; public abstract class AbstractExtractor { protected final ExtractorContext context; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AttributesExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/AttributesExtractor.java similarity index 92% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AttributesExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/AttributesExtractor.java index 33ca29d..6b80a7e 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/AttributesExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/AttributesExtractor.java @@ -1,14 +1,14 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.Schema; -import net.cloudappi.apigen.generatorcore.config.controller.Attribute; -import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; -import net.cloudappi.apigen.generatorcore.utils.CustomStringUtils; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Attribute; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.Openapi2JavapoetType; +import org.apiaddicts.apitools.apigen.generatorcore.utils.CustomStringUtils; import java.util.*; -import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.*; +import static org.apiaddicts.apitools.apigen.generatorcore.spec.components.Extensions.*; public class AttributesExtractor { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ConfigurationExtractor.java similarity index 77% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ConfigurationExtractor.java index 39e0983..8bc44b7 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ConfigurationExtractor.java @@ -1,12 +1,12 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.config.extractors.context.ExtractorContext; -import net.cloudappi.apigen.generatorcore.exceptions.DefinitionException; -import net.cloudappi.apigen.generatorcore.exceptions.ExtractorException; -import net.cloudappi.apigen.generatorcore.spec.OpenAPIExtended; -import net.cloudappi.apigen.generatorcore.spec.components.ApigenProject; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.extractors.context.ExtractorContext; +import org.apiaddicts.apitools.apigen.generatorcore.exceptions.DefinitionException; +import org.apiaddicts.apitools.apigen.generatorcore.exceptions.ExtractorException; +import org.apiaddicts.apitools.apigen.generatorcore.spec.OpenAPIExtended; +import org.apiaddicts.apitools.apigen.generatorcore.spec.components.ApigenProject; @Slf4j public class ConfigurationExtractor extends AbstractExtractor { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ControllersExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ControllersExtractor.java similarity index 92% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ControllersExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ControllersExtractor.java index 4c357da..f668d37 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ControllersExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ControllersExtractor.java @@ -1,14 +1,14 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.media.Schema; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.spec.components.ApigenBinding; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.spec.components.ApigenBinding; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import java.util.ArrayList; import java.util.HashMap; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/EntitiesExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/EntitiesExtractor.java similarity index 91% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/EntitiesExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/EntitiesExtractor.java index 9e4f1f7..bc516be 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/EntitiesExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/EntitiesExtractor.java @@ -1,17 +1,17 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; -import net.cloudappi.apigen.generatorcore.config.entity.Attribute; -import net.cloudappi.apigen.generatorcore.config.entity.Column; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.Relation; -import net.cloudappi.apigen.generatorcore.config.extractors.context.ExtractorContext; -import net.cloudappi.apigen.generatorcore.spec.components.ApigenModel; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Attribute; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Column; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Relation; +import org.apiaddicts.apitools.apigen.generatorcore.config.extractors.context.ExtractorContext; +import org.apiaddicts.apitools.apigen.generatorcore.spec.components.ApigenModel; import java.util.*; import java.util.stream.Collectors; -import static net.cloudappi.apigen.generatorcore.exceptions.GeneratorErrors.*; -import static net.cloudappi.apigen.generatorcore.generator.common.ApigenExt2JavapoetType.isComposedID; +import static org.apiaddicts.apitools.apigen.generatorcore.exceptions.GeneratorErrors.*; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.ApigenExt2JavapoetType.isComposedID; public class EntitiesExtractor extends AbstractExtractor { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ParametersExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ParametersExtractor.java similarity index 90% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ParametersExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ParametersExtractor.java index 5207b91..507ce99 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ParametersExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ParametersExtractor.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.Schema; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; import java.util.ArrayList; import java.util.List; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/RequestExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/RequestExtractor.java similarity index 80% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/RequestExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/RequestExtractor.java index 8e77571..be3ae19 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/RequestExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/RequestExtractor.java @@ -1,14 +1,14 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.parameters.RequestBody; -import net.cloudappi.apigen.generatorcore.config.controller.Request; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Request; import java.util.Map; -import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.MAPPING; -import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.MAPPING_MODEL; +import static org.apiaddicts.apitools.apigen.generatorcore.spec.components.Extensions.MAPPING; +import static org.apiaddicts.apitools.apigen.generatorcore.spec.components.Extensions.MAPPING_MODEL; public class RequestExtractor { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ResponseExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ResponseExtractor.java similarity index 91% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ResponseExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ResponseExtractor.java index f0168d2..c3b5408 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ResponseExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ResponseExtractor.java @@ -1,15 +1,15 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.ArraySchema; import io.swagger.v3.oas.models.media.Schema; import io.swagger.v3.oas.models.responses.ApiResponse; -import net.cloudappi.apigen.generatorcore.config.controller.Response; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Response; import java.util.Map; -import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.MAPPING; -import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.MAPPING_MODEL; +import static org.apiaddicts.apitools.apigen.generatorcore.spec.components.Extensions.MAPPING; +import static org.apiaddicts.apitools.apigen.generatorcore.spec.components.Extensions.MAPPING_MODEL; public class ResponseExtractor { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ValidationsExtractor.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ValidationsExtractor.java similarity index 95% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ValidationsExtractor.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ValidationsExtractor.java index 8f2142d..8fe36cd 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/ValidationsExtractor.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ValidationsExtractor.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; import io.swagger.v3.oas.models.media.Schema; -import net.cloudappi.apigen.generatorcore.config.validation.Validation; -import net.cloudappi.apigen.generatorcore.config.validation.ValidationType; -import net.cloudappi.apigen.generatorcore.spec.components.ApigenModel; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.Validation; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.ValidationType; +import org.apiaddicts.apitools.apigen.generatorcore.spec.components.ApigenModel; import java.math.BigDecimal; import java.util.ArrayList; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/context/ExtractorContext.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/context/ExtractorContext.java similarity index 77% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/context/ExtractorContext.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/context/ExtractorContext.java index a54765f..309ed73 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/extractors/context/ExtractorContext.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/context/ExtractorContext.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.config.extractors.context; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors.context; import lombok.AllArgsConstructor; -import net.cloudappi.apigen.generatorcore.exceptions.ExtractorException; -import net.cloudappi.apigen.generatorcore.exceptions.GeneratorErrors; +import org.apiaddicts.apitools.apigen.generatorcore.exceptions.ExtractorException; +import org.apiaddicts.apitools.apigen.generatorcore.exceptions.GeneratorErrors; import java.util.ArrayList; import java.util.List; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/Validation.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/validation/Validation.java similarity index 95% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/Validation.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/validation/Validation.java index b5670d0..ff3a69a 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/Validation.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/validation/Validation.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.validation; +package org.apiaddicts.apitools.apigen.generatorcore.config.validation; import com.fasterxml.jackson.annotation.JsonProperty; import com.squareup.javapoet.FieldSpec; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/ValidationType.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/validation/ValidationType.java similarity index 82% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/ValidationType.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/validation/ValidationType.java index f0e755a..fc34c78 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/config/validation/ValidationType.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/config/validation/ValidationType.java @@ -1,16 +1,16 @@ -package net.cloudappi.apigen.generatorcore.config.validation; +package org.apiaddicts.apitools.apigen.generatorcore.config.validation; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.ParameterSpec; -import net.cloudappi.apigen.generatorcore.generator.common.Members; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members; import javax.validation.constraints.*; import java.math.BigDecimal; import static java.util.Objects.nonNull; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.LITERAL; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; public enum ValidationType { @@ -80,6 +80,13 @@ public void apply(Validation validation, FieldSpec.Builder builder) { public void apply(Validation validation, ParameterSpec.Builder builder) { builder.addAnnotation(getSizeAnnotation(validation.getIntegerValueOne(), validation.getIntegerValueTwo())); } + + private AnnotationSpec getSizeAnnotation(Integer min, Integer max) { + AnnotationSpec.Builder annotationSpec = AnnotationSpec.builder(Size.class); + if (nonNull(min)) annotationSpec.addMember(Members.MIN, LITERAL, min); + if (nonNull(max)) annotationSpec.addMember(Members.MAX, LITERAL, max); + return annotationSpec.build(); + } }, DIGITS(Digits.class) { @Override @@ -91,6 +98,13 @@ public void apply(Validation validation, FieldSpec.Builder builder) { public void apply(Validation validation, ParameterSpec.Builder builder) { builder.addAnnotation(getDigitsAnnotation(validation.getIntegerValueOne(), validation.getIntegerValueTwo())); } + + private AnnotationSpec getDigitsAnnotation(int integer, int fraction) { + return AnnotationSpec.builder(Digits.class) + .addMember(Members.INTEGER, LITERAL, integer) + .addMember(Members.FRACTION, LITERAL, fraction) + .build(); + } }, PATTERN(Pattern.class) { @Override @@ -102,6 +116,10 @@ public void apply(Validation validation, FieldSpec.Builder builder) { public void apply(Validation validation, ParameterSpec.Builder builder) { builder.addAnnotation(getPatternAnnotation(validation.getStringValue())); } + + private AnnotationSpec getPatternAnnotation(String regex) { + return AnnotationSpec.builder(Pattern.class).addMember(Members.REGEXP, STRING, regex).build(); + } }; protected Class enumClass; @@ -129,24 +147,6 @@ private static AnnotationSpec getDecimalAnnotation(Class enumClass, BigDecima return builder.build(); } - private static AnnotationSpec getSizeAnnotation(Integer min, Integer max) { - AnnotationSpec.Builder annotationSpec = AnnotationSpec.builder(Size.class); - if (nonNull(min)) annotationSpec.addMember(Members.MIN, LITERAL, min); - if (nonNull(max)) annotationSpec.addMember(Members.MAX, LITERAL, max); - return annotationSpec.build(); - } - - private static AnnotationSpec getDigitsAnnotation(int integer, int fraction) { - return AnnotationSpec.builder(Digits.class) - .addMember(Members.INTEGER, LITERAL, integer) - .addMember(Members.FRACTION, LITERAL, fraction) - .build(); - } - - private static AnnotationSpec getPatternAnnotation(String regex) { - return AnnotationSpec.builder(Pattern.class).addMember(Members.REGEXP, STRING, regex).build(); - } - public void apply(Validation validation, FieldSpec.Builder builder) { builder.addAnnotation(getDefaultAnnotation(enumClass, validation.getStringValue())); } diff --git a/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/DefinitionException.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/DefinitionException.java new file mode 100644 index 0000000..e64916b --- /dev/null +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/DefinitionException.java @@ -0,0 +1,4 @@ +package org.apiaddicts.apitools.apigen.generatorcore.exceptions; + +public class DefinitionException extends RuntimeException { +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/ExtractorException.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/ExtractorException.java similarity index 64% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/ExtractorException.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/ExtractorException.java index 91305ce..34e33c6 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/ExtractorException.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/ExtractorException.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.exceptions; +package org.apiaddicts.apitools.apigen.generatorcore.exceptions; import lombok.Getter; -import net.cloudappi.apigen.generatorcore.config.extractors.context.ExtractorContext; +import org.apiaddicts.apitools.apigen.generatorcore.config.extractors.context.ExtractorContext; import java.util.List; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/GeneratorErrors.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/GeneratorErrors.java similarity index 92% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/GeneratorErrors.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/GeneratorErrors.java index aca708c..a5d8e87 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/GeneratorErrors.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/GeneratorErrors.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.exceptions; +package org.apiaddicts.apitools.apigen.generatorcore.exceptions; public enum GeneratorErrors { MODEL_WITHOUT_RELATIONAL_PERSISTENCE(1, "Model %s without relational-persistence"), diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/InvalidValuesException.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/InvalidValuesException.java similarity index 84% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/InvalidValuesException.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/InvalidValuesException.java index d690b38..698af96 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/exceptions/InvalidValuesException.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/exceptions/InvalidValuesException.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.exceptions; +package org.apiaddicts.apitools.apigen.generatorcore.exceptions; import lombok.AllArgsConstructor; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/ApigenProjectGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/ApigenProjectGenerator.java similarity index 68% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/ApigenProjectGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/ApigenProjectGenerator.java index 8ea3390..1df5785 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/ApigenProjectGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/ApigenProjectGenerator.java @@ -1,29 +1,31 @@ -package net.cloudappi.apigen.generatorcore.generator; +package org.apiaddicts.apitools.apigen.generatorcore.generator; import io.swagger.parser.OpenAPIParser; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.parser.core.models.ParseOptions; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.config.extractors.ConfigurationExtractor; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.generator.base.PomGenerator; -import net.cloudappi.apigen.generatorcore.generator.base.ProjectStructureGenerator; -import net.cloudappi.apigen.generatorcore.generator.base.SpringBootBaseGenerator; -import net.cloudappi.apigen.generatorcore.generator.common.Validator; -import net.cloudappi.apigen.generatorcore.generator.mapper.MappersGenerator; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesGenerator; -import net.cloudappi.apigen.generatorcore.generator.persistence.repository.RepositoriesGenerator; -import net.cloudappi.apigen.generatorcore.generator.service.RelationManagersGenerator; -import net.cloudappi.apigen.generatorcore.generator.service.ServicesGenerator; -import net.cloudappi.apigen.generatorcore.generator.web.controller.ControllersGenerator; -import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourcesData; -import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourcesGenerator; -import net.cloudappi.apigen.generatorcore.generator.web.response.ResponsesGenerator; -import net.cloudappi.apigen.generatorcore.spec.OpenAPIExtended; -import net.cloudappi.apigen.generatorcore.utils.ZipUtils; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.extractors.ConfigurationExtractor; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.generator.base.PomGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.base.ProjectStructureGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.base.SpringBootBaseGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.Validator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.base.GitIgnoreGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.base.LombokConfigGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.mapper.MappersGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.repository.RepositoriesGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.service.RelationManagersGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.service.ServicesGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.ControllersGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.ResourcesData; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.ResourcesGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.ResponsesGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.spec.OpenAPIExtended; +import org.apiaddicts.apitools.apigen.generatorcore.utils.ZipUtils; import org.apache.commons.io.FileUtils; import org.springframework.core.io.ByteArrayResource; @@ -133,6 +135,8 @@ private void generateCode(File projectFolder, String basePackage, List e private void generateCoreCode(File projectFolder, Configuration config) throws IOException { PomGenerator.generate(config, parentGroup, parentArtifact, parentVersion, projectFolder); + GitIgnoreGenerator.generate(projectFolder); + LombokConfigGenerator.generate(projectFolder); SpringBootBaseGenerator.generate(config, projectFolder); } } diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/Project.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/Project.java similarity index 76% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/Project.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/Project.java index 97d009d..dea2f32 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/Project.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/Project.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator; +package org.apiaddicts.apitools.apigen.generatorcore.generator; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ApplicationBuilder.java similarity index 90% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ApplicationBuilder.java index 8b32fcb..a775097 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ApplicationBuilder.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; import com.squareup.javapoet.*; -import net.cloudappi.apigen.archetypecore.autoconfigure.ApigenApplication; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.autoconfigure.ApigenApplication; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.ApplicationPidFileWriter; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ApplicationGenerator.java similarity index 62% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ApplicationGenerator.java index eef86f3..9f3d246 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ApplicationGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ApplicationGenerator.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; import java.util.Collection; import java.util.Collections; diff --git a/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/GitIgnoreGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/GitIgnoreGenerator.java new file mode 100644 index 0000000..7813967 --- /dev/null +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/GitIgnoreGenerator.java @@ -0,0 +1,54 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class GitIgnoreGenerator { + + private GitIgnoreGenerator() { + // Intentional blank + } + + public static void generate(File projectFolder) throws IOException { + String folder = projectFolder.getPath(); + String gitignore = ".gitignore"; + Path folderPath = Paths.get(folder); + Path gitignorePath = folderPath.resolve(gitignore); + + List lines = new ArrayList<>(); + lines.add("/target"); + lines.add(""); + lines.add("### STS ###"); + lines.add(".apt_generated"); + lines.add(".classpath"); + lines.add(".factorypath"); + lines.add(".project"); + lines.add(".settings"); + lines.add(".springBeans"); + lines.add(".sts4-cache"); + lines.add(""); + lines.add("### IntelliJ IDEA ###"); + lines.add(".idea"); + lines.add("*.iws"); + lines.add("*.iml"); + lines.add("*.ipr"); + lines.add(""); + lines.add("### NetBeans ###"); + lines.add("/nbproject/private/"); + lines.add("/nbbuild/"); + lines.add("/dist/"); + lines.add("/nbdist/"); + lines.add("/.nb-gradle/"); + lines.add("build/"); + lines.add(""); + lines.add("### VS Code ###"); + lines.add(".vscode/"); + + Files.write(gitignorePath, lines); + } +} diff --git a/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/LombokConfigGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/LombokConfigGenerator.java new file mode 100644 index 0000000..52d1b45 --- /dev/null +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/LombokConfigGenerator.java @@ -0,0 +1,28 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class LombokConfigGenerator { + + private LombokConfigGenerator() { + // Intentional blank + } + + public static void generate(File projectFolder) throws IOException { + String folder = projectFolder.getPath(); + String fileName = "lombok.config"; + Path folderPath = Paths.get(folder); + Path filePath = folderPath.resolve(fileName); + + List lines = new ArrayList<>(); + lines.add("lombok.addLombokGeneratedAnnotation=true"); + + Files.write(filePath, lines); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PomGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomGenerator.java similarity index 95% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PomGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomGenerator.java index b797182..fdbb818 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PomGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomGenerator.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; -import net.cloudappi.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.apache.maven.model.Parent; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ProjectStructureGenerator.java similarity index 86% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ProjectStructureGenerator.java index 8eba273..fa08b27 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ProjectStructureGenerator.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; -import net.cloudappi.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; import java.io.File; import java.io.IOException; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PropertiesGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PropertiesGenerator.java similarity index 94% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PropertiesGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PropertiesGenerator.java index ae848fd..b4e0ddb 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/PropertiesGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PropertiesGenerator.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; import java.io.File; import java.io.FileOutputStream; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java similarity index 85% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java index 60fb924..1875007 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootBaseGenerator.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; import java.io.File; import java.io.IOException; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java similarity index 77% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java index 5c03ee8..b530638 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootContextTestGenerator.java @@ -1,11 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.*; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; import javax.lang.model.element.Modifier; @@ -25,7 +21,7 @@ private static TypeSpec.Builder getTestBuilder() { return TypeSpec .classBuilder("ApplicationTests") .addModifiers(Modifier.PUBLIC) - .addAnnotation(SpringBootTest.class); + .addAnnotation(ClassName.get("org.springframework.boot.test.context", "SpringBootTest")); } private static MethodSpec buildTestMethod() { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractClassBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/AbstractClassBuilder.java similarity index 96% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractClassBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/AbstractClassBuilder.java index bc2310e..2e68858 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractClassBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/AbstractClassBuilder.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.FieldSpec; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/AbstractGenerator.java similarity index 93% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/AbstractGenerator.java index 27e9744..e4fbe87 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/AbstractGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/AbstractGenerator.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java similarity index 96% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java index 0297cbe..6d70827 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/ApigenExt2JavapoetType.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Formats.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Formats.java similarity index 76% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Formats.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Formats.java index 28331e7..0571045 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Formats.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Formats.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; public class Formats { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Members.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Members.java similarity index 96% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Members.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Members.java index 28163d0..89ff49a 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Members.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Members.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; public class Members { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetType.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Openapi2JavapoetType.java similarity index 97% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetType.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Openapi2JavapoetType.java index 5d8173b..d313deb 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetType.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Openapi2JavapoetType.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; import com.squareup.javapoet.ArrayTypeName; import com.squareup.javapoet.ClassName; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Validator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Validator.java similarity index 78% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Validator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Validator.java index 3e689fe..2c52133 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/common/Validator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Validator.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; -import net.cloudappi.apigen.generatorcore.exceptions.InvalidValuesException; +import org.apiaddicts.apitools.apigen.generatorcore.exceptions.InvalidValuesException; import javax.validation.ConstraintViolation; import javax.validation.Validation; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperBuilder.java similarity index 69% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperBuilder.java index 8e06975..b6b1896 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperBuilder.java @@ -1,19 +1,20 @@ -package net.cloudappi.apigen.generatorcore.generator.mapper; +package org.apiaddicts.apitools.apigen.generatorcore.generator.mapper; import com.squareup.javapoet.*; -import net.cloudappi.apigen.archetypecore.core.ApigenMapper; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.ApigenMapper; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; import org.mapstruct.*; import javax.lang.model.element.Modifier; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.LITERAL; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.*; public class MapperBuilder extends AbstractClassBuilder { @@ -26,19 +27,21 @@ public class MapperBuilder extends AbstractClassBuilder { private Set basicAttributes; private Set relatedEntitiesName; private Set resourcesToEntity; + private Set entityToResources; private TypeName entityType; - private TypeName composedIdType; + private TypeName idType; private TypeName resourceType; - public MapperBuilder(String entityName, String basePackage, Set basicAttributes, Set relatedEntitiesName, Set resourcesToEntity, TypeName composedIdType) { + public MapperBuilder(String entityName, String basePackage, Set basicAttributes, Set relatedEntitiesName, Set resourcesToEntity, Set entityToResources, TypeName idType) { this.basePackage = basePackage; this.entityName = entityName; this.relatedEntitiesName = relatedEntitiesName; this.basicAttributes = basicAttributes; this.resourcesToEntity = resourcesToEntity; + this.entityToResources = entityToResources; this.entityType = EntityBuilder.getTypeName(entityName, basePackage); - this.composedIdType = composedIdType; + this.idType = idType; this.resourceType = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); } @@ -68,7 +71,8 @@ protected void initialize() { addEntitySetToResource(); addResourcesToEntity(); addUpdateBasicData(); - addComposedIDMapping(); + addComposedIDMapping(); + addIdToEntityMapping(); } private void initializeBuilder() { @@ -93,6 +97,7 @@ private CodeBlock getRelatedEntitiesCodeBlock() { } private void addEntityToResource() { + if (!entityToResources.contains(resourceType)) return; MethodSpec methodSpec = MethodSpec.methodBuilder(TO_RESOURCE) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .addParameter(entityType, "entity") @@ -102,6 +107,7 @@ private void addEntityToResource() { } private void addEntityListToResource() { + if (!entityToResources.contains(resourceType)) return; MethodSpec methodSpec = MethodSpec.methodBuilder(TO_RESOURCE) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .addParameter(ParameterizedTypeName.get(ClassName.get(List.class), entityType), "entities") @@ -111,6 +117,7 @@ private void addEntityListToResource() { } private void addEntitySetToResource() { + if (!entityToResources.contains(resourceType)) return; MethodSpec methodSpec = MethodSpec.methodBuilder(TO_RESOURCE) .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .addParameter(ParameterizedTypeName.get(ClassName.get(Set.class), entityType), "entities") @@ -136,26 +143,24 @@ private void addUpdateBasicData() { .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .addAnnotation(Override.class) .addAnnotation(getAnnotation(BeanMapping.class).addMember(IGNORE_BY_DEFAULT, LITERAL, true).build()) - .addAnnotation(getMappingsAnnotation()) + .addAnnotations(getMappingsAnnotations()) .addParameter(entityType, "source") .addParameter(ParameterSpec.builder(entityType, "target").addAnnotation(MappingTarget.class).build()) .build(); builder.addMethod(methodSpecBuilder); } - private AnnotationSpec getMappingsAnnotation() { - AnnotationSpec.Builder annotationBuilder = getAnnotation(Mappings.class); - basicAttributes.stream().sorted() + private List getMappingsAnnotations() { + return basicAttributes.stream().sorted() .map(attribute -> getAnnotation(Mapping.class).addMember(SOURCE, STRING, attribute).addMember(TARGET, STRING, attribute).build()) - .forEach(annotationSpec -> annotationBuilder.addMember(VALUE, LITERAL, annotationSpec)); - return annotationBuilder.build(); + .collect(Collectors.toList()); } private void addComposedIDMapping() { - if (composedIdType == null) return; + if (!isComposed(idType)) return; MethodSpec mapIDToString = MethodSpec.methodBuilder("map") .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) - .addParameter(composedIdType, "id") + .addParameter(idType, "id") .returns(String.class) .addStatement("return id.toString()") .build(); @@ -163,9 +168,32 @@ private void addComposedIDMapping() { MethodSpec mapStringToID = MethodSpec.methodBuilder("map") .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) .addParameter(String.class, "id") - .returns(composedIdType) - .addStatement("return $T.from(id)", composedIdType) + .returns(idType) + .addStatement("return $T.from(id)", idType) .build(); builder.addMethod(mapStringToID); + MethodSpec mapStringIDToEntity = MethodSpec.methodBuilder("mapToEntity") + .addModifiers(Modifier.PUBLIC,Modifier.DEFAULT) + .addParameter(String.class,"id") + .returns(entityType) + .addStatement("return new $T(map(id))",entityType) + .build(); + builder.addMethod(mapStringIDToEntity); + } + + private void addIdToEntityMapping() { + if (isComposed(idType)) return; + MethodSpec methodSpec = MethodSpec.methodBuilder(TO_ENTITY) + .addModifiers(Modifier.PUBLIC, Modifier.DEFAULT) + .addParameter(idType, "id") + .returns(entityType) + .addStatement("if (id == null) return null") + .addStatement("return new $T(id)", entityType) + .build(); + builder.addMethod(methodSpec); + } + + private boolean isComposed(TypeName type) { + return type != null && !type.toString().startsWith("java"); } } diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MappersGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MappersGenerator.java similarity index 57% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MappersGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MappersGenerator.java index 8bfcef4..833465e 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/mapper/MappersGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MappersGenerator.java @@ -1,12 +1,12 @@ -package net.cloudappi.apigen.generatorcore.generator.mapper; +package org.apiaddicts.apitools.apigen.generatorcore.generator.mapper; import com.squareup.javapoet.TypeName; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; -import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourcesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.ResourcesData; import java.util.*; @@ -23,8 +23,9 @@ public MappersGenerator(Collection entities, EntitiesData entitiesData, Set basicAttributes = entitiesData.getBasicAttributes(name); Set relateEntities = entitiesData.getRelatedEntities(name); Set inputResources = resourcesData.getInputResources(name); - TypeName composedIdType = entitiesData.getComposedIDType(name); - builders.put(name, new MapperBuilder(name, basePackage, basicAttributes, relateEntities, inputResources, composedIdType)); + Set outputResources = resourcesData.getOutputResources(name); + TypeName idType = entitiesData.getIDType(name); + builders.put(name, new MapperBuilder(name, basePackage, basicAttributes, relateEntities, inputResources, outputResources, idType)); }); } diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java similarity index 86% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java index fa42e82..b2e4138 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/ComposedIdBuilder.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence; import com.squareup.javapoet.*; import lombok.AllArgsConstructor; @@ -6,12 +6,13 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.entity.Column; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.*; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.ApigenExt2JavapoetType; -import net.cloudappi.apigen.generatorcore.utils.CustomStringUtils; +import lombok.EqualsAndHashCode; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Column; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.*; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.ApigenExt2JavapoetType; +import org.apiaddicts.apitools.apigen.generatorcore.utils.CustomStringUtils; import javax.lang.model.element.Modifier; import javax.persistence.*; @@ -23,8 +24,8 @@ import java.util.stream.Collectors; import static java.util.Objects.isNull; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.*; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.*; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.*; @Slf4j public class ComposedIdBuilder extends AbstractClassBuilder { @@ -76,7 +77,8 @@ private void initializeBuilder() { .addSuperinterface(Serializable.class) .addSuperinterface(getComparableInterfaceClass()) .addAnnotation(Getter.class).addAnnotation(Setter.class).addAnnotation(Embeddable.class) - .addAnnotation(NoArgsConstructor.class).addAnnotation(AllArgsConstructor.class); + .addAnnotation(NoArgsConstructor.class).addAnnotation(AllArgsConstructor.class) + .addAnnotation(EqualsAndHashCode.class); } private ParameterizedTypeName getComparableInterfaceClass() { @@ -130,7 +132,7 @@ private void addFromMethod() { for (int i = 0; i < idAttributes.size(); i++) { TypeName attTypeName = ApigenExt2JavapoetType.transformSimpleType(idAttributes.get(i).getType()); if (attTypeName.equals(TypeName.get(String.class))) { - params.append("parts[").append(i).append("],"); + params.append("parts[").append(i).append("]"); } else if (attTypeName.equals(TypeName.get(LocalDate.class)) || attTypeName.equals(TypeName.get(LocalDateTime.class))) { args.add(attTypeName); params.append("$T.parse(parts[").append(i).append("])"); @@ -138,6 +140,9 @@ private void addFromMethod() { args.add(attTypeName); params.append("$T.valueOf(parts[").append(i).append("])"); } + if (i < idAttributes.size() - 1) { + params.append(", "); + } } methodBuilder.addStatement("return new $T(" + params.toString() + ")", args.toArray()); builder.addMethod(methodBuilder.build()); diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesData.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesData.java similarity index 98% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesData.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesData.java index 2e7aa50..6fe85bb 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesData.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesData.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence; import com.squareup.javapoet.*; import lombok.AllArgsConstructor; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesGenerator.java similarity index 79% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesGenerator.java index d920b25..cc30348 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesGenerator.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; import java.util.ArrayList; import java.util.Collection; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityBuilder.java similarity index 84% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityBuilder.java index ddf6449..d649ff6 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityBuilder.java @@ -1,16 +1,17 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence; import com.squareup.javapoet.*; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; -import net.cloudappi.apigen.generatorcore.config.entity.Column; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.*; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.ApigenExt2JavapoetType; -import net.cloudappi.apigen.generatorcore.utils.CustomStringUtils; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenAbstractPersistable; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Column; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.*; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.ApigenExt2JavapoetType; +import org.apiaddicts.apitools.apigen.generatorcore.utils.CustomStringUtils; import org.hibernate.annotations.GenericGenerator; import javax.lang.model.element.Modifier; @@ -22,8 +23,8 @@ import static java.util.Objects.isNull; import static java.util.Objects.nonNull; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.*; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.*; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.*; @Slf4j public class EntityBuilder extends AbstractClassBuilder { @@ -94,17 +95,19 @@ public String getPackage() { @Override protected void initialize() { initializeBuilder(); + addConstructorById(); addSetAndGetIdMethods(); addAttributes(); addIsReferenceMethod(); } - private void initializeBuilder() { + private void initializeBuilder() { builder = getClass(entity.getName()) .superclass(getParentClass()) .addAnnotation(Getter.class).addAnnotation(Setter.class) .addAnnotation(getAnnotation(javax.persistence.Entity.class).build()) - .addAnnotation(getAnnotation(Table.class).addMember(NAME, STRING, getTable()).build()); + .addAnnotation(getAnnotation(Table.class).addMember(NAME, STRING, getTable()).build()) + .addAnnotation(NoArgsConstructor.class); } private String getTable() { @@ -119,6 +122,18 @@ private ParameterizedTypeName getParentClass() { ); } + private void addConstructorById() { + String attributeName = getIDName(entity); + if (attributeName == null) return; + TypeName identifierType = getIDTypeName(entity, basePackage); + + builder.addMethod(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addParameter(identifierType, "id") + .addStatement("this.setId(id)") + .build()); + } + private void addSetAndGetIdMethods() { String attributeName = getIDName(entity); if (attributeName == null) return; @@ -269,25 +284,27 @@ private void addUUIDIdGenerationAnnotations(FieldSpec.Builder fieldBuilder) { } private void addNativeIdGenerationAnnotations(FieldSpec.Builder fieldBuilder) { - // @GeneratedValue(generator="native") + // @GeneratedValue(strategy = GenerationType.IDENTITY) // @GenericGenerator(name = "native", strategy = "native") AnnotationSpec.Builder generatedValueAnnotation = getAnnotation(GeneratedValue.class) - .addMember(GENERATOR, STRING, "native"); + .addMember(STRATEGY, ENUM_VALUE, GenerationType.class, GenerationType.IDENTITY); fieldBuilder.addAnnotation(generatedValueAnnotation.build()); - AnnotationSpec.Builder genericGeneratorAnnotationNative = getAnnotation(GenericGenerator.class) - .addMember(NAME, STRING, "native") - .addMember(STRATEGY, STRING, "native"); - fieldBuilder.addAnnotation(genericGeneratorAnnotationNative.build()); } private void addIsReferenceMethod() { - String idAttributeName = getIDName(entity); - List otherAttributes = entity.getAttributes().stream() - .map(Attribute::getName).filter(n -> !n.equals(idAttributeName)).collect(Collectors.toList()); + + Column column = getSimpleIDAttribute(entity).flatMap(a -> Optional.of(a.getColumns().get(0))).orElse(null); + boolean autogenerated = column != null && column.getAutogenerated(); StringBuilder statement = new StringBuilder(); statement.append("return getId() != null"); - otherAttributes.forEach(a -> statement.append(" && ").append(a).append(" == null")); + + if (!autogenerated) { + String idAttributeName = getIDName(entity); + List otherAttributes = entity.getAttributes().stream() + .map(Attribute::getName).filter(n -> !n.equals(idAttributeName)).collect(Collectors.toList()); + otherAttributes.forEach(a -> statement.append(" && ").append(a).append(" == null")); + } builder.addMethod(MethodSpec.methodBuilder("isReference") .addAnnotation(AnnotationSpec.builder(Override.class).build()) diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityRelationManager.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityRelationManager.java similarity index 62% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityRelationManager.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityRelationManager.java index a801473..9e25061 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntityRelationManager.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityRelationManager.java @@ -1,55 +1,59 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence; import com.squareup.javapoet.FieldSpec; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.entity.*; -import net.cloudappi.apigen.generatorcore.generator.persistence.relations.*; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.*; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations.*; +import org.apache.commons.lang3.tuple.Pair; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @Slf4j public class EntityRelationManager { /* > */ - private Map> manyToManyMappedEntities = new ConcurrentHashMap<>(); + private Map> manyToManyMappedEntities = new HashMap<>(); - private Map> manyToOneMappedEntities = new ConcurrentHashMap<>(); + private Map> manyToOneMappedEntities = new HashMap<>(); /* > */ - private Map> oneToManyMappedEntities = new ConcurrentHashMap<>(); + private Map> oneToManyMappedEntities = new HashMap<>(); /* > */ - private Map> oneToOneMappedEntities = new ConcurrentHashMap<>(); - - /* > */ - private Map> remainingAttributes = new HashMap<>(); + private Map> oneToOneMappedEntities = new HashMap<>(); public EntityRelationManager(Collection entities) { detectRelations(entities); } private void detectRelations(Collection entities) { - for (Entity entity : entities) { - for (Attribute attribute : entity.getAttributes()) { - detectRelation(entity, attribute); - } - } - checkRemainingAttributes(); + detectAnyToManyRelations(entities); + detectAnyToOneRelations(entities); } - private void detectRelation(Entity entity, Attribute attribute) { - if (attribute.getRelation() == null) return; + private void detectAnyToManyRelations(Collection entities) { + entities.stream() + .flatMap(e -> e.getAttributes().stream().map(a -> Pair.of(a, e.getName()))) + .filter(p -> Objects.nonNull(p.getLeft().getRelation())) + .filter(p -> p.getLeft().getIsCollection()) + .forEach(p -> identifyAnyToMany(p.getLeft(), p.getRight())); + } - if (attribute.getIsCollection()) { - identifyToMany(attribute, entity.getName()); - } else { - identifyToOne(attribute, entity.getName()); - } + private void detectAnyToOneRelations(Collection entities) { + entities.stream() + .flatMap(e -> e.getAttributes().stream().map(a -> Pair.of(a, e.getName()))) + .filter(p -> Objects.nonNull(p.getLeft().getRelation())) + .filter(p -> !p.getLeft().getIsCollection()) + .forEach(p -> identifyOneToOne(p.getLeft(), p.getRight())); + entities.stream() + .flatMap(e -> e.getAttributes().stream().map(a -> Pair.of(a, e.getName()))) + .filter(p -> Objects.nonNull(p.getLeft().getRelation())) + .filter(p -> !p.getLeft().getIsCollection()) + .forEach(p -> identifyAnyToOne(p.getLeft(), p.getRight())); } - private void identifyToMany(Attribute attribute, String entityName) { + private void identifyAnyToMany(Attribute attribute, String entityName) { Relation relation = attribute.getRelation(); if (relation.getIntermediateTable() == null) { relation.setType(RelationType.ONE_TO_MANY); @@ -64,53 +68,35 @@ private void identifyToMany(Attribute attribute, String entityName) { } } - private void identifyToOne(Attribute attribute, String entityName) { - if (!attribute.getForeignColumns().isEmpty()) { - attribute.getRelation().setType(RelationType.ONE_TO_ONE); - registerOneToOne(attribute, entityName); - } else { - remainingAttributes.putIfAbsent(entityName, new HashMap<>()); - remainingAttributes.get(entityName).put(attribute.getName(), attribute); - } + private void identifyOneToOne(Attribute attribute, String entityName) { + if (attribute.getForeignColumns().isEmpty()) return; + attribute.getRelation().setType(RelationType.ONE_TO_ONE); + registerOneToOne(attribute, entityName); } - private void checkRemainingAttributes() { - for (String entityName : remainingAttributes.keySet()) { - checkIfEntityContainsManyToOneAttributes(entityName); - checkIfEntityContainsOneToOneOwnerAttributes(entityName); - } - } + private void identifyAnyToOne(Attribute attribute, String entityName) { + if (!attribute.getForeignColumns().isEmpty()) return; - private void checkIfEntityContainsManyToOneAttributes(String entityName) { - if (!oneToManyMappedEntities.containsKey(entityName)) { - for (Attribute attribute : remainingAttributes.get(entityName).values()) { + // Try to find a correlation with a one to many + for (Attribute foreignAttribute : oneToManyMappedEntities.getOrDefault(entityName, new HashMap<>()).values()) { + if (columnsMatch(attribute, foreignAttribute)) { attribute.getRelation().setType(RelationType.MANY_TO_ONE); registerManyToOne(attribute, entityName); - } - } else { - for (Attribute attribute : remainingAttributes.get(entityName).values()) { - for (Attribute foreignAttribute : oneToManyMappedEntities.get(entityName).values()) { - if (columnsMatch(attribute, foreignAttribute)) { - attribute.getRelation().setType(RelationType.MANY_TO_ONE); - registerManyToOne(attribute, entityName); - break; - } - } + return; } } - } - private void checkIfEntityContainsOneToOneOwnerAttributes(String entityName) { - if (!oneToOneMappedEntities.containsKey(entityName)) return; - for (Attribute attribute : remainingAttributes.get(entityName).values()) { - for (Attribute foreignAttribute : oneToOneMappedEntities.get(entityName).values()) { - if (columnsMatch(attribute, foreignAttribute)) { - attribute.getRelation().setType(RelationType.ONE_TO_ONE_OWNER); - registerOneToOne(attribute, entityName); - break; - } + // Try to find a correlation with a one to one + for (Attribute foreignAttribute : oneToOneMappedEntities.getOrDefault(entityName, new HashMap<>()).values()) { + if (columnsMatch(attribute, foreignAttribute)) { + attribute.getRelation().setType(RelationType.ONE_TO_ONE_OWNER); + registerOneToOne(attribute, entityName); + return; } } + + // Default to many to one + attribute.getRelation().setType(RelationType.MANY_TO_ONE); } private boolean columnsMatch(Attribute attribute, Attribute foreignAttribute) { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java similarity index 66% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java index ce0783a..bb19f1e 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ColumnRelation.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.relations; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java similarity index 67% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java index 37c36ed..6f80557 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToManyBuilder.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.relations; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations; import com.squareup.javapoet.AnnotationSpec; import javax.persistence.ManyToMany; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.MAPPED_BY; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.MAPPED_BY; public class ManyToManyBuilder extends RelatedFieldBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java similarity index 85% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java index 4286c37..6837943 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToManyOwnerBuilder.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.relations; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations; import com.squareup.javapoet.AnnotationSpec; @@ -7,9 +7,9 @@ import java.util.ArrayList; import java.util.List; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.LITERAL; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.*; public class ManyToManyOwnerBuilder extends RelatedFieldBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java similarity index 87% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java index e83fe97..889d96d 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/ManyToOneBuilder.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.relations; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations; import com.squareup.javapoet.AnnotationSpec; import lombok.extern.slf4j.Slf4j; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java similarity index 69% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java index a4637d8..50278f9 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToManyBuilder.java @@ -1,12 +1,12 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.relations; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations; import com.squareup.javapoet.AnnotationSpec; import lombok.extern.slf4j.Slf4j; import javax.persistence.OneToMany; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.MAPPED_BY; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.MAPPED_BY; @Slf4j public class OneToManyBuilder extends RelatedFieldBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java similarity index 67% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java index 2e63e7c..0516c30 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToOneBuilder.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.relations; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations; import com.squareup.javapoet.AnnotationSpec; import javax.persistence.OneToOne; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.MAPPED_BY; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.MAPPED_BY; public class OneToOneBuilder extends RelatedFieldBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java similarity index 87% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java index 067600c..458fa0b 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/OneToOneOwnerBuilder.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.relations; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations; import com.squareup.javapoet.AnnotationSpec; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java similarity index 70% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java index 24ec597..8fc6d94 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/relations/RelatedFieldBuilder.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.relations; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.relations; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.FieldSpec; @@ -8,9 +8,9 @@ import java.util.ArrayList; import java.util.List; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.NAME; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.REFERENCED_COLUMN_NAME; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.NAME; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.REFERENCED_COLUMN_NAME; @Slf4j public abstract class RelatedFieldBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java similarity index 65% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java index defd022..7e94e96 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoriesGenerator.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.repository; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.repository; import com.squareup.javapoet.TypeName; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; import java.util.ArrayList; import java.util.Collection; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java similarity index 84% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java index 058ab68..b4ed37a 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoryBuilder.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.repository; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.repository; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepository; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; import org.springframework.stereotype.Repository; public class RepositoryBuilder extends AbstractClassBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagerBuilder.java similarity index 66% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagerBuilder.java index 69760fd..1694eb0 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagerBuilder.java @@ -1,12 +1,12 @@ -package net.cloudappi.apigen.generatorcore.generator.service; +package org.apiaddicts.apitools.apigen.generatorcore.generator.service; import com.squareup.javapoet.*; -import net.cloudappi.apigen.archetypecore.core.AbstractRelationsManager; -import net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors; -import net.cloudappi.apigen.archetypecore.exceptions.RelationalErrorsException; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData.AttributeData; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.AbstractRelationsManager; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrors; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrorsException; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData.AttributeData; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -19,8 +19,8 @@ import java.util.Set; import java.util.stream.Collectors; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.ENUM_VALUE; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.PROPAGATION; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.ENUM_VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.PROPAGATION; public class RelationManagerBuilder extends AbstractClassBuilder { @@ -62,8 +62,6 @@ protected void initialize() { addServiceFields(); addCreateOrRetrieveRelationsMethod(); addUpdateRelationsMethod(); - addCreateOrRetrieveMethods(); - addRetrieveMethods(); } private void initializeBuilder() { @@ -97,13 +95,14 @@ private void addCreateOrRetrieveRelationsMethod() { AttributeData attributeData = attributes.get(attribute); if (!attributeData.isOwned()) continue; String capAttribute = StringUtils.capitalize(attribute); + String serviceName = StringUtils.uncapitalize(ServiceBuilder.getName(attributeData.getRelatedEntity())); methodSpecBuilder.beginControlFlow("if ($L.get$L() != null)", paramName, capAttribute); if (attributeData.isCollection()) { - methodSpecBuilder.addStatement("$1L.set$2L($1L.get$2L().stream().map(e -> createOrRetrieve(e, errors)).collect($3T.toSet()))", - paramName, capAttribute, Collectors.class); + methodSpecBuilder.addStatement("$1L.set$2L($1L.get$2L().stream().map(e -> createOrRetrieve(e, $4L, errors)).collect($3T.toSet()))", + paramName, capAttribute, Collectors.class, serviceName); } else { - methodSpecBuilder.addStatement("$1L.set$2L(createOrRetrieve($1L.get$2L(), errors))", paramName, capAttribute); + methodSpecBuilder.addStatement("$1L.set$2L(createOrRetrieve($1L.get$2L(), $3L, errors))", paramName, capAttribute, serviceName); } methodSpecBuilder.endControlFlow(); } @@ -133,15 +132,16 @@ private void addUpdateRelationsMethod() { AttributeData attributeData = attributes.get(attribute); if (!attributeData.isOwned()) continue; String capAttribute = StringUtils.capitalize(attribute); + String serviceName = StringUtils.uncapitalize(ServiceBuilder.getName(attributeData.getRelatedEntity())); methodSpecBuilder.beginControlFlow("if (updateAll || fields.contains($S))", attribute); methodSpecBuilder.beginControlFlow("if ($L.get$L() != null)", paramName, capAttribute); if (attributeData.isCollection()) { methodSpecBuilder.addStatement("$1L.get$2L().clear()", persistedParamName, capAttribute); - methodSpecBuilder.addStatement("$1L.get$3L().addAll($2L.get$3L().stream().map(e -> retrieve(e, errors)).collect($4T.toSet()))", - persistedParamName, paramName, capAttribute, Collectors.class); + methodSpecBuilder.addStatement("$1L.get$3L().addAll($2L.get$3L().stream().map(e -> retrieve(e, $5L, errors)).collect($4T.toSet()))", + persistedParamName, paramName, capAttribute, Collectors.class, serviceName); } else { - methodSpecBuilder.addStatement("$1L.set$3L(retrieve($2L.get$3L(), errors))", persistedParamName, paramName, capAttribute); + methodSpecBuilder.addStatement("$1L.set$3L(retrieve($2L.get$3L(), $4L, errors))", persistedParamName, paramName, capAttribute, serviceName); } methodSpecBuilder.nextControlFlow("else"); methodSpecBuilder.addStatement("$L.set$L(null)", persistedParamName, capAttribute); @@ -154,51 +154,4 @@ private void addUpdateRelationsMethod() { builder.addMethod(methodSpecBuilder.build()); } - - private void addCreateOrRetrieveMethods() { - sortedRelatedEntities.forEach(this::addCreateOrRetrieveMethod); - } - - private void addCreateOrRetrieveMethod(String entityName) { - String serviceName = StringUtils.uncapitalize(ServiceBuilder.getName(entityName)); - TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); - String paramName = StringUtils.uncapitalize(entityName); - MethodSpec methodSpec = MethodSpec.methodBuilder("createOrRetrieve") - .addModifiers(Modifier.PRIVATE) - .addParameter(entityType, paramName) - .addParameter(RelationalErrors.class, "errors") - .returns(entityType) - .beginControlFlow("if ($L.isReference())", paramName) - .addStatement("return retrieve($L, errors)", paramName) - .nextControlFlow("else", paramName) - .beginControlFlow("try") - .addStatement("return $L.create($L)", serviceName, paramName) - .nextControlFlow("catch ($T e)", RelationalErrorsException.class) - .addStatement("errors.merge(e.getRelationalErrors())") - .addStatement("return null") - .endControlFlow() - .endControlFlow() - .build(); - builder.addMethod(methodSpec); - } - - private void addRetrieveMethods() { - sortedRelatedEntities.forEach(this::addRetrieveMethod); - } - - private void addRetrieveMethod(String entityName) { - String serviceName = StringUtils.uncapitalize(ServiceBuilder.getName(entityName)); - TypeName entityType = EntityBuilder.getTypeName(entityName, basePackage); - String paramName = StringUtils.uncapitalize(entityName); - MethodSpec methodSpec = MethodSpec.methodBuilder("retrieve") - .addModifiers(Modifier.PRIVATE) - .addParameter(entityType, paramName) - .addParameter(RelationalErrors.class, "errors") - .returns(entityType) - .addStatement("$1T retrieved = ($2L.getId() == null) ? null : $3L.getOne($2L.getId()).orElse(null)", entityType, paramName, serviceName) - .addStatement("if (retrieved == null) errors.register($T.class, $L.getId())", entityType, paramName) - .addStatement("return retrieved") - .build(); - builder.addMethod(methodSpec); - } } diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagersGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagersGenerator.java similarity index 62% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagersGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagersGenerator.java index edc2256..09e56ea 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagersGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagersGenerator.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.service; +package org.apiaddicts.apitools.apigen.generatorcore.generator.service; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; import java.util.ArrayList; import java.util.Collection; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServiceBuilder.java similarity index 87% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServiceBuilder.java index 2d371c5..58e0b5a 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServiceBuilder.java @@ -1,13 +1,13 @@ -package net.cloudappi.apigen.generatorcore.generator.service; +package org.apiaddicts.apitools.apigen.generatorcore.generator.service; import com.squareup.javapoet.*; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.core.AbstractCrudService; -import net.cloudappi.apigen.archetypecore.core.AbstractRelationsManager; -import net.cloudappi.apigen.archetypecore.core.ApigenMapper; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; -import net.cloudappi.apigen.generatorcore.generator.persistence.repository.RepositoryBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.AbstractCrudService; +import org.apiaddicts.apitools.apigen.archetypecore.core.AbstractRelationsManager; +import org.apiaddicts.apitools.apigen.archetypecore.core.ApigenMapper; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.repository.RepositoryBuilder; import org.apache.commons.lang3.StringUtils; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServicesGenerator.java similarity index 66% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServicesGenerator.java index 133dcfc..e4f947e 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServicesGenerator.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.service; +package org.apiaddicts.apitools.apigen.generatorcore.generator.service; import com.squareup.javapoet.TypeName; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; import java.util.*; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllerBuilder.java similarity index 69% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllerBuilder.java index 587842f..51c2c7a 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllerBuilder.java @@ -1,16 +1,16 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.TypeSpec; import io.swagger.annotations.Api; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointBuilderFactory; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.EndpointBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.EndpointBuilderFactory; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -18,9 +18,9 @@ import javax.lang.model.element.Modifier; import java.util.List; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.TAGS; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.TAGS; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.VALUE; public abstract class ControllerBuilder extends AbstractClassBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java similarity index 66% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java index 5470ae9..b376d94 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllerBuilderFactory.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; public class ControllerBuilderFactory { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllersGenerator.java similarity index 60% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllersGenerator.java index 1a79efc..c47b4bb 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllersGenerator.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; import java.util.ArrayList; import java.util.Collection; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java similarity index 80% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java index 071fd1a..861ec17 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/EntityControllerBuilder.java @@ -1,13 +1,13 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.archetypecore.core.resource.ResourceNamingTranslator; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.generator.mapper.MapperBuilder; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; -import net.cloudappi.apigen.generatorcore.generator.service.ServiceBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.resource.ResourceNamingTranslator; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.generator.mapper.MapperBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.generator.service.ServiceBuilder; import org.springframework.beans.factory.annotation.Autowired; import javax.lang.model.element.Modifier; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java similarity index 73% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java index 3495ff8..a840d49 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ResourceControllerBuilder.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; public class ResourceControllerBuilder extends ControllerBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java similarity index 76% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java index c4b1942..0591e43 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilder.java @@ -1,18 +1,18 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.archetypecore.exceptions.NotImplementedException; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.config.controller.Response; -import net.cloudappi.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.ResourceListResponseBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.ResourceSimpleResponseBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.exceptions.NotImplementedException; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Response; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.ResourceListResponseBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.ResourceSimpleResponseBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java similarity index 79% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java index 4740ebf..6e2b507 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilder.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.TypeName; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.springframework.web.bind.annotation.DeleteMapping; @Slf4j diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java similarity index 81% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java index eaf04f9..bf705e5 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilder.java @@ -1,14 +1,14 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.*; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; -import net.cloudappi.apigen.generatorcore.generator.web.controller.parameters.ParameterBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.controller.parameters.PathParameterBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.controller.parameters.QueryParameterBuilder; -import net.cloudappi.apigen.generatorcore.utils.CustomStringUtils; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.parameters.ParameterBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.parameters.PathParameterBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.parameters.QueryParameterBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.utils.CustomStringUtils; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseStatus; @@ -18,10 +18,10 @@ import java.util.HashSet; import java.util.Set; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.ENUM_VALUE; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.CODE; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.ENUM_VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.CODE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.VALUE; @Slf4j public abstract class EndpointBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java similarity index 86% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java index 28b28d0..3524d7c 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactory.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; public class EndpointBuilderFactory { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java similarity index 79% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java index c40fe12..8b7bb4a 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilder.java @@ -1,14 +1,14 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearchResult; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java similarity index 79% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java index a7aa4f4..2ff58c5 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilder.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; import org.springframework.web.bind.annotation.GetMapping; import java.util.Arrays; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java similarity index 74% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java index bd8c90a..9d78031 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilder.java @@ -1,13 +1,13 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java similarity index 78% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java index 3f74ed0..f2a26e6 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilder.java @@ -1,16 +1,16 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult; -import net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter; -import net.cloudappi.apigen.archetypecore.core.resource.FilterResource; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearchResult; +import org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter; +import org.apiaddicts.apitools.apigen.archetypecore.core.resource.FilterResource; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.EntityListResponseBuilder; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java similarity index 79% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java index 523938f..98db1e6 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilder.java @@ -1,15 +1,15 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input.AllInputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.response.SimpleResponseBuilder; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestAttribute; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java similarity index 83% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java index b83ae23..5c14d2a 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilder.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.parameters; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.parameters; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; -import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.Openapi2JavapoetType; import java.util.List; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java similarity index 71% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java index fe43651..695d80e 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/PathParameterBuilder.java @@ -1,12 +1,12 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.parameters; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.parameters; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; import org.springframework.web.bind.annotation.PathVariable; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.VALUE; public class PathParameterBuilder extends ParameterBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java similarity index 78% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java index f54a7f9..6f2288e 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/QueryParameterBuilder.java @@ -1,16 +1,16 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.parameters; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.parameters; import com.fasterxml.jackson.databind.node.ArrayNode; import com.squareup.javapoet.AnnotationSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; import org.springframework.web.bind.annotation.RequestParam; import java.util.ArrayList; import java.util.List; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.*; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.LITERAL; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.*; public class QueryParameterBuilder extends ParameterBuilder { diff --git a/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourceBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourceBuilder.java new file mode 100644 index 0000000..e904769 --- /dev/null +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourceBuilder.java @@ -0,0 +1,7 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource; + +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; + +public abstract class ResourceBuilder extends AbstractClassBuilder { + +} diff --git a/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourcesData.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourcesData.java new file mode 100644 index 0000000..a75d3a5 --- /dev/null +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourcesData.java @@ -0,0 +1,34 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource; + +import com.squareup.javapoet.TypeName; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input.InputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.OutputResourceBuilder; + +import java.util.*; + +public class ResourcesData { + private Map> resourcesToEntity = new HashMap<>(); + private Map> entityToResources = new HashMap<>(); + + public ResourcesData(List inputResourceBuilders, List outputResourceBuilders) { + inputResourceBuilders.forEach(builder -> { + String entityName = builder.getEntityName(); + resourcesToEntity.putIfAbsent(entityName, new HashSet<>()); + resourcesToEntity.get(entityName).add(builder.getTypeName()); + }); + outputResourceBuilders.forEach(builder -> { + String entityName = builder.getEntityName(); + entityToResources.putIfAbsent(entityName, new HashSet<>()); + entityToResources.get(entityName).add(builder.getTypeName()); + }); + } + + + public Set getInputResources(String entityName) { + return resourcesToEntity.getOrDefault(entityName, Collections.emptySet()); + } + + public Set getOutputResources(String entityName) { + return entityToResources.getOrDefault(entityName, Collections.emptySet()); + } +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java similarity index 61% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java index 87ca5a3..401b201 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourcesGenerator.java @@ -1,16 +1,16 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.config.controller.Request; -import net.cloudappi.apigen.generatorcore.config.controller.Response; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; -import net.cloudappi.apigen.generatorcore.generator.web.resource.input.InputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.input.InputResourceBuilderFactory; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.OutputResourceBuilder; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.OutputResourceBuilderFactory; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Request; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Response; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input.InputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input.InputResourceBuilderFactory; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.OutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.OutputResourceBuilderFactory; import java.util.ArrayList; import java.util.Collection; @@ -30,7 +30,7 @@ public ResourcesGenerator(Collection controllers, String basePackage processResponse(controller, endpoint, basePackage); } } - resourcesData = new ResourcesData(inputBuilders); + resourcesData = new ResourcesData(inputBuilders, outputBuilders); } private void processRequest(Controller controller, Endpoint endpoint, String basePackage) { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java similarity index 86% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java index 3865aab..4efa210 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilder.java @@ -1,16 +1,18 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.input; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.squareup.javapoet.*; +import com.squareup.javapoet.TypeSpec.Builder; + import lombok.Data; import lombok.NoArgsConstructor; -import net.cloudappi.apigen.generatorcore.config.controller.Attribute; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.config.controller.Request; -import net.cloudappi.apigen.generatorcore.config.validation.Validation; -import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Attribute; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Request; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.Validation; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.Openapi2JavapoetType; import org.apache.commons.lang3.StringUtils; import javax.validation.Valid; @@ -19,10 +21,10 @@ import java.util.Set; import static java.util.Objects.nonNull; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.LITERAL; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.MODE; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.LITERAL; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.MODE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.VALUE; public class AllInputResourceBuilder extends InputResourceBuilder { @@ -104,7 +106,6 @@ private void addAttributes() { } private void addAttributes(List attributes, TypeSpec.Builder builder, TypeName parentType) { - for (Attribute attribute : attributes) { boolean requiresNestedObject = requiredNestedObject(attribute); String javaName = attribute.getEntityFieldName(); @@ -112,7 +113,7 @@ private void addAttributes(List attributes, TypeSpec.Builder builder, boolean nested = false; if (requiresNestedObject) { - type = createNestedObject(javaName, attribute.getAttributes(), parentType); + type = createNestedObject(javaName, attribute.getAttributes(), parentType, builder); nested = true; } else { type = Openapi2JavapoetType.transformSimpleType(attribute.getType(), attribute.getFormat()); @@ -134,7 +135,7 @@ private boolean requiredNestedObject(Attribute attribute) { return Openapi2JavapoetType.TYPE_OBJECT.equals(attribute.getType()) || Openapi2JavapoetType.TYPE_ARRAY.equals(attribute.getType()); } - private TypeName createNestedObject(String javaName, List attributes, TypeName parentType) { + private TypeName createNestedObject(String javaName, List attributes, TypeName parentType, Builder builder) { String nestedName = StringUtils.capitalize(javaName); TypeName type = ((ClassName) parentType).nestedClass(nestedName); TypeSpec.Builder nestedBuilder = getPublicInnerClass(nestedName).addAnnotation(Data.class); @@ -164,7 +165,7 @@ private TypeName createIdentifierNestedObject(String javaName, TypeName type, Ty nestedAttributeBuilder.addMethod(constructor); addAttribute(nestedAttributeType, nestedAttributeName, nestedAttributeName, Collections.emptyList(), false, nestedAttributeBuilder); builder.addType(nestedAttributeBuilder.build()); - return type; + return nestedAttributeType; } diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java similarity index 53% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java index 4a0f9e6..c2d8752 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/InputResourceBuilder.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.input; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.ResourceBuilder; public abstract class InputResourceBuilder extends ResourceBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java similarity index 55% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java index 4e64e68..e98e78c 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/InputResourceBuilderFactory.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.input; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; public class InputResourceBuilderFactory { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java similarity index 78% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java index 62f5b45..ade44f0 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilder.java @@ -1,18 +1,18 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.output; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output; import com.fasterxml.jackson.annotation.JsonProperty; import com.squareup.javapoet.*; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.resource.ApigenEntityOutResource; -import net.cloudappi.apigen.generatorcore.config.controller.Attribute; -import net.cloudappi.apigen.generatorcore.config.controller.Response; -import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; +import org.apiaddicts.apitools.apigen.archetypecore.core.resource.ApigenEntityOutResource; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Attribute; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Response; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.Openapi2JavapoetType; import java.util.List; import java.util.Set; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.VALUE; public class EntityOutputResourceBuilder extends OutputResourceBuilder { @@ -38,6 +38,16 @@ private static String getPackage(String entityName, String basePackage) { return concatPackage(basePackage, entityName, "web"); } + @Override + public TypeName getTypeName() { + return getTypeName(entityName, basePackage); + } + + @Override + public String getEntityName() { + return entityName; + } + @Override public String getPackage() { return getPackage(entityName, basePackage); diff --git a/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java new file mode 100644 index 0000000..b522e0f --- /dev/null +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilder.java @@ -0,0 +1,10 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output; + +import com.squareup.javapoet.TypeName; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.ResourceBuilder; + +public abstract class OutputResourceBuilder extends ResourceBuilder { + public abstract String getEntityName(); + + public abstract TypeName getTypeName(); +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java similarity index 62% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java index f257946..6791c36 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/OutputResourceBuilderFactory.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.output; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.config.controller.Response; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Response; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; public class OutputResourceBuilderFactory { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java similarity index 91% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java index 393c16a..ead98e8 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilder.java @@ -1,19 +1,19 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.output; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output; import com.fasterxml.jackson.annotation.JsonProperty; import com.squareup.javapoet.*; import lombok.Data; -import net.cloudappi.apigen.generatorcore.config.controller.*; -import net.cloudappi.apigen.generatorcore.generator.common.Openapi2JavapoetType; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.*; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.Openapi2JavapoetType; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.apache.commons.lang3.StringUtils; import javax.validation.Valid; import java.util.List; import java.util.Set; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.VALUE; public class ResourceOutputResourceBuilder extends OutputResourceBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java similarity index 70% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java index e0ca647..d61bdb0 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/EntityListResponseBuilder.java @@ -1,15 +1,15 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; public class EntityListResponseBuilder extends ListResponseBuilder { private String entityName; - public EntityListResponseBuilder(String entityName, String listName, String basePackage) { - super(listName, EntityOutputResourceBuilder.getTypeName(entityName, basePackage), basePackage); + public EntityListResponseBuilder(String entityName, String jsonListName, String basePackage) { + super(jsonListName, EntityOutputResourceBuilder.getTypeName(entityName, basePackage), basePackage); this.entityName = entityName; } diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ListResponseBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ListResponseBuilder.java similarity index 69% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ListResponseBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ListResponseBuilder.java index c3c090f..2258e3a 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ListResponseBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ListResponseBuilder.java @@ -1,26 +1,29 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.fasterxml.jackson.annotation.JsonProperty; import com.squareup.javapoet.*; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.archetypecore.core.responses.content.ApiListResponseContent; -import org.codehaus.plexus.util.StringUtils; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.content.ApiListResponseContent; +import org.apiaddicts.apitools.apigen.generatorcore.utils.CustomStringUtils; +import javax.lang.model.element.Modifier; import java.util.List; -import static net.cloudappi.apigen.generatorcore.generator.common.Formats.STRING; -import static net.cloudappi.apigen.generatorcore.generator.common.Members.VALUE; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Formats.STRING; +import static org.apiaddicts.apitools.apigen.generatorcore.generator.common.Members.VALUE; public abstract class ListResponseBuilder extends ResponseBuilder { protected String basePackage; - protected String listName; + protected String jsonListName; + protected String javaListName; protected TypeName resourceType; protected TypeSpec.Builder contentBuilder; - public ListResponseBuilder(String listName, TypeName resourceType, String basePackage) { - this.listName = listName; + public ListResponseBuilder(String jsonListName, TypeName resourceType, String basePackage) { + this.jsonListName = jsonListName; + this.javaListName = CustomStringUtils.snakeCaseToCamelCase(jsonListName); this.resourceType = resourceType; this.basePackage = basePackage; } @@ -55,8 +58,8 @@ private void addConstructor() { .addParameter(ParameterizedTypeName.get( ClassName.get(List.class), resourceType - ), listName) - .addStatement("super(new " + getContentName() + "(" + listName + "))") + ), javaListName) + .addStatement("super(new " + getContentName() + "(" + javaListName + "))") .build(); builder.addMethod(constructor); } @@ -88,15 +91,17 @@ private void addContentConstructor() { .addParameter(ParameterizedTypeName.get( ClassName.get(List.class), resourceType - ), listName) - .addStatement("super(" + listName + ")") + ), javaListName) + .addStatement("super(" + javaListName + ")") .build(); contentBuilder.addMethod(constructor); } private void addContentGetter() { - MethodSpec get = MethodSpec.methodBuilder("get" + StringUtils.capitalise(listName)) - .addAnnotation(getAnnotation(JsonProperty.class).addMember(VALUE, STRING, listName).build()) + MethodSpec get = MethodSpec.methodBuilder("getContent") + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .addAnnotation(getAnnotation(JsonProperty.class).addMember(VALUE, STRING, jsonListName).build()) .returns(ParameterizedTypeName.get( ClassName.get(List.class), resourceType diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java similarity index 83% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java index 55b4e4b..34a951e 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceListResponseBuilder.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; public class ResourceListResponseBuilder extends ListResponseBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java similarity index 84% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java index 5b8dbc3..f30d816 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilder.java @@ -1,14 +1,14 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.ResourceOutputResourceBuilder; public class ResourceSimpleResponseBuilder extends ResponseBuilder { diff --git a/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponseBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponseBuilder.java new file mode 100644 index 0000000..167f176 --- /dev/null +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponseBuilder.java @@ -0,0 +1,7 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; + +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; + +public abstract class ResponseBuilder extends AbstractClassBuilder { + +} diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java similarity index 72% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java index 8351486..2106d7f 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponseBuilderFactory.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.config.controller.Response; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Response; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; public class ResponseBuilderFactory { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGenerator.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponsesGenerator.java similarity index 63% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGenerator.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponsesGenerator.java index 297d122..d274ef5 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGenerator.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponsesGenerator.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.config.controller.Response; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Response; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractGenerator; import java.util.ArrayList; import java.util.Collection; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java similarity index 88% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java index aa1cee0..071b7fd 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/SimpleResponseBuilder.java @@ -1,12 +1,12 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import lombok.Data; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; public class SimpleResponseBuilder extends ResponseBuilder { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/OpenAPIExtended.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/OpenAPIExtended.java similarity index 86% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/OpenAPIExtended.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/OpenAPIExtended.java index cb9fcec..40967fb 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/OpenAPIExtended.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/OpenAPIExtended.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.spec; +package org.apiaddicts.apitools.apigen.generatorcore.spec; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -8,14 +8,14 @@ import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.media.Schema; import lombok.Getter; -import net.cloudappi.apigen.generatorcore.spec.components.ApigenBinding; -import net.cloudappi.apigen.generatorcore.spec.components.ApigenModel; -import net.cloudappi.apigen.generatorcore.spec.components.ApigenProject; +import org.apiaddicts.apitools.apigen.generatorcore.spec.components.ApigenBinding; +import org.apiaddicts.apitools.apigen.generatorcore.spec.components.ApigenModel; +import org.apiaddicts.apitools.apigen.generatorcore.spec.components.ApigenProject; import java.util.HashMap; import java.util.Map; -import static net.cloudappi.apigen.generatorcore.spec.components.Extensions.*; +import static org.apiaddicts.apitools.apigen.generatorcore.spec.components.Extensions.*; @Getter public class OpenAPIExtended { diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenBinding.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenBinding.java similarity index 54% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenBinding.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenBinding.java index 8f581e0..432053d 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenBinding.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenBinding.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.spec.components; +package org.apiaddicts.apitools.apigen.generatorcore.spec.components; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenModel.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenModel.java similarity index 95% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenModel.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenModel.java index 38d8f26..3544dfb 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenModel.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenModel.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.spec.components; +package org.apiaddicts.apitools.apigen.generatorcore.spec.components; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenProject.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenProject.java similarity index 84% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenProject.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenProject.java index fe33c48..629fd05 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/ApigenProject.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/ApigenProject.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.spec.components; +package org.apiaddicts.apitools.apigen.generatorcore.spec.components; import lombok.Data; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/Extensions.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/Extensions.java similarity index 81% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/Extensions.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/Extensions.java index d9509d2..9d5c70c 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/spec/components/Extensions.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/spec/components/Extensions.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.spec.components; +package org.apiaddicts.apitools.apigen.generatorcore.spec.components; public class Extensions { - public Extensions() { + private Extensions() { // Intentional blank } diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtils.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/CustomStringUtils.java similarity index 96% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtils.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/CustomStringUtils.java index a553029..5c9916b 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtils.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/CustomStringUtils.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.utils; +package org.apiaddicts.apitools.apigen.generatorcore.utils; import org.apache.commons.lang3.StringUtils; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/Mapping.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/Mapping.java similarity index 96% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/Mapping.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/Mapping.java index 0af6a43..13f3d50 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/Mapping.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/Mapping.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.utils; +package org.apiaddicts.apitools.apigen.generatorcore.utils; import lombok.Data; import org.apache.commons.lang3.StringUtils; diff --git a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/ZipUtils.java b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/ZipUtils.java similarity index 98% rename from generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/ZipUtils.java rename to generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/ZipUtils.java index f9c18c6..78565cc 100644 --- a/generator-core/src/main/java/net/cloudappi/apigen/generatorcore/utils/ZipUtils.java +++ b/generator-core/src/main/java/org/apiaddicts/apitools/apigen/generatorcore/utils/ZipUtils.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.utils; +package org.apiaddicts.apitools.apigen.generatorcore.utils; import lombok.extern.slf4j.Slf4j; import org.springframework.web.multipart.MultipartFile; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java b/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java deleted file mode 100644 index 31480f5..0000000 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.cloudappi.apigen.generatorcore.config.mapper; - -import net.cloudappi.apigen.generatorcore.generator.mapper.MapperBuilder; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - - -public class MapperBuilderObjectMother { - - public static MapperBuilder createMapper(String entityName, String basePackage, String basicAttribute, String relatedEntityName) { - Set basicAttributes = new HashSet<>(Collections.singletonList(basicAttribute)); - Set relatedEntities = new HashSet<>(Collections.singletonList(relatedEntityName)); - return new MapperBuilder(entityName, basePackage, basicAttributes, relatedEntities, Collections.emptySet(), null); - } - -} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/ConfigurationObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/ConfigurationObjectMother.java similarity index 88% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/ConfigurationObjectMother.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/ConfigurationObjectMother.java index e5d5163..547c1c9 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/ConfigurationObjectMother.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/ConfigurationObjectMother.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config; +package org.apiaddicts.apitools.apigen.generatorcore.config; public class ConfigurationObjectMother { diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/ControllerObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/ControllerObjectMother.java similarity index 93% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/ControllerObjectMother.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/ControllerObjectMother.java index fe33539..6b39e57 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/ControllerObjectMother.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/ControllerObjectMother.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; import java.util.ArrayList; import java.util.List; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java similarity index 97% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java index 02ee5ce..0178922 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/EndpointBaseResponseObjectMother.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; import java.util.ArrayList; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java similarity index 87% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java index 4f5f3d7..ea53945 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/controller/EndpointRequestObjectMother.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.config.controller; +package org.apiaddicts.apitools.apigen.generatorcore.config.controller; import java.util.ArrayList; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/entity/EntityObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/EntityObjectMother.java similarity index 77% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/entity/EntityObjectMother.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/EntityObjectMother.java index 12bfceb..d213e49 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/entity/EntityObjectMother.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/entity/EntityObjectMother.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.config.entity; +package org.apiaddicts.apitools.apigen.generatorcore.config.entity; -import net.cloudappi.apigen.generatorcore.config.validation.Validation; -import net.cloudappi.apigen.generatorcore.config.validation.ValidationType; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.Validation; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.ValidationType; import java.math.BigDecimal; import java.util.*; @@ -17,6 +17,23 @@ public static Entity createSimpleEntityWithStringAsPrimaryKey() { primaryKeyAttribute.setColumns(Arrays.asList(new Column("primaryKeyColumn", true, true, null))); return new Entity("SimpleTestEntity", "", Collections.singletonList(primaryKeyAttribute)); } + + public static Entity createSimpleEntityWithComposedID() { + List attributeList = new ArrayList<>(); + List composedAttributes = new ArrayList<>(); + + Attribute composedIdAttribute = new Attribute("id", "ComposedID"); + Attribute composedIdSAttribute = new Attribute("idS", "String"); + composedIdSAttribute.setColumns(Arrays.asList(new Column("id_s"))); + Attribute composedIdNAttribute = new Attribute("idN", "Long"); + composedIdNAttribute.setColumns(Arrays.asList(new Column("id_n"))); + composedAttributes.addAll(Arrays.asList(composedIdSAttribute,composedIdNAttribute)); + composedIdAttribute.setAttributes(composedAttributes); + + attributeList.add(composedIdAttribute); + + return new Entity("TestEntity", "tableName", attributeList); + } public static Entity createEntityWithSimpleAttributes() { List attributeList = new ArrayList<>(); @@ -236,7 +253,7 @@ public static Entity createEntityWithAttributeValidations() { return new Entity("TestEntityWithAttributeValidations", "tableName", attributeList); } - public static Entity createEntitiesSequenceIdAttribute() { + public static Entity createEntityWithSequenceIdAttribute() { List attributeList = new ArrayList<>(); Column column = new Column(); @@ -250,7 +267,73 @@ public static Entity createEntitiesSequenceIdAttribute() { attributeList.add(longAttribute); - return new Entity("TestEntityWithSequenceIdAttribute", "tableName", attributeList); + return new Entity("TestEntity", "tableName", attributeList); + } + + public static Entity withManyToOne(String entityName, String fieldName, String relatedEntity, String columnName) { + + List attributes = new LinkedList<>(); + + Attribute attribute = new Attribute(fieldName, relatedEntity); + attribute.setIsCollection(false); + + Column column = new Column(); + column.setName(columnName); + attribute.setColumns(Arrays.asList(column)); + + Relation relationOwner = new Relation(relatedEntity); + attribute.setRelation(relationOwner); + + attributes.add(attribute); + + return new Entity(entityName, entityName, attributes); } + public static Entity createEntityWithAutogeneratedLongId() { + List attributeList = new ArrayList<>(); + + Column column = new Column(); + column.setName("id"); + column.setPrimaryKey(true); + column.setAutogenerated(true); + + Attribute longAttribute = new Attribute("id", "Long"); + longAttribute.setColumns(Arrays.asList(column)); + + attributeList.add(longAttribute); + + return new Entity("TestEntity", "tableName", attributeList); + } + + public static Entity createEntityWithAutogeneratedStringId() { + List attributeList = new ArrayList<>(); + + Column column = new Column(); + column.setName("id"); + column.setPrimaryKey(true); + column.setAutogenerated(true); + + Attribute longAttribute = new Attribute("id", "String"); + longAttribute.setColumns(Arrays.asList(column)); + + attributeList.add(longAttribute); + + return new Entity("TestEntity", "tableName", attributeList); + } + + public static Entity createEntityWithoutAutogeneratedId() { + List attributeList = new ArrayList<>(); + + Column column = new Column(); + column.setName("id"); + column.setPrimaryKey(true); + column.setAutogenerated(false); + + Attribute longAttribute = new Attribute("id", "Long"); + longAttribute.setColumns(Arrays.asList(column)); + + attributeList.add(longAttribute); + + return new Entity("TestEntity", "tableName", attributeList); + } } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java similarity index 94% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java index 4501f5c..58931db 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/extractors/ConfigurationExtractorTest.java @@ -1,24 +1,24 @@ -package net.cloudappi.apigen.generatorcore.config.extractors; +package org.apiaddicts.apitools.apigen.generatorcore.config.extractors; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.parser.OpenAPIV3Parser; import io.swagger.v3.parser.core.models.ParseOptions; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; -import net.cloudappi.apigen.generatorcore.config.controller.Request; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.validation.Validation; -import net.cloudappi.apigen.generatorcore.config.validation.ValidationType; -import net.cloudappi.apigen.generatorcore.spec.OpenAPIExtended; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Request; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.Validation; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.ValidationType; +import org.apiaddicts.apitools.apigen.generatorcore.spec.OpenAPIExtended; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.util.List; -import static net.cloudappi.apigen.generatorcore.config.controller.Endpoint.Method.*; +import static org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint.Method.*; import static org.junit.jupiter.api.Assertions.*; // TODO #14909 refactor tests to use individualized api fragments diff --git a/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java new file mode 100644 index 0000000..b0a84d6 --- /dev/null +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/mapper/MapperBuilderObjectMother.java @@ -0,0 +1,29 @@ +package org.apiaddicts.apitools.apigen.generatorcore.config.mapper; + +import com.squareup.javapoet.TypeName; +import org.apiaddicts.apitools.apigen.generatorcore.generator.mapper.MapperBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.ComposedIdBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output.EntityOutputResourceBuilder; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + + +public class MapperBuilderObjectMother { + + public static MapperBuilder createMapperWithDefaultOutResource(String entityName, String basePackage, String basicAttribute, String relatedEntityName) { + Set basicAttributes = new HashSet<>(Collections.singletonList(basicAttribute)); + Set relatedEntities = new HashSet<>(Collections.singletonList(relatedEntityName)); + TypeName outResource = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + return new MapperBuilder(entityName, basePackage, basicAttributes, relatedEntities, Collections.emptySet(), new HashSet<>(Collections.singletonList(outResource)), TypeName.get(Long.class)); + } + public static MapperBuilder createMapperWithComposedID(String entityName, String basePackage, String basicAttribute, String relatedEntityName) { + Set basicAttributes = new HashSet<>(Collections.singletonList(basicAttribute)); + Set relatedEntities = new HashSet<>(Collections.singletonList(relatedEntityName)); + TypeName outResource = EntityOutputResourceBuilder.getTypeName(entityName, basePackage); + TypeName id = ComposedIdBuilder.getTypeName(entityName, basePackage); + return new MapperBuilder(entityName, basePackage, basicAttributes, relatedEntities, Collections.emptySet(), new HashSet<>(Collections.singletonList(outResource)), id); + } + +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/parameter/ParameterObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/parameter/ParameterObjectMother.java similarity index 96% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/parameter/ParameterObjectMother.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/parameter/ParameterObjectMother.java index ca8a89f..af4e2e9 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/config/parameter/ParameterObjectMother.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/config/parameter/ParameterObjectMother.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.config.parameter; +package org.apiaddicts.apitools.apigen.generatorcore.config.parameter; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; -import net.cloudappi.apigen.generatorcore.config.validation.Validation; -import net.cloudappi.apigen.generatorcore.config.validation.ValidationType; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.Validation; +import org.apiaddicts.apitools.apigen.generatorcore.config.validation.ValidationType; import java.math.BigDecimal; import java.util.ArrayList; diff --git a/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/GitIgnoreFileGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/GitIgnoreFileGeneratorTest.java new file mode 100644 index 0000000..50bed67 --- /dev/null +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/GitIgnoreFileGeneratorTest.java @@ -0,0 +1,26 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class GitIgnoreFileGeneratorTest { + + @Test + void thatGenerates(@TempDir File projectFolder) throws IOException { + GitIgnoreGenerator.generate(projectFolder); + assertGitIgnoreExists(projectFolder); + } + + private void assertGitIgnoreExists(File projectFolder) { + Path path = Paths.get(projectFolder.getPath(), "/.gitignore"); + assertTrue(Files.exists(path), ".gitignore file not generated"); + } +} \ No newline at end of file diff --git a/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/LombokConfigFileGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/LombokConfigFileGeneratorTest.java new file mode 100644 index 0000000..11674ed --- /dev/null +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/LombokConfigFileGeneratorTest.java @@ -0,0 +1,26 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class LombokConfigFileGeneratorTest { + + @Test + void thatGenerates(@TempDir File projectFolder) throws IOException { + LombokConfigGenerator.generate(projectFolder); + assertLombokConfigExists(projectFolder); + } + + private void assertLombokConfigExists(File projectFolder) { + Path path = Paths.get(projectFolder.getPath(), "/lombok.config"); + assertTrue(Files.exists(path), "lombok.config file not generated"); + } +} \ No newline at end of file diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/PomFileGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomFileGeneratorTest.java similarity index 76% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/PomFileGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomFileGeneratorTest.java index c8a3070..86b53b9 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/PomFileGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomFileGeneratorTest.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.config.ConfigurationObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.ConfigurationObjectMother; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -27,9 +27,9 @@ void prepareTest() { void thatGenerates(@TempDir File projectFolder) throws IOException { PomGenerator.generate( configuration, - "net.cloudappi.apigen", + "org.apiaddicts.apitools.apigen", "archetype-parent-spring-boot", - "0.0.1-SNAPSHOT", + "0.0.3-SNAPSHOT", projectFolder ); assertPomExists(projectFolder); diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java similarity index 84% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java index 24d1433..863e5f4 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/ProjectStructureGeneratorTest.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.config.ConfigurationObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.ConfigurationObjectMother; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java similarity index 88% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java index 877154e..12b7fae 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootBaseGeneratorTest.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.config.ConfigurationObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.ConfigurationObjectMother; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java similarity index 86% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java index 23a89d0..008667b 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/SpringBootContextTestGeneratorTest.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.base; +package org.apiaddicts.apitools.apigen.generatorcore.generator.base; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.config.ConfigurationObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.config.ConfigurationObjectMother; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java similarity index 95% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java index 892ccc0..10dcb42 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/ApigenExt2JavapoetTypeTests.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java similarity index 98% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java index aed51ab..35fec58 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/common/Openapi2JavapoetTypeTest.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.common; +package org.apiaddicts.apitools.apigen.generatorcore.generator.common; import org.junit.jupiter.api.Test; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilderTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperBuilderTest.java similarity index 51% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilderTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperBuilderTest.java index 7671ec6..6f59c86 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperBuilderTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperBuilderTest.java @@ -1,27 +1,35 @@ -package net.cloudappi.apigen.generatorcore.generator.mapper; +package org.apiaddicts.apitools.apigen.generatorcore.generator.mapper; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.mapper.MapperBuilderObjectMother; + +import org.apiaddicts.apitools.apigen.generatorcore.config.mapper.MapperBuilderObjectMother; + import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import javax.lang.model.element.Modifier; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import static org.junit.jupiter.api.Assertions.assertEquals; class MapperBuilderTest { private static TypeSpec generatedMapper; + private static TypeSpec generatedMapperWithComposedId; @BeforeAll static void prepareTest() { generatedMapper = MapperBuilderObjectMother - .createMapper("EntityName", "the.base.package", "simpleAttribute", "RelatedEntity") + .createMapperWithDefaultOutResource("EntityName", "the.base.package", "simpleAttribute", "RelatedEntity") .build(); + generatedMapperWithComposedId = MapperBuilderObjectMother + .createMapperWithComposedID("EntityNameWithComposedID", "the.base.package", "simpleAttribute", "RelatedEntity") + .build(); } @Test @@ -30,12 +38,32 @@ void givenValidAttributes_whenBuildMapper_thenFileStructureIsCorrect() { assertEquals(TypeSpec.Kind.INTERFACE, generatedMapper.kind, "Interface declaration is wrong"); assertEquals("EntityNameMapper", generatedMapper.name, "The name is wrong"); assertEquals(1, generatedMapper.annotations.size(), "Number of annotations is wrong"); - assertEquals(4, generatedMapper.methodSpecs.size(), "Number of methods is wrong"); + assertEquals(5, generatedMapper.methodSpecs.size(), "Number of methods is wrong"); + } + + @Test + void givenValidAttributesWithComposedID_whenBuildMapper_thenMapMethodsAreCorrect() { + assertEquals(7, generatedMapperWithComposedId.methodSpecs.size(), "Number of methods is wrong"); + assertEquals("public default java.lang.String map(\n" + + " the.base.package.entitynamewithcomposedid.EntityNameWithComposedIDID id) {\n" + + " return id.toString();\n" + + "}\n" + + "",generatedMapperWithComposedId.methodSpecs.get(4).toString(),"The map method is wrong"); + assertEquals("public default the.base.package.entitynamewithcomposedid.EntityNameWithComposedIDID map(\n" + + " java.lang.String id) {\n" + + " return the.base.package.entitynamewithcomposedid.EntityNameWithComposedIDID.from(id);\n" + + "}\n" + + "",generatedMapperWithComposedId.methodSpecs.get(5).toString(),"The map method is wrong"); + assertEquals("public default the.base.package.entitynamewithcomposedid.EntityNameWithComposedID mapToEntity(\n" + + " java.lang.String id) {\n" + + " return new the.base.package.entitynamewithcomposedid.EntityNameWithComposedID(map(id));\n" + + "}\n" + + "",generatedMapperWithComposedId.methodSpecs.get(6).toString(),"The map method to transform an id to an entity is wrong"); } @Test void givenValidAttributes_whenBuildMapper_thenPackageIsCorrect() { - MapperBuilder builder = new MapperBuilder("EntityName", "the.base.package", Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), null); + MapperBuilder builder = new MapperBuilder("EntityName", "the.base.package", Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(),null); assertEquals("the.base.package.entityname", builder.getPackage(), "The package is wrong"); } @@ -47,7 +75,7 @@ void givenValidAttributes_whenBuildMapper_thenTypeNameIsCorrect() { @Test void givenValidAttributes_whenBuildMapper_thenSuperinterfaceIsCorrect() { - assertEquals("[net.cloudappi.apigen.archetypecore.core.ApigenMapper]", generatedMapper.superinterfaces.toString()); + assertEquals("[org.apiaddicts.apitools.apigen.archetypecore.core.ApigenMapper]", generatedMapper.superinterfaces.toString()); } @Test @@ -84,12 +112,38 @@ void givenValidAttributes_whenBuildMapper_thenGeneratedUpdateBasicDataIsCorrect( "[" + "@java.lang.Override, " + "@org.mapstruct.BeanMapping(ignoreByDefault = true), " + - "@org.mapstruct.Mappings(@org.mapstruct.Mapping(source = \"simpleAttribute\", target = \"simpleAttribute\"))" + + "@org.mapstruct.Mapping(source = \"simpleAttribute\", target = \"simpleAttribute\")" + + "]", + methodSpec.annotations.toString()); + assertEquals("[the.base.package.entityname.EntityName source, @org.mapstruct.MappingTarget the.base.package.entityname.EntityName target]" + , methodSpec.parameters.toString()); + assertEquals("void", methodSpec.returnType.toString()); + } + + @Test + void givenMultipleValidAttributes_whenBuildMapper_thenGeneratedUpdateBasicDataIsCorrect() { + TypeSpec spec = new MapperBuilder("EntityName", "the.base.package", new HashSet<>(Arrays.asList("one", "two")), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), TypeName.get(String.class)).build(); + MethodSpec methodSpec = spec.methodSpecs.get(0); + assertEquals("" + + "[" + + "@java.lang.Override, " + + "@org.mapstruct.BeanMapping(ignoreByDefault = true), " + + "@org.mapstruct.Mapping(source = \"one\", target = \"one\"), " + + "@org.mapstruct.Mapping(source = \"two\", target = \"two\")" + "]", methodSpec.annotations.toString()); assertEquals("[the.base.package.entityname.EntityName source, @org.mapstruct.MappingTarget the.base.package.entityname.EntityName target]" , methodSpec.parameters.toString()); assertEquals("void", methodSpec.returnType.toString()); } + + @Test + void givenValidAttributes_whenBuildMapper_thenGeneratedIdToEntityIsCorrect() { + assertEquals("public default the.base.package.entityname.EntityName toEntity(java.lang.Long id) {\n" + + " if (id == null) return null;\n" + + " return new the.base.package.entityname.EntityName(id);\n" + + "}\n", + generatedMapper.methodSpecs.get(4).toString(), "idToEntity is wrong"); + } } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java similarity index 74% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java index 98fc028..3580132 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/mapper/MapperGeneratorTest.java @@ -1,9 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.mapper; +package org.apiaddicts.apitools.apigen.generatorcore.generator.mapper; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; -import net.cloudappi.apigen.generatorcore.generator.web.resource.ResourcesData; +import com.squareup.javapoet.TypeName; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.EntityObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.ResourcesData; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -27,7 +28,7 @@ void givenValidEntitiesData_whenGenerateMapper_thenMapperFileIsGenerated(@TempDi Entity testEntity = EntityObjectMother.createEntityWithSimpleAttributes(); EntitiesData entitiesData = createMockedEntitiesData(testEntity.getName()); - ResourcesData resourcesData = new ResourcesData(Collections.emptyList()); + ResourcesData resourcesData = new ResourcesData(Collections.emptyList(), Collections.emptyList()); boolean success = new MappersGenerator(Arrays.asList(testEntity), entitiesData, resourcesData, "the.base.package").generate(filesRootPath); @@ -45,6 +46,7 @@ void givenValidEntitiesData_whenGenerateMapper_thenMapperFileIsGenerated(@TempDi private EntitiesData createMockedEntitiesData(String entityName) { EntitiesData mockedEntitiesData = mock(EntitiesData.class); when(mockedEntitiesData.getRelatedEntities(entityName)).thenReturn(Collections.emptySet()); + when(mockedEntitiesData.getIDType(entityName)).thenReturn(TypeName.get(String.class)); return mockedEntitiesData; } diff --git a/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/ComposedIdBuilderTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/ComposedIdBuilderTest.java new file mode 100644 index 0000000..0f3658b --- /dev/null +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/ComposedIdBuilderTest.java @@ -0,0 +1,95 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.squareup.javapoet.TypeSpec; + +import javax.lang.model.element.Modifier; + +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.EntityObjectMother; + +public class ComposedIdBuilderTest { + + private static Entity entity; + private static TypeSpec entityTypeSpec; + private static String BASE_PACKAGE = "org.test"; + + @BeforeAll + static void prepareTest() { + entity = EntityObjectMother.createSimpleEntityWithComposedID(); + ComposedIdBuilder composedIdbuilder = new ComposedIdBuilder(entity, BASE_PACKAGE); + entityTypeSpec = composedIdbuilder.build(); + } + + @Test + void givenEntityWithComposedID_whenGenerated_thenModifierIsPublic() { + assertTrue(entityTypeSpec.hasModifier(Modifier.PUBLIC)); + } + + @Test + void givenEntityWithComposedID_whenGenerated_thenKindIsClass() { + assertEquals("CLASS", entityTypeSpec.kind.toString()); + } + + @Test + void givenEntityWithComposedID_whenGenerated_thenAnnotationsAreCorrect() { + assertEquals(6, entityTypeSpec.annotations.size(), "Number of annotations is wrong"); + assertEquals("@lombok.Getter",entityTypeSpec.annotations.get(0).toString(), "Checking if the Entity contains @Setter annotation:"); + assertEquals("@lombok.Setter",entityTypeSpec.annotations.get(1).toString(), "Checking if the Entity contains @Getter annotation:"); + assertEquals("@javax.persistence.Embeddable",entityTypeSpec.annotations.get(2).toString(), "Checking if the Entity contains @Embeddable annotation:"); + assertEquals("@lombok.NoArgsConstructor",entityTypeSpec.annotations.get(3).toString(), "Checking if the Entity contains @NoArgsConstructor annotation:"); + assertEquals("@lombok.AllArgsConstructor",entityTypeSpec.annotations.get(4).toString(), "Checking if the Entity contains @AllArgsConstructor annotation:"); + assertEquals("@lombok.EqualsAndHashCode",entityTypeSpec.annotations.get(5).toString(), "Checking if the Entity contains @EqualsAndHashCode annotation:"); + } + + @Test + void givenEntityWithComposedID_whenGenerated_thenNameIsCorrect() { + assertEquals("TestEntityID", entityTypeSpec.name,"Checking if the Entity has the correct name: TestEntityID"); + } + + @Test + void givenEntityWithComposedID_whenGenerated_thenPropertiesAreCorrect() { + assertEquals("@javax.persistence.Column(\n" + + " name = \"id_s\"\n" + + ")\n" + + "private java.lang.String idS;\n" + + "",entityTypeSpec.fieldSpecs.get(0).toString()); + assertEquals("@javax.persistence.Column(\n" + + " name = \"id_n\"\n" + + ")\n" + + "private java.lang.Long idN;\n" + + "",entityTypeSpec.fieldSpecs.get(1).toString()); + } + + @Test + void givenEntityWithComposedID_whenGenerated_thenMethodsAreCorrect() { + + assertEquals("public static org.test.testentity.TestEntityID from(java.lang.String str) {\n" + + " if (str == null) return null;\n" + + " java.lang.String[] parts = str.split(\"_\");\n" + + " if (parts.length != 2) throw new java.lang.IllegalArgumentException();\n" + + " return new org.test.testentity.TestEntityID(parts[0], java.lang.Long.valueOf(parts[1]));\n" + + "}\n" + + "",entityTypeSpec.methodSpecs.get(0).toString(),"Checking if the Entity contains the method: from"); + assertEquals("@java.lang.Override\n" + + "public java.lang.String toString() {\n" + + " return idS + \"_\" + idN;\n" + + "}\n" + + "",entityTypeSpec.methodSpecs.get(1).toString(),"Checking if the Entity contains the method: toString"); + assertEquals("@java.lang.Override\n" + + "public int compareTo(org.test.testentity.TestEntityID o) {\n" + + " int c;\n" + + " c = idS.compareTo(o.idS);\n" + + " if (c != 0) return c;\n" + + " c = idN.compareTo(o.idN);\n" + + " if (c != 0) return c;\n" + + " return c;\n" + + "}\n" + + "",entityTypeSpec.methodSpecs.get(2).toString(),"Checking if the Entity contains the method: compareTo"); + } +} diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java similarity index 81% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java index e9e1cba..987f7ab 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntitiesGeneratorTest.java @@ -1,11 +1,12 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence; import com.squareup.javapoet.*; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; -import net.cloudappi.apigen.generatorcore.generator.common.AbstractClassBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.EntityObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.common.AbstractClassBuilder; import org.junit.jupiter.api.Test; import javax.persistence.*; @@ -13,8 +14,7 @@ import java.util.*; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; // TODO #14908 refactor to avoid validate full generation class EntitiesGeneratorTest { @@ -63,6 +63,7 @@ void generateEntityWith_Simple_Attributes() { assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(javax.persistence.Entity.class).build()), "Checking if Entity contains @Entity annotation:"); assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(javax.persistence.Table.class).addMember("name", "$S", "tableName").build()), "Checking if Entity contains @Table annotation:"); + assertTrue(generatedEntity.annotations.contains(AnnotationSpec.builder(NoArgsConstructor.class).build()), "Checking if Entity contains @NoArgsConstructor annotation:"); } @Test @@ -371,40 +372,36 @@ void generateEntityWith_Array_OneToMany_Attribute() { } @Test - void generateEntityWith_Sequence_Id_Attribute() { - Entity testEntity = EntityObjectMother.createEntitiesSequenceIdAttribute(); + void generateEntityWith_GetAndSet_Id_Method() { + Entity testEntity = EntityObjectMother.createEntityWithSequenceIdAttribute(); EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntity), BASE_PACKAGE); TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntity.getName()); - assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( - AnnotationSpec.builder(SequenceGenerator.class) - .addMember("name", "$S", testEntity.getAttributes().get(0).getColumns().get(0).getSequence() + "_name") - .addMember("sequenceName", "$S", testEntity.getAttributes().get(0).getColumns().get(0).getSequence()) - .build() - ), "Checking if attribute has @SequenceGenerator annotation"); + assertEquals("getId", generatedEntity.methodSpecs.get(1).name, "Checking if getId method exists"); + assertEquals("java.lang.Long", generatedEntity.methodSpecs.get(1).returnType.toString(), "Checking getId method return type"); - assertTrue(generatedEntity.fieldSpecs.get(0).annotations.contains( - AnnotationSpec.builder(GeneratedValue.class) - .addMember("generator", "$S", testEntity.getAttributes().get(0).getColumns().get(0).getSequence() + "_name") - .build() - ), "Checking if attribute has @GeneratedValue annotation"); + assertEquals("setId", generatedEntity.methodSpecs.get(2).name, "Checking if setId method exists"); + assertEquals("void", generatedEntity.methodSpecs.get(2).returnType.toString(), "Checking setId method return type"); } @Test - void generateEntityWith_GetAndSet_Id_Method() { - Entity testEntity = EntityObjectMother.createEntitiesSequenceIdAttribute(); + void generateEntityWith_ConstructorById() { + Entity testEntity = EntityObjectMother.createEntityWithSequenceIdAttribute(); EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntity), BASE_PACKAGE); TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntity.getName()); - assertEquals("getId", generatedEntity.methodSpecs.get(0).name, "Checking if getId method exists"); - assertEquals("java.lang.Long", generatedEntity.methodSpecs.get(0).returnType.toString(), "Checking getId method return type"); + MethodSpec methodSpec = generatedEntity.methodSpecs.get(0); - assertEquals("setId", generatedEntity.methodSpecs.get(1).name, "Checking if setId method exists"); - assertEquals("void", generatedEntity.methodSpecs.get(1).returnType.toString(), "Checking setId method return type"); + assertTrue(methodSpec.isConstructor(), "Method is not a constructor"); + assertEquals("", methodSpec.name); + assertTrue(methodSpec.annotations.isEmpty(), "Method has annotations"); + assertEquals("[java.lang.Long id]", methodSpec.parameters.toString()); + assertNull(methodSpec.returnType, "Method has return types"); + assertEquals("this.setId(id);\n", methodSpec.code.toString()); } private String concatPackage(String basePackage, String newPackage) { @@ -414,4 +411,70 @@ private String concatPackage(String basePackage, String newPackage) { private Map getGeneratedEntities(EntitiesGenerator eg) { return eg.getBuilders().stream().map(AbstractClassBuilder::build).collect(Collectors.toMap(b -> b.name, b -> b)); } + + @Test + void givenAutogeneratedSequenceId_whenGenerated_thenSuccess() { + Entity testEntity = EntityObjectMother.createEntityWithSequenceIdAttribute(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntity), BASE_PACKAGE); + + TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntity.getName()); + + String actual = generatedEntity.fieldSpecs.get(0).annotations.toString(); + + assertEquals("[" + + "@javax.persistence.Id, " + + "@javax.persistence.GeneratedValue(generator = \"SEQUENCE_NAME_name\"), " + + "@javax.persistence.SequenceGenerator(name = \"SEQUENCE_NAME_name\", sequenceName = \"SEQUENCE_NAME\"), " + + "@javax.persistence.Column(name = \"id_column\")" + + "]", actual); + } + + @Test + void givenAutogeneratedNumericId_whenGenerated_thenSuccess() { + Entity testEntity = EntityObjectMother.createEntityWithAutogeneratedLongId(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntity), BASE_PACKAGE); + + TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntity.getName()); + + String actual = generatedEntity.fieldSpecs.get(0).annotations.toString(); + + assertEquals("[" + + "@javax.persistence.Id, " + + "@javax.persistence.GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY), " + + "@javax.persistence.Column(name = \"id\")" + + "]", actual); + } + + @Test + void givenAutogeneratedStringId_whenGenerated_thenSuccess() { + Entity testEntity = EntityObjectMother.createEntityWithAutogeneratedStringId(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntity), BASE_PACKAGE); + + TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntity.getName()); + + String actual = generatedEntity.fieldSpecs.get(0).annotations.toString(); + + assertEquals("[" + + "@javax.persistence.Id, " + + "@javax.persistence.GeneratedValue(generator = \"uuid\"), " + + "@org.hibernate.annotations.GenericGenerator(name = \"uuid\", strategy = \"uuid2\"), " + + "@javax.persistence.Column(name = \"id\")" + + "]", actual); + } + + @Test + void givenNotAutogeneratedId_whenGenerated_thenSuccess() { + Entity testEntity = EntityObjectMother.createEntityWithoutAutogeneratedId(); + + EntitiesGenerator entitiesGenerator = new EntitiesGenerator(Arrays.asList(testEntity), BASE_PACKAGE); + + TypeSpec generatedEntity = getGeneratedEntities(entitiesGenerator).get(testEntity.getName()); + + String actual = generatedEntity.fieldSpecs.get(0).annotations.toString(); + + assertEquals("[@javax.persistence.Id, @javax.persistence.Column(name = \"id\")]", actual); + } } diff --git a/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityRelationManagerTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityRelationManagerTest.java new file mode 100644 index 0000000..eef8c9e --- /dev/null +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/EntityRelationManagerTest.java @@ -0,0 +1,31 @@ +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence; + +import com.squareup.javapoet.FieldSpec; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.EntityObjectMother; +import org.junit.jupiter.api.Test; + +import javax.lang.model.element.Modifier; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class EntityRelationManagerTest { + + @Test + void manyToOne() { + Entity entity = EntityObjectMother.withManyToOne("One", "field", "Other", "column"); + + EntityRelationManager m = new EntityRelationManager(Arrays.asList(entity)); + + FieldSpec.Builder builder = FieldSpec.builder(String.class, "field", Modifier.PRIVATE); + + m.applyRelation("One", entity.getAttributes().get(0), builder); + + assertEquals("[" + + "@javax.persistence.ManyToOne, " + + "@javax.persistence.JoinColumn(name = \"column\")" + + "]", + builder.annotations.toString()); + } +} \ No newline at end of file diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java similarity index 79% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java index 13da229..360cbf8 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoriesGeneratorTest.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.repository; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.repository; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.EntityObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mockito; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java similarity index 82% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java index 938a757..d40c67e 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/persistence/repository/RepositoryBuilderTests.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.persistence.repository; +package org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.repository; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.EntityObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -40,7 +40,7 @@ void givenAnEntityWithStringAsPrimaryKey_whenGenerateRepository_thenAnnotationsA @Test void givenAnEntityWithStringAsPrimaryKey_whenGenerateRepository_thenSuperinterfaceIsCorrect() { - assertEquals("net.cloudappi.apigen.archetypecore.core.persistence.ApigenRepository", + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenRepository", generatedRepository.superinterfaces.get(0).toString(), "Superinterface is wrong"); } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java similarity index 64% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java index fa1aeb1..3df2b2a 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/RelationManagerBuilderTests.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.service; +package org.apiaddicts.apitools.apigen.generatorcore.generator.service; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData.AttributeData; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData.AttributeData; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -42,7 +42,7 @@ void givenValidRelationManagerBuilder_whenBuild_thenAnnotationsCorrect() { @Test void givenValidRelationManagerBuilder_whenBuild_thenSuperclassCorrect() { - assertEquals("net.cloudappi.apigen.archetypecore.core.AbstractRelationsManager", spec.superclass.toString()); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.AbstractRelationsManager", spec.superclass.toString()); } @Test @@ -65,7 +65,7 @@ void givenValidRelationManagerBuilder_whenBuild_thenHasAllFields() { @Test void givenValidRelationManagerBuilder_whenBuild_thenHasAllMethods() { - assertEquals(6, spec.methodSpecs.size()); + assertEquals(2, spec.methodSpecs.size()); } @Test @@ -78,18 +78,18 @@ void givenValidRelationManagerBuilder_whenBuild_thenMainCreateMethodCorrect() { assertEquals("[the.pkg.entityfirst.EntityFirst entityFirst]", methodSpec.parameters.toString()); assertEquals("void", methodSpec.returnType.toString()); assertEquals("" + - "net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors errors = new net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors();\n" + + "org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrors errors = new org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrors();\n" + "if (entityFirst.getList() != null) {\n" + - " entityFirst.setList(entityFirst.getList().stream().map(e -> createOrRetrieve(e, errors)).collect(java.util.stream.Collectors.toSet()));\n" + + " entityFirst.setList(entityFirst.getList().stream().map(e -> createOrRetrieve(e, listItemService, errors)).collect(java.util.stream.Collectors.toSet()));\n" + "}\n" + "if (entityFirst.getSimple() != null) {\n" + - " entityFirst.setSimple(createOrRetrieve(entityFirst.getSimple(), errors));\n" + + " entityFirst.setSimple(createOrRetrieve(entityFirst.getSimple(), simpleService, errors));\n" + "}\n" + "if (entityFirst.getSimpleTwo() != null) {\n" + - " entityFirst.setSimpleTwo(createOrRetrieve(entityFirst.getSimpleTwo(), errors));\n" + + " entityFirst.setSimpleTwo(createOrRetrieve(entityFirst.getSimpleTwo(), simpleService, errors));\n" + "}\n" + "if (!errors.isEmpty()) {\n" + - " throw new net.cloudappi.apigen.archetypecore.exceptions.RelationalErrorsException(errors);\n" + + " throw new org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrorsException(errors);\n" + "}\n" , methodSpec.code.toString()); } @@ -104,69 +104,36 @@ void givenValidRelationManagerBuilder_whenBuild_thenMainUpdateMethodCorrect() { assertEquals("[the.pkg.entityfirst.EntityFirst persistedEntityFirst, the.pkg.entityfirst.EntityFirst entityFirst, java.util.Set fields]", methodSpec.parameters.toString()); assertEquals("void", methodSpec.returnType.toString()); assertEquals("" + - "net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors errors = new net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors();\n" + + "org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrors errors = new org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrors();\n" + "boolean updateAll = (fields == null);\n" + "if (updateAll || fields.contains(\"list\")) {\n" + " if (entityFirst.getList() != null) {\n" + " persistedEntityFirst.getList().clear();\n" + - " persistedEntityFirst.getList().addAll(entityFirst.getList().stream().map(e -> retrieve(e, errors)).collect(java.util.stream.Collectors.toSet()));\n" + + " persistedEntityFirst.getList().addAll(entityFirst.getList().stream().map(e -> retrieve(e, listItemService, errors)).collect(java.util.stream.Collectors.toSet()));\n" + " } else {\n" + " persistedEntityFirst.setList(null);\n" + " }\n" + "}\n" + "if (updateAll || fields.contains(\"simple\")) {\n" + " if (entityFirst.getSimple() != null) {\n" + - " persistedEntityFirst.setSimple(retrieve(entityFirst.getSimple(), errors));\n" + + " persistedEntityFirst.setSimple(retrieve(entityFirst.getSimple(), simpleService, errors));\n" + " } else {\n" + " persistedEntityFirst.setSimple(null);\n" + " }\n" + "}\n" + "if (updateAll || fields.contains(\"simpleTwo\")) {\n" + " if (entityFirst.getSimpleTwo() != null) {\n" + - " persistedEntityFirst.setSimpleTwo(retrieve(entityFirst.getSimpleTwo(), errors));\n" + + " persistedEntityFirst.setSimpleTwo(retrieve(entityFirst.getSimpleTwo(), simpleService, errors));\n" + " } else {\n" + " persistedEntityFirst.setSimpleTwo(null);\n" + " }\n" + "}\n" + "if (!errors.isEmpty()) {\n" + - " throw new net.cloudappi.apigen.archetypecore.exceptions.RelationalErrorsException(errors);\n" + + " throw new org.apiaddicts.apitools.apigen.archetypecore.exceptions.RelationalErrorsException(errors);\n" + "}\n" , methodSpec.code.toString()); } - @Test - void givenValidRelationManagerBuilder_whenBuild_thenCreateSubMethodCorrect() { - MethodSpec methodSpec = spec.methodSpecs.get(2); - assertEquals("createOrRetrieve", methodSpec.name); - assertEquals("[the.pkg.listitem.ListItem listItem, net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors errors]", methodSpec.parameters.toString()); - assertEquals("the.pkg.listitem.ListItem", methodSpec.returnType.toString()); - assertEquals("" + - "if (listItem.isReference()) {\n" + - " return retrieve(listItem, errors);\n" + - "} else {\n" + - " try {\n" + - " return listItemService.create(listItem);\n" + - " } catch (net.cloudappi.apigen.archetypecore.exceptions.RelationalErrorsException e) {\n" + - " errors.merge(e.getRelationalErrors());\n" + - " return null;\n" + - " }\n" + - "}\n" - , methodSpec.code.toString()); - } - - @Test - void givenValidRelationManagerBuilder_whenBuild_thenUpdateSubMethodCorrect() { - MethodSpec methodSpec = spec.methodSpecs.get(4); - assertEquals("retrieve", methodSpec.name); - assertEquals("[the.pkg.listitem.ListItem listItem, net.cloudappi.apigen.archetypecore.exceptions.RelationalErrors errors]", methodSpec.parameters.toString()); - assertEquals("the.pkg.listitem.ListItem", methodSpec.returnType.toString()); - assertEquals("" + - "the.pkg.listitem.ListItem retrieved = (listItem.getId() == null) ? null : listItemService.getOne(listItem.getId()).orElse(null);\n" + - "if (retrieved == null) errors.register(the.pkg.listitem.ListItem.class, listItem.getId());\n" + - "return retrieved;\n" - , methodSpec.code.toString()); - } - @Test void givenEntityName_whenGetTypeName_thenCorrect() { TypeName typeName = RelationManagerBuilder.getTypeName("EntityName", "the.pkg"); diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServiceBuilderTests.java similarity index 85% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServiceBuilderTests.java index 1646779..18153e9 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServiceBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServiceBuilderTests.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.service; +package org.apiaddicts.apitools.apigen.generatorcore.generator.service; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.entity.Attribute; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntityBuilder; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Attribute; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.EntityObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntityBuilder; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -51,8 +51,8 @@ void givenAnEntityWithStringAsPrimaryKey_whenGenerateService_thenAnnotationsAreC @Test void givenAnEntityWithStringAsPrimaryKey_whenGenerateService_thenConstructorMethodIsCorrect() { assertEquals("public Constructor(the.base.package.simpletestentity.SimpleTestEntityRepository repository,\n" + - " @org.springframework.lang.Nullable net.cloudappi.apigen.archetypecore.core.AbstractRelationsManager relationsManager,\n" + - " @org.springframework.lang.Nullable net.cloudappi.apigen.archetypecore.core.ApigenMapper mapper) {\n" + + " @org.springframework.lang.Nullable org.apiaddicts.apitools.apigen.archetypecore.core.AbstractRelationsManager relationsManager,\n" + + " @org.springframework.lang.Nullable org.apiaddicts.apitools.apigen.archetypecore.core.ApigenMapper mapper) {\n" + " super(repository, relationsManager, mapper);\n" + "}\n", generatedService.methodSpecs.get(0).toString(), "Constructor Method is wrong"); diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServicesGeneratorTest.java similarity index 81% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServicesGeneratorTest.java index 1e0f5f7..38e0573 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/service/ServicesGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/service/ServicesGeneratorTest.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.generator.service; +package org.apiaddicts.apitools.apigen.generatorcore.generator.service; import com.squareup.javapoet.TypeName; -import net.cloudappi.apigen.generatorcore.config.entity.Entity; -import net.cloudappi.apigen.generatorcore.config.entity.EntityObjectMother; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.Entity; +import org.apiaddicts.apitools.apigen.generatorcore.config.entity.EntityObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mockito; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java similarity index 77% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java index 3cf49c8..a9c5bc2 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/ControllersGeneratorTest.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.ControllerObjectMother; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.ControllerObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java similarity index 90% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java index 5b255a0..5e29d1d 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/EntityControllerBuilderTests.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.ControllerObjectMother; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.ControllerObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -72,7 +72,7 @@ void givenValidController_whenBuildController_thenMapperFieldIsCorrect() { void givenValidController_whenBuildController_thenResourceNamingTranslatorFieldIsCorrect() { FieldSpec mapperFieldSpec = generatedController.fieldSpecs.get(2); assertEquals("namingTranslator", mapperFieldSpec.name, "The mapper field name is wrong"); - assertEquals("net.cloudappi.apigen.archetypecore.core.resource.ResourceNamingTranslator", mapperFieldSpec.type.toString(), "The mapper field type is wrong"); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.resource.ResourceNamingTranslator", mapperFieldSpec.type.toString(), "The mapper field type is wrong"); assertEquals("@org.springframework.beans.factory.annotation.Autowired", mapperFieldSpec.annotations.get(0).toString(), "The mapper field annotation is wrong"); } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java similarity index 86% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java index 1dcd757..1b4c551 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/AttributeObjectMother.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; -import net.cloudappi.apigen.generatorcore.config.controller.Attribute; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Attribute; import java.util.ArrayList; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java similarity index 84% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java index 59fa31c..99b60d1 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/CustomEndpointBuilderTests.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -47,7 +47,7 @@ void givenCustomEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { assertEquals("body", parameterSpec.name); assertEquals("// TODO: Implement this non standard endpoint\n" + - "throw new net.cloudappi.apigen.archetypecore.exceptions.NotImplementedException(\"POST /endpoint\");\n", methodSpec.code.toString()); + "throw new org.apiaddicts.apitools.apigen.archetypecore.exceptions.NotImplementedException(\"POST /endpoint\");\n", methodSpec.code.toString()); } } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java similarity index 92% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java index b1edc3f..9a068ec 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/DeleteEndpointBuilderTests.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java similarity index 91% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java index 17ff3c6..bd83a37 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointBuilderFactoryTests.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.generator.persistence.EntitiesData; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.generator.persistence.EntitiesData; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.Test; import org.mockito.Mockito; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java similarity index 88% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java index e2f5b93..631db0a 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/EndpointObjectMother.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.config.controller.EndpointBaseResponseObjectMother; -import net.cloudappi.apigen.generatorcore.config.controller.EndpointRequestObjectMother; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; -import net.cloudappi.apigen.generatorcore.config.parameter.ParameterObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.EndpointBaseResponseObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.EndpointRequestObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.config.parameter.ParameterObjectMother; import java.util.ArrayList; import java.util.List; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java similarity index 92% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java index e77e384..f8efadd 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetAllEndpointBuilderTests.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -107,7 +107,7 @@ void givenGetAllEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { assertEquals("orderby", parameterSpec.name); assertEquals("namingTranslator.translate(select, exclude, expand, orderby, the.base.package.entityname.web.EntityNameOutResource.class);\n" + - "net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult searchResult = service.search(select, exclude, expand, null, orderby, init, limit, total);\n" + + "org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearchResult searchResult = service.search(select, exclude, expand, null, orderby, init, limit, total);\n" + "java.util.List result = mapper.toResource(searchResult.getSearchResult());\n" + "return new the.base.package.entityname.web.EntityNameListResponse(result).withMetadataPagination(init, limit, searchResult.getTotal());\n", methodSpec.code.toString()); } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java similarity index 95% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java index a7d414a..2a43023 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/GetByIdEndpointBuilderTests.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java similarity index 93% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java index 0aad6c6..71290c6 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostEndpointBuilderTests.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java similarity index 88% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java index 40df298..bf9fab2 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PostSearchEndpointBuilderTests.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -106,12 +106,12 @@ void givenPostSearchEndpointBuilder_whenBuild_thenHaveMethodSpecIsCorrect() { parameterSpec = methodSpec.parameters.get(7); assertEquals("[@org.springframework.web.bind.annotation.RequestBody, @javax.validation.Valid]", parameterSpec.annotations.toString()); - assertEquals("net.cloudappi.apigen.archetypecore.core.resource.FilterResource", parameterSpec.type.toString()); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.resource.FilterResource", parameterSpec.type.toString()); assertEquals("body", parameterSpec.name); - assertEquals("net.cloudappi.apigen.archetypecore.core.persistence.filter.Filter filter = body.getFilter();\n" + + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.persistence.filter.Filter filter = body.getFilter();\n" + "namingTranslator.translate(select, exclude, expand, filter, orderby, the.base.package.entityname.web.EntityNameOutResource.class);\n" + - "net.cloudappi.apigen.archetypecore.core.persistence.ApigenSearchResult searchResult = service.search(select, exclude, expand, filter, orderby, init, limit, total);\n" + + "org.apiaddicts.apitools.apigen.archetypecore.core.persistence.ApigenSearchResult searchResult = service.search(select, exclude, expand, filter, orderby, init, limit, total);\n" + "java.util.List result = mapper.toResource(searchResult.getSearchResult());\n" + "return new the.base.package.entityname.web.EntityNameListResponse(result).withMetadataPagination(init, limit, searchResult.getTotal());\n", methodSpec.code.toString()); } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java similarity index 94% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java index 5864752..d2eff08 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/endpoints/PutEndpointBuilderTests.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java similarity index 96% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java index 58f2ef1..fa7db93 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/controller/parameters/ParameterBuilderTest.java @@ -1,8 +1,8 @@ -package net.cloudappi.apigen.generatorcore.generator.web.controller.parameters; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.parameters; import com.squareup.javapoet.ParameterSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Parameter; -import net.cloudappi.apigen.generatorcore.config.parameter.ParameterObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Parameter; +import org.apiaddicts.apitools.apigen.generatorcore.config.parameter.ParameterObjectMother; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java similarity index 81% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java index 44e1f2c..2f04d04 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/ResourceGeneratorTest.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.ControllerObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.ControllerObjectMother; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java similarity index 69% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java index da6e865..03e59b8 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/input/AllInputResourceBuilderTests.java @@ -1,14 +1,15 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.input; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.input; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Attribute; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.AttributeObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Attribute; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.AttributeObjectMother; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -20,6 +21,7 @@ class AllInputResourceBuilderTests { private static TypeSpec entityTypeSpec; private static TypeSpec entityNestedTypeSpec; private static TypeSpec resourceTypeSpec; + private static TypeSpec classNestedSubclassSpec; @BeforeAll @@ -27,9 +29,10 @@ static void init() { initEntityTypeSpec(); initEntityNestedIdTypeSpec(); initResourceTypeSpec(); + initClassNestedSubclassSpec(); } - private static void initEntityTypeSpec() { + private static void initEntityTypeSpec() { Attribute attribute = AttributeObjectMother.createSimpleAttribute("string", "json", "java"); Endpoint endpoint = EndpointObjectMother.customEndpoint(); @@ -64,6 +67,21 @@ private static void initResourceTypeSpec() { resourceTypeSpec = builder.build(); } + private static void initClassNestedSubclassSpec() { + Attribute attribute = AttributeObjectMother.createSimpleAttribute("object", "classOne", "fieldOne"); + attribute.setRelatedEntity("classTwo"); + + Attribute nestedClassAttribute = AttributeObjectMother.createSimpleAttribute("object", "classTwo", "fieldTwo"); + attribute.setAttributes(new ArrayList() {{ add(nestedClassAttribute); }}); + + Endpoint endpoint = EndpointObjectMother.customEndpoint(); + endpoint.setRelatedEntity("Entity"); + endpoint.getRequest().setAttributes(Collections.singletonList(attribute)); + + AllInputResourceBuilder builder = new AllInputResourceBuilder(new Mapping("/entity"), endpoint, basePackage); + classNestedSubclassSpec = builder.build(); + } + @Test void givenEntityEndpoint_whenGenerated_thenAnnotationsAreCorrect() { assertEquals("[@lombok.Data]", entityTypeSpec.annotations.toString()); @@ -99,7 +117,7 @@ void givenEntityNestedIdEndpoint_whenGenerated_thenFieldsAreCorrect() { assertEquals("" + "@com.fasterxml.jackson.annotation.JsonProperty(\"other\")\n" + "@javax.validation.Valid\n" + - "private the.base.pkge.entity.web.CreateEntityEndpointResource.Other other;\n", + "private java.lang.Long other;\n", entityNestedTypeSpec.fieldSpecs.get(0).toString()); } @@ -154,4 +172,28 @@ void givenResourceEndpoint_whenGenerated_thenNestedClassIsCorrect() { "}\n", resourceTypeSpec.typeSpecs.get(0).toString()); } + + @Test + void givenEntityNestedIdEndpoint_whenGenerated_thenClassNestedSubclassIsCorrect() { + assertEquals(1, classNestedSubclassSpec.typeSpecs.size()); + assertEquals("@lombok.Data\n" + + "public class CreateEntityEndpointResource {\n" + + " @com.fasterxml.jackson.annotation.JsonProperty(\"classOne\")\n" + + " @javax.validation.Valid\n" + + " private the.base.pkge.entity.web.CreateEntityEndpointResource.FieldOne fieldOne;\n" + + "\n" + + " @lombok.Data\n" + + " public static class FieldOne {\n" + + " @com.fasterxml.jackson.annotation.JsonProperty(\"classTwo\")\n" + + " @javax.validation.Valid\n" + + " private the.base.pkge.entity.web.CreateEntityEndpointResource.FieldOne.FieldTwo fieldTwo;\n" + + "\n" + + " @lombok.Data\n" + + " public static class FieldTwo {\n" + + " }\n" + + " }\n" + + "}\n" + + "", + classNestedSubclassSpec.toString()); + } } \ No newline at end of file diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java similarity index 91% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java index f31f1b0..7abe2b5 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/EntityOutputResourceBuilderTest.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.output; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.EndpointBaseResponseObjectMother; -import net.cloudappi.apigen.generatorcore.config.controller.Response; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.EndpointBaseResponseObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Response; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -19,7 +19,7 @@ void givenAResponseWithAttribute_whenGenerateResource_thenFileStructureIsCorrect assertEquals("CLASS", spec.kind.name(), "The file is not a class"); assertFalse(spec.annotations.isEmpty(), "The annotations are wrong"); assertEquals("@lombok.Data", spec.annotations.get(0).toString(), "The @Data annotation is wrong"); - assertEquals("@net.cloudappi.apigen.archetypecore.core.resource.ApigenEntityOutResource", spec.annotations.get(1).toString(), "The @ApigenEntityOutResource annotation is wrong"); + assertEquals("@org.apiaddicts.apitools.apigen.archetypecore.core.resource.ApigenEntityOutResource", spec.annotations.get(1).toString(), "The @ApigenEntityOutResource annotation is wrong"); assertFalse(spec.fieldSpecs.isEmpty(), "The fields are wrong"); } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java similarity index 91% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java index e0961c1..d957ddb 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/resource/output/ResourceOutputResourceBuilderTest.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.resource.output; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.resource.output; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.*; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.AttributeObjectMother; -import net.cloudappi.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.*; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.AttributeObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java similarity index 78% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java index 305fb0c..468f778 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/EntityListResponseBuilderTests.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; @@ -16,7 +16,7 @@ class EntityListResponseBuilderTests { @BeforeAll static void init() { - EntityListResponseBuilder builder = new EntityListResponseBuilder("EntityName", "listName", "the.base.package"); + EntityListResponseBuilder builder = new EntityListResponseBuilder("EntityName", "list_name", "the.base.package"); typeSpec = builder.build(); } @@ -42,7 +42,7 @@ void givenListResponseBuilder_whenBuild_thenHaveOneAnnotationAndValueIsCorrect() @Test void givenListResponseBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { - assertEquals("net.cloudappi.apigen.archetypecore.core.responses.ApiResponse>", typeSpec.superclass.toString()); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse>", typeSpec.superclass.toString()); } @Test @@ -60,7 +60,7 @@ void givenListResponseBuilder_whenBuild_thenHaveTypeSpec() { assertEquals(2, subTypeSpec.modifiers.size()); assertEquals("[private, static]", subTypeSpec.modifiers.toString()); assertEquals("EntityNameListResponseContent", subTypeSpec.name); - assertEquals("net.cloudappi.apigen.archetypecore.core.responses.content.ApiListResponseContent", subTypeSpec.superclass.toString()); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.responses.content.ApiListResponseContent", subTypeSpec.superclass.toString()); assertEquals(2, subTypeSpec.methodSpecs.size()); MethodSpec methodSpec; methodSpec = subTypeSpec.methodSpecs.get(0); @@ -69,8 +69,8 @@ void givenListResponseBuilder_whenBuild_thenHaveTypeSpec() { assertEquals("super(listName);\n", methodSpec.code.toString()); methodSpec = subTypeSpec.methodSpecs.get(1); assertFalse(methodSpec.isConstructor()); - assertEquals("getListName", methodSpec.name); - assertEquals("[@com.fasterxml.jackson.annotation.JsonProperty(\"listName\")]", methodSpec.annotations.toString()); + assertEquals("getContent", methodSpec.name); + assertEquals("[@java.lang.Override, @com.fasterxml.jackson.annotation.JsonProperty(\"list_name\")]", methodSpec.annotations.toString()); assertEquals("return content;\n", methodSpec.code.toString()); } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java similarity index 75% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java index 054f365..56c1af0 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceListResponseBuilderTests.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -25,7 +25,7 @@ static void init() { endpoint = EndpointObjectMother.customEndpoint(); endpoint.getResponse().setIsCollection(true); endpoint.getResponse().setIsStandard(true); - endpoint.getResponse().setCollectionName("collectionName"); + endpoint.getResponse().setCollectionName("collection_name"); rootMapping = new Mapping("/resource"); ResourceListResponseBuilder builder = new ResourceListResponseBuilder(rootMapping, endpoint, "the.base.package"); typeSpec = builder.build(); @@ -53,7 +53,7 @@ void givenResourceListResponseBuilder_whenBuild_thenHaveOneAnnotationAndValueIsC @Test void givenResourceListResponseBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { - assertEquals("net.cloudappi.apigen.archetypecore.core.responses.ApiResponse>", typeSpec.superclass.toString()); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse>", typeSpec.superclass.toString()); } @Test @@ -71,7 +71,7 @@ void givenResourceListResponseBuilder_whenBuild_thenHaveTypeSpec() { assertEquals(2, subTypeSpec.modifiers.size()); assertEquals("[private, static]", subTypeSpec.modifiers.toString()); assertEquals("ResourceEndpointListResponseContent", subTypeSpec.name); - assertEquals("net.cloudappi.apigen.archetypecore.core.responses.content.ApiListResponseContent", subTypeSpec.superclass.toString()); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.responses.content.ApiListResponseContent", subTypeSpec.superclass.toString()); assertEquals(2, subTypeSpec.methodSpecs.size()); MethodSpec methodSpec; methodSpec = subTypeSpec.methodSpecs.get(0); @@ -80,8 +80,8 @@ void givenResourceListResponseBuilder_whenBuild_thenHaveTypeSpec() { assertEquals("super(collectionName);\n", methodSpec.code.toString()); methodSpec = subTypeSpec.methodSpecs.get(1); assertFalse(methodSpec.isConstructor()); - assertEquals("getCollectionName", methodSpec.name); - assertEquals("[@com.fasterxml.jackson.annotation.JsonProperty(\"collectionName\")]", methodSpec.annotations.toString()); + assertEquals("getContent", methodSpec.name); + assertEquals("[@java.lang.Override, @com.fasterxml.jackson.annotation.JsonProperty(\"collection_name\")]", methodSpec.annotations.toString()); assertEquals("return content;\n", methodSpec.code.toString()); } diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java similarity index 83% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java index 58c43b7..418ab61 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResourceSimpleResponseBuilderTests.java @@ -1,11 +1,11 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import net.cloudappi.apigen.generatorcore.config.controller.Endpoint; -import net.cloudappi.apigen.generatorcore.utils.Mapping; -import net.cloudappi.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Endpoint; +import org.apiaddicts.apitools.apigen.generatorcore.utils.Mapping; +import org.apiaddicts.apitools.apigen.generatorcore.generator.web.controller.endpoints.EndpointObjectMother; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -56,7 +56,7 @@ void givenResourceSimpleResponseBuilder_whenBuild_thenNoHaveFieldsSpecAndIsCorre @Test void givenSimpleResponseBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { - assertEquals("net.cloudappi.apigen.archetypecore.core.responses.ApiResponse", typeSpec.superclass.toString()); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse", typeSpec.superclass.toString()); } @Test diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java similarity index 92% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java index fdb71e0..f493ea5 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/ResponsesGeneratorTests.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; -import net.cloudappi.apigen.generatorcore.config.controller.Controller; -import net.cloudappi.apigen.generatorcore.config.controller.ControllerObjectMother; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.Controller; +import org.apiaddicts.apitools.apigen.generatorcore.config.controller.ControllerObjectMother; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java similarity index 89% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java index c4bcc26..25be435 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/web/response/SimpleResponseBuilderTests.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.generator.web.response; +package org.apiaddicts.apitools.apigen.generatorcore.generator.web.response; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; @@ -47,7 +47,7 @@ void givenSimpleResponseBuilder_whenBuild_thenNoHaveFieldsSpecAndIsCorrect() { @Test void givenSimpleResponseBuilder_whenBuild_thenHaveSuperClassAndIsCorrect() { - assertEquals("net.cloudappi.apigen.archetypecore.core.responses.ApiResponse", typeSpec.superclass.toString()); + assertEquals("org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse", typeSpec.superclass.toString()); } @Test diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtilsTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/utils/CustomStringUtilsTest.java similarity index 97% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtilsTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/utils/CustomStringUtilsTest.java index 4d92074..d49a3b9 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/CustomStringUtilsTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/utils/CustomStringUtilsTest.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.utils; +package org.apiaddicts.apitools.apigen.generatorcore.utils; import org.junit.jupiter.api.Test; diff --git a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/ZipUtilsTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/utils/ZipUtilsTest.java similarity index 96% rename from generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/ZipUtilsTest.java rename to generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/utils/ZipUtilsTest.java index fb5fe27..9fdd37c 100644 --- a/generator-core/src/test/java/net/cloudappi/apigen/generatorcore/utils/ZipUtilsTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/utils/ZipUtilsTest.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorcore.utils; +package org.apiaddicts.apitools.apigen.generatorcore.utils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; diff --git a/generator-rest/pom.xml b/generator-rest/pom.xml index fe62edd..e035bcc 100644 --- a/generator-rest/pom.xml +++ b/generator-rest/pom.xml @@ -7,14 +7,44 @@ apigen - net.cloudappi.apigen - 0.0.1-SNAPSHOT + org.apiaddicts.apitools.apigen + 0.0.3-SNAPSHOT ../pom.xml generator-rest generator-rest ${revision} + https://github.com/apiaddicts/apigen + + + + GNU Lesser General Public License (LGPLV3+) + http://www.gnu.org/licenses/lgpl-3.0.html + + + + + Apiaddicts + https://apiaddicts.org + + + + scm:git:git://github.com/apiaddicts/apigen.git + scm:git:ssh://github.com:apiaddicts/apigen.git + https://github.com/apiaddicts/apigen/tree/master + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + 1.4.13 @@ -22,7 +52,7 @@ - net.cloudappi.apigen + org.apiaddicts.apitools.apigen generator-core diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/App.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/App.java similarity index 82% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/App.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/App.java index f771283..07430ed 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/App.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/App.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorrest; +package org.apiaddicts.apitools.apigen.generatorrest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenCoreConfig.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/ApigenCoreConfig.java similarity index 75% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenCoreConfig.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/ApigenCoreConfig.java index ca0c466..a98a255 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenCoreConfig.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/ApigenCoreConfig.java @@ -1,6 +1,6 @@ -package net.cloudappi.apigen.generatorrest.core.config; +package org.apiaddicts.apitools.apigen.generatorrest.core.config; -import net.cloudappi.apigen.generatorcore.generator.ApigenProjectGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.ApigenProjectGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenProperties.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/ApigenProperties.java similarity index 86% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenProperties.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/ApigenProperties.java index 9839361..d5cab6b 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/ApigenProperties.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/ApigenProperties.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorrest.core.config; +package org.apiaddicts.apitools.apigen.generatorrest.core.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java similarity index 88% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java index 694dd2e..8e6364c 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/AutoconfigureExclusionsConfig.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorrest.core.config; +package org.apiaddicts.apitools.apigen.generatorrest.core.config; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/DocumentationConfig.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/DocumentationConfig.java similarity index 94% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/DocumentationConfig.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/DocumentationConfig.java index cc01998..03a6520 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/config/DocumentationConfig.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/config/DocumentationConfig.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorrest.core.config; +package org.apiaddicts.apitools.apigen.generatorrest.core.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java similarity index 84% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java index c80c0ba..99739ad 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/exceptions/GeneratorRestErrors.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorrest.core.exceptions; +package org.apiaddicts.apitools.apigen.generatorrest.core.exceptions; public enum GeneratorRestErrors { ZIP_EMPTY(1000, "Zip empty"), diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestException.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/exceptions/GeneratorRestException.java similarity index 84% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestException.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/exceptions/GeneratorRestException.java index e0e3008..373129f 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/exceptions/GeneratorRestException.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/exceptions/GeneratorRestException.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorrest.core.exceptions; +package org.apiaddicts.apitools.apigen.generatorrest.core.exceptions; import java.util.Collections; import java.util.List; diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/web/ExceptionAdvice.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/web/ExceptionAdvice.java similarity index 74% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/web/ExceptionAdvice.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/web/ExceptionAdvice.java index 656a54f..2531dde 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/core/web/ExceptionAdvice.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/core/web/ExceptionAdvice.java @@ -1,12 +1,12 @@ -package net.cloudappi.apigen.generatorrest.core.web; +package org.apiaddicts.apitools.apigen.generatorrest.core.web; import lombok.extern.slf4j.Slf4j; -import net.cloudappi.apigen.archetypecore.core.responses.ApiResponse; -import net.cloudappi.apigen.archetypecore.core.responses.result.ApiError; -import net.cloudappi.apigen.generatorcore.exceptions.DefinitionException; -import net.cloudappi.apigen.generatorcore.exceptions.ExtractorException; -import net.cloudappi.apigen.generatorcore.exceptions.InvalidValuesException; -import net.cloudappi.apigen.generatorrest.core.exceptions.GeneratorRestException; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.ApiResponse; +import org.apiaddicts.apitools.apigen.archetypecore.core.responses.result.ApiError; +import org.apiaddicts.apitools.apigen.generatorcore.exceptions.DefinitionException; +import org.apiaddicts.apitools.apigen.generatorcore.exceptions.ExtractorException; +import org.apiaddicts.apitools.apigen.generatorcore.exceptions.InvalidValuesException; +import org.apiaddicts.apitools.apigen.generatorrest.core.exceptions.GeneratorRestException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -17,9 +17,9 @@ import java.util.List; import java.util.stream.Collectors; -import static net.cloudappi.apigen.generatorcore.exceptions.GeneratorErrors.CONFIGURATION_NOT_VALID; -import static net.cloudappi.apigen.generatorcore.exceptions.GeneratorErrors.EXTRACTOR_ERROR; -import static net.cloudappi.apigen.generatorrest.core.exceptions.GeneratorRestErrors.UNEXPECTED_ERROR; +import static org.apiaddicts.apitools.apigen.generatorcore.exceptions.GeneratorErrors.CONFIGURATION_NOT_VALID; +import static org.apiaddicts.apitools.apigen.generatorcore.exceptions.GeneratorErrors.EXTRACTOR_ERROR; +import static org.apiaddicts.apitools.apigen.generatorrest.core.exceptions.GeneratorRestErrors.UNEXPECTED_ERROR; @Slf4j @ControllerAdvice diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/uitls/OpenAPIZipUtils.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/uitls/OpenAPIZipUtils.java similarity index 77% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/uitls/OpenAPIZipUtils.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/uitls/OpenAPIZipUtils.java index e1c5a04..6f91e5a 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/uitls/OpenAPIZipUtils.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/uitls/OpenAPIZipUtils.java @@ -1,7 +1,7 @@ -package net.cloudappi.apigen.generatorrest.uitls; +package org.apiaddicts.apitools.apigen.generatorrest.uitls; -import net.cloudappi.apigen.generatorrest.core.exceptions.GeneratorRestErrors; -import net.cloudappi.apigen.generatorrest.core.exceptions.GeneratorRestException; +import org.apiaddicts.apitools.apigen.generatorrest.core.exceptions.GeneratorRestErrors; +import org.apiaddicts.apitools.apigen.generatorrest.core.exceptions.GeneratorRestException; import org.springframework.web.multipart.MultipartFile; import java.io.File; @@ -19,7 +19,7 @@ private OpenAPIZipUtils() { } public static File getZipRootFile(MultipartFile zip) throws IOException { - Path tempDir = net.cloudappi.apigen.generatorcore.utils.ZipUtils.unzip(zip.getBytes()); + Path tempDir = org.apiaddicts.apitools.apigen.generatorcore.utils.ZipUtils.unzip(zip.getBytes()); try (Stream stream = Files.walk(tempDir).filter(Files::isRegularFile).map(Path::toFile)) { List files = stream.collect(Collectors.toList()); if (files.isEmpty()) { diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/ConfigController.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/web/ConfigController.java similarity index 68% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/ConfigController.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/web/ConfigController.java index 1bb5546..7f8e206 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/ConfigController.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/web/ConfigController.java @@ -1,9 +1,9 @@ -package net.cloudappi.apigen.generatorrest.web; +package org.apiaddicts.apitools.apigen.generatorrest.web; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.generator.ApigenProjectGenerator; -import net.cloudappi.apigen.generatorcore.utils.ZipUtils; -import net.cloudappi.apigen.generatorrest.uitls.OpenAPIZipUtils; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.generator.ApigenProjectGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.utils.ZipUtils; +import org.apiaddicts.apitools.apigen.generatorrest.uitls.OpenAPIZipUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; diff --git a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/GeneratorController.java b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/web/GeneratorController.java similarity index 79% rename from generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/GeneratorController.java rename to generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/web/GeneratorController.java index c154aa7..36d5a43 100644 --- a/generator-rest/src/main/java/net/cloudappi/apigen/generatorrest/web/GeneratorController.java +++ b/generator-rest/src/main/java/org/apiaddicts/apitools/apigen/generatorrest/web/GeneratorController.java @@ -1,10 +1,10 @@ -package net.cloudappi.apigen.generatorrest.web; +package org.apiaddicts.apitools.apigen.generatorrest.web; -import net.cloudappi.apigen.generatorcore.config.Configuration; -import net.cloudappi.apigen.generatorcore.generator.ApigenProjectGenerator; -import net.cloudappi.apigen.generatorcore.generator.Project; -import net.cloudappi.apigen.generatorcore.utils.ZipUtils; -import net.cloudappi.apigen.generatorrest.uitls.OpenAPIZipUtils; +import org.apiaddicts.apitools.apigen.generatorcore.config.Configuration; +import org.apiaddicts.apitools.apigen.generatorcore.generator.ApigenProjectGenerator; +import org.apiaddicts.apitools.apigen.generatorcore.generator.Project; +import org.apiaddicts.apitools.apigen.generatorcore.utils.ZipUtils; +import org.apiaddicts.apitools.apigen.generatorrest.uitls.OpenAPIZipUtils; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; diff --git a/generator-rest/src/main/resources/application.properties b/generator-rest/src/main/resources/application.properties index 68451a9..ac7dc40 100644 --- a/generator-rest/src/main/resources/application.properties +++ b/generator-rest/src/main/resources/application.properties @@ -1,4 +1,4 @@ -logging.level.net.cloudappi.apigen=debug +logging.level.org.apiaddicts.apitools.apigen=debug spring.servlet.multipart.enabled=true spring.servlet.multipart.max-file-size=10MB spring.servlet.multipart.max-request-size=15MB @@ -8,6 +8,6 @@ spring.application.name=@name@ spring.application.description=@description@ spring.application.version=@version@ -apigen.parent.group=net.cloudappi.apigen +apigen.parent.group=org.apiaddicts.apitools.apigen apigen.parent.artifact=archetype-parent-spring-boot -apigen.parent.version=0.0.1-SNAPSHOT \ No newline at end of file +apigen.parent.version=0.0.3-SNAPSHOT \ No newline at end of file diff --git a/generator-rest/src/test/java/net/cloudappi/apigen/generatorrest/AppTests.java b/generator-rest/src/test/java/org/apiaddicts/apitools/apigen/generatorrest/AppTests.java similarity index 78% rename from generator-rest/src/test/java/net/cloudappi/apigen/generatorrest/AppTests.java rename to generator-rest/src/test/java/org/apiaddicts/apitools/apigen/generatorrest/AppTests.java index 510108e..ef7f677 100644 --- a/generator-rest/src/test/java/net/cloudappi/apigen/generatorrest/AppTests.java +++ b/generator-rest/src/test/java/org/apiaddicts/apitools/apigen/generatorrest/AppTests.java @@ -1,4 +1,4 @@ -package net.cloudappi.apigen.generatorrest; +package org.apiaddicts.apitools.apigen.generatorrest; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; diff --git a/pom.xml b/pom.xml index bc6ffb1..d0a8388 100644 --- a/pom.xml +++ b/pom.xml @@ -6,21 +6,51 @@ org.springframework.boot spring-boot-starter-parent - 2.3.3.RELEASE + 2.4.0 - net.cloudappi.apigen + org.apiaddicts.apitools.apigen apigen - 0.0.1-SNAPSHOT + 0.0.3-SNAPSHOT pom apigen Spring Boot Archetype Generator + https://github.com/apiaddicts/apigen + + + + GNU Lesser General Public License (LGPLV3+) + http://www.gnu.org/licenses/lgpl-3.0.html + + + + + Apiaddicts + https://apiaddicts.org + + + + scm:git:git://github.com/apiaddicts/apigen.git + scm:git:ssh://github.com:apiaddicts/apigen.git + https://github.com/apiaddicts/apigen/tree/master + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + - 0.0.1-SNAPSHOT + 0.0.3-SNAPSHOT 1.8 1.8 @@ -42,8 +72,15 @@ 0.8.5 jacoco - ${project.basedir}/../target/jacoco.exec - java + reuseReports + ${project.basedir}/target/jacoco.exec + + 3.2.1 + 3.2.0 + 1.6 + 3.0.0-M3 + + false @@ -56,12 +93,12 @@ - net.cloudappi.apigen + org.apiaddicts.apitools.apigen archetype-core ${revision} - net.cloudappi.apigen + org.apiaddicts.apitools.apigen generator-core ${revision} @@ -110,6 +147,67 @@ + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-maven + + enforce + + + + + 3.6.0 + + + true + + + + org.codehaus.mojo flatten-maven-plugin @@ -144,16 +242,23 @@ jacoco-maven-plugin ${jacoco.maven.plugin.version} - ${sonar.jacoco.reportPath} + ${maven.test.skip} + ${sonar.jacoco.reportPaths} true - agent prepare-agent + + jacoco-report + test + + report + + From 125c45624d987e9b1630f31f8d1ada4fbe90537b Mon Sep 17 00:00:00 2001 From: Adrian Palanques Date: Fri, 9 Jul 2021 11:59:04 +0200 Subject: [PATCH 7/7] Publish version --- README.md | 16 +++- apigen-examples/composed-id-models/data.sql | 26 +++--- archetype-core/pom.xml | 6 +- archetype-parent-spring-boot/pom.xml | 89 +++++++++++++++---- generator-core/pom.xml | 6 +- .../generator/base/PomFileGeneratorTest.java | 2 +- generator-rest/Dockerfile | 1 + generator-rest/pom.xml | 6 +- .../src/main/resources/application.properties | 2 +- pom.xml | 8 +- 10 files changed, 115 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 38def82..0c6540f 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,24 @@

# OpenAPI Apigen Extension -# contributors +# Contributors + ## CloudAPPi CloudAppi is one leader in APIs in global word. See the [CloudAPPi Services](https://cloudappi.net) +# Usage + +## Docker compose + +```yaml +version: "3.3" +services: + apigen: + image: "apiaddicts/apitools-apigen:0.0.3" + ports: + - "8080:8080" +``` + ## Proyecto ### Esquema diff --git a/apigen-examples/composed-id-models/data.sql b/apigen-examples/composed-id-models/data.sql index 836a548..cf37c7d 100644 --- a/apigen-examples/composed-id-models/data.sql +++ b/apigen-examples/composed-id-models/data.sql @@ -1,16 +1,16 @@ -insert into tags(str_value, int_value, name) values ('T', 1, 'Tag 1'); -insert into tags(str_value, int_value, name) values ('T', 2, 'Tag 2'); +insert into tag(id_s, id_n, name) values ('T', 1, 'Tag 1'); +insert into tag(id_s, id_n, name) values ('T', 2, 'Tag 2'); -insert into owners(str_value, int_value, name) values ('O', 1, 'Owner 1'); -insert into owners(str_value, int_value, name) values ('O', 2, 'Owner 2'); -insert into owners(str_value, int_value, name) values ('O', 3, 'Owner 3'); +insert into owner(id_s, id_n, name) values ('O', 1, 'Owner 1'); +insert into owner(id_s, id_n, name) values ('O', 2, 'Owner 2'); +insert into owner(id_s, id_n, name) values ('O', 3, 'Owner 3'); -insert into pets(str_value, int_value, name, owner_s, owner_i, parent_s, parent_i, tag_s, tag_i) values ('P', 1, 'Dog', 'O', 1, null, null, 'T', 1); -insert into pets(str_value, int_value, name, owner_s, owner_i, parent_s, parent_i, tag_s, tag_i) values ('P', 2, 'Cat', 'O', 1, 'P', 1, 'T', 1); -insert into pets(str_value, int_value, name, owner_s, owner_i, parent_s, parent_i, tag_s, tag_i) values ('P', 3, 'Potato', 'O', 1, 'P', 2, 'T', 2); +insert into pet(id_s, id_n, name, owner_id_s, owner_id_n, parent_id_s, parent_id_n, tag_id_s, tag_id_n) values ('P', 1, 'Dog', 'O', 1, null, null, 'T', 1); +insert into pet(id_s, id_n, name, owner_id_s, owner_id_n, parent_id_s, parent_id_n, tag_id_s, tag_id_n) values ('P', 2, 'Cat', 'O', 1, 'P', 1, 'T', 1); +insert into pet(id_s, id_n, name, owner_id_s, owner_id_n, parent_id_s, parent_id_n, tag_id_s, tag_id_n) values ('P', 3, 'Potato', 'O', 1, 'P', 2, 'T', 2); -insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 1, 'T', 1); -insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 1, 'T', 2); -insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 2, 'T', 1); -insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 2, 'T', 2); -insert into pet_tags(pet_s_id, pet_i_id, tag_s_id, tag_i_id) values ('P', 3, 'T', 2); +insert into pet_tags(pet_id_s, pet_id_n, tag_id_s, tag_id_n) values ('P', 1, 'T', 1); +insert into pet_tags(pet_id_s, pet_id_n, tag_id_s, tag_id_n) values ('P', 1, 'T', 2); +insert into pet_tags(pet_id_s, pet_id_n, tag_id_s, tag_id_n) values ('P', 2, 'T', 1); +insert into pet_tags(pet_id_s, pet_id_n, tag_id_s, tag_id_n) values ('P', 2, 'T', 2); +insert into pet_tags(pet_id_s, pet_id_n, tag_id_s, tag_id_n) values ('P', 3, 'T', 2); diff --git a/archetype-core/pom.xml b/archetype-core/pom.xml index 08f518e..cd102f8 100644 --- a/archetype-core/pom.xml +++ b/archetype-core/pom.xml @@ -7,7 +7,7 @@ org.apiaddicts.apitools.apigen apigen - 0.0.3-SNAPSHOT + 0.0.3 ../pom.xml @@ -37,11 +37,11 @@ ossrh - https://oss.sonatype.org/content/repositories/snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ diff --git a/archetype-parent-spring-boot/pom.xml b/archetype-parent-spring-boot/pom.xml index 7abfb90..0ed5983 100644 --- a/archetype-parent-spring-boot/pom.xml +++ b/archetype-parent-spring-boot/pom.xml @@ -14,7 +14,7 @@ org.apiaddicts.apitools.apigen archetype-parent-spring-boot - 0.0.3-SNAPSHOT + 0.0.3 pom https://github.com/apiaddicts/apigen @@ -39,17 +39,17 @@ ossrh - https://oss.sonatype.org/content/repositories/snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - 0.0.3-SNAPSHOT + 0.0.3 1.8 1.8 @@ -58,10 +58,15 @@ UTF-8 UTF-8 - 0.0.3-SNAPSHOT + 0.0.3 1.4.2.Final 2.9.2 0.2.0 + + 3.2.1 + 3.2.0 + 1.6 + 3.0.0-M3 @@ -133,6 +138,67 @@ + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-maven + + enforce + + + + + 3.6.0 + + + true + + + + org.springframework.boot spring-boot-maven-plugin @@ -166,17 +232,4 @@ - - - - false - - - true - - cloudappi-maven-snapshots - https://nexus.cloudappi.net/repository/maven-snapshots/ - - - \ No newline at end of file diff --git a/generator-core/pom.xml b/generator-core/pom.xml index 9230fed..289a431 100644 --- a/generator-core/pom.xml +++ b/generator-core/pom.xml @@ -7,7 +7,7 @@ apigen org.apiaddicts.apitools.apigen - 0.0.3-SNAPSHOT + 0.0.3 ../pom.xml @@ -38,11 +38,11 @@ ossrh - https://oss.sonatype.org/content/repositories/snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ diff --git a/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomFileGeneratorTest.java b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomFileGeneratorTest.java index 86b53b9..0d1b0bb 100644 --- a/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomFileGeneratorTest.java +++ b/generator-core/src/test/java/org/apiaddicts/apitools/apigen/generatorcore/generator/base/PomFileGeneratorTest.java @@ -29,7 +29,7 @@ void thatGenerates(@TempDir File projectFolder) throws IOException { configuration, "org.apiaddicts.apitools.apigen", "archetype-parent-spring-boot", - "0.0.3-SNAPSHOT", + "0.0.3", projectFolder ); assertPomExists(projectFolder); diff --git a/generator-rest/Dockerfile b/generator-rest/Dockerfile index 97804f3..6bc1451 100644 --- a/generator-rest/Dockerfile +++ b/generator-rest/Dockerfile @@ -2,4 +2,5 @@ FROM openjdk:8-jre-alpine VOLUME /tmp ARG JAR_FILE COPY ${JAR_FILE} app.jar +EXPOSE 8080 ENTRYPOINT ["java","-Xms256m","-Xmx512m","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] \ No newline at end of file diff --git a/generator-rest/pom.xml b/generator-rest/pom.xml index e035bcc..c65160e 100644 --- a/generator-rest/pom.xml +++ b/generator-rest/pom.xml @@ -8,7 +8,7 @@ apigen org.apiaddicts.apitools.apigen - 0.0.3-SNAPSHOT + 0.0.3 ../pom.xml @@ -38,11 +38,11 @@ ossrh - https://oss.sonatype.org/content/repositories/snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ diff --git a/generator-rest/src/main/resources/application.properties b/generator-rest/src/main/resources/application.properties index ac7dc40..6d06fa7 100644 --- a/generator-rest/src/main/resources/application.properties +++ b/generator-rest/src/main/resources/application.properties @@ -10,4 +10,4 @@ spring.application.version=@version@ apigen.parent.group=org.apiaddicts.apitools.apigen apigen.parent.artifact=archetype-parent-spring-boot -apigen.parent.version=0.0.3-SNAPSHOT \ No newline at end of file +apigen.parent.version=0.0.3 \ No newline at end of file diff --git a/pom.xml b/pom.xml index d0a8388..2da84a6 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.apiaddicts.apitools.apigen apigen - 0.0.3-SNAPSHOT + 0.0.3 pom apigen @@ -40,17 +40,17 @@ ossrh - https://oss.sonatype.org/content/repositories/snapshots + https://s01.oss.sonatype.org/content/repositories/snapshots ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ - 0.0.3-SNAPSHOT + 0.0.3 1.8 1.8