From b4922d319d293894fddd512d29b5f0d1411915d9 Mon Sep 17 00:00:00 2001 From: ARULNA Date: Mon, 12 Jun 2017 16:41:12 -0400 Subject: [PATCH] Initial commit for AAI-UI(sparky-backend) Change-Id: I785397ed4197663cdf0c1351041d2f708ed08763 Signed-off-by: ARULNA --- .gitattributes | 2 + .gitignore | 9 + .gitreview | 4 + LICENSE | 22 + README.MD | 73 + VIEW_INSPECT.MD | 27 + VNFS.MD | 14 + ajsc-shared-config/README.txt | 2 + ajsc-shared-config/etc/aft.properties | 15 + .../etc/basic-logback_root_logger_level_off.xml | 87 + ajsc-shared-config/etc/logback.xml | 192 + ajsc-shared-config/etc/spm2.jks | Bin 0 -> 62008 bytes antBuild/build.xml | 234 + appconfig-local/aai.properties | 76 + appconfig-local/auth/SAS-client-cert.p12 | Bin 0 -> 5813 bytes appconfig-local/auth/aai-client-cert-SDA.p12 | Bin 0 -> 5813 bytes appconfig-local/auth/aai-client-cert.p12 | Bin 0 -> 5813 bytes appconfig-local/auth/amdocs-il01-client-cert.p12 | Bin 0 -> 5954 bytes appconfig-local/auth/inventory-ui-keystore | Bin 0 -> 2903 bytes appconfig-local/auth/jssecacerts | Bin 0 -> 107287 bytes appconfig-local/auth/synchronizer.jks | Bin 0 -> 94364 bytes appconfig-local/auth/tabular-client-cert.p12 | Bin 0 -> 5813 bytes appconfig-local/auth/tempcrt.pem | 108 + appconfig-local/auth/tomcat_keystore | Bin 0 -> 7201 bytes appconfig-local/elasticsearch.properties | 54 + appconfig-local/model/aai_oxm_v9.xml | 4775 ++++++++++++++++++++ appconfig-local/portal-authentication.properties | 14 + appconfig-local/portal.properties | 23 + .../portal/portal-authentication.properties | 14 + appconfig-local/portal/portal.properties | 23 + appconfig-local/roles.config | 6 + appconfig-local/search-service.properties | 16 + appconfig-local/suggestive-search.properties | 11 + appconfig-local/synchronizer.properties | 30 + bundleconfig-local/README.txt | 2 + bundleconfig-local/RELEASE_NOTES.txt | 2 + .../etc/appprops/AAFUserRoles.properties | 13 + .../appprops/PostProcessorInterceptors.properties | 3 + .../appprops/PreProcessorInterceptors.properties | 4 + .../etc/appprops/app-intercepts.properties | 8 + bundleconfig-local/etc/appprops/caet.properties | 6 + .../etc/appprops/csp-cookie-filter.properties | 18 + .../etc/appprops/methodMapper.properties | 46 + .../etc/appprops/source-of-truth.properties.bak | 47 + .../etc/appprops/visualization.properties | 20 + .../etc/sysprops/sys-props.properties | 118 + eclipse-config/eclipse-java-google-style.xml | 295 ++ pom.xml | 582 +++ project-configs/code-tools/sonar-secret.txt | 1 + .../v1/conf/HelloWorldBeans.xml | 8 + .../inventory-ui-service/v1/conf/jaxrsBeans.groovy | 11 + .../inventory-ui-service/v1/docs/README.txt | 1 + .../inventory-ui-service/v1/lib/README.txt | 1 + .../inventory-ui-service/v1/props/module.props | 1 + .../v1/routes/helloServlet.route | 4 + .../v1/routes/helloWorld.route | 4 + .../v1/routes/jaxrsExample.route | 4 + .../v1/routes/serverStaticContent.route | 4 + src/main/assemble/ajsc_module_assembly.xml | 69 + src/main/assemble/ajsc_props_assembly.xml | 26 + src/main/assemble/ajsc_runtime_assembly.xml | 47 + src/main/config/aaiEntityNodeDescriptors.json | 188 + src/main/config/ajsc-chef.jks | Bin 0 -> 5256 bytes src/main/config/ajsc-jetty.xml | 128 + src/main/config/ajsc-override-web.xml | 78 + src/main/config/ajscJetty.jks | Bin 0 -> 3736 bytes src/main/config/autoSuggestMappings.json | 10 + src/main/config/autoSuggestSettings.json | 21 + src/main/config/cadi.properties | 36 + src/main/config/csp-cookie-filter.properties | 18 + src/main/config/dynamicMappings.json | 14 + src/main/config/entityCountHistoryMappings.json | 16 + src/main/config/es_mappings.json | 32 + src/main/config/es_settings.json | 36 + src/main/config/jul-redirect.properties | 13 + src/main/config/keyfile | 27 + src/main/config/runner-web.xml | 124 + src/main/docker/Dockerfile | 29 + src/main/java/org/openecomp/sparky/HelloWorld.java | 49 + .../org/openecomp/sparky/JaxrsEchoService.java | 83 + .../org/openecomp/sparky/JaxrsUserService.java | 64 + .../sparky/analytics/AbstractStatistics.java | 180 + .../sparky/analytics/AveragingRingBuffer.java | 122 + .../sparky/analytics/ComponentStatistics.java | 81 + .../sparky/analytics/HistogramSampler.java | 287 ++ .../sparky/analytics/HistoricalCounter.java | 155 + .../org/openecomp/sparky/config/Configurable.java | 46 + .../config/exception/ConfigurationException.java | 34 + .../sparky/config/oxm/CrossEntityReference.java | 80 + .../sparky/config/oxm/OxmEntityDescriptor.java | 179 + .../sparky/config/oxm/OxmModelLoader.java | 534 +++ .../sparky/config/oxm/OxmModelLoaderFilter.java | 88 + .../openecomp/sparky/dal/NetworkTransaction.java | 135 + .../sparky/dal/aai/ActiveInventoryAdapter.java | 418 ++ .../dal/aai/ActiveInventoryDataProvider.java | 91 + .../dal/aai/ActiveInventoryEntityStatistics.java | 307 ++ ...tiveInventoryProcessingExceptionStatistics.java | 139 + .../dal/aai/config/ActiveInventoryConfig.java | 159 + .../dal/aai/config/ActiveInventoryRestConfig.java | 283 ++ .../dal/aai/config/ActiveInventorySslConfig.java | 217 + .../dal/aai/enums/RestAuthenticationMode.java | 69 + .../openecomp/sparky/dal/cache/EntityCache.java | 63 + .../sparky/dal/cache/InMemoryEntityCache.java | 101 + .../sparky/dal/cache/PersistentEntityCache.java | 305 ++ .../dal/elasticsearch/ElasticSearchAdapter.java | 165 + .../elasticsearch/ElasticSearchDataProvider.java | 66 + .../ElasticSearchEntityStatistics.java | 274 ++ .../dal/elasticsearch/HashQueryResponse.java | 59 + .../sparky/dal/elasticsearch/SearchAdapter.java | 122 + .../elasticsearch/config/ElasticSearchConfig.java | 543 +++ .../exception/ElasticSearchOperationException.java | 54 + .../org/openecomp/sparky/dal/rest/HttpMethod.java | 34 + .../openecomp/sparky/dal/rest/OperationResult.java | 198 + .../sparky/dal/rest/RestClientBuilder.java | 148 + .../sparky/dal/rest/RestDataProvider.java | 112 + .../sparky/dal/rest/RestOperationalStatistics.java | 256 ++ .../sparky/dal/rest/RestfulDataAccessor.java | 357 ++ .../sparky/dal/sas/config/SearchServiceConfig.java | 224 + .../ResettableStreamHttpServletRequest.java | 129 + .../inventory/EntityHistoryQueryBuilder.java | 144 + .../sparky/inventory/entity/GeoIndexDocument.java | 322 ++ .../inventory/entity/TopographicalEntity.java | 221 + .../servlet/EntityCountHistoryServlet.java | 376 ++ .../inventory/servlet/GeoVisualizationServlet.java | 223 + .../org/openecomp/sparky/logging/AaiUiMsgs.java | 422 ++ .../sparky/logging/util/LoggingUtils.java | 44 + .../openecomp/sparky/search/EntityTypeSummary.java | 53 + .../sparky/search/EntityTypeSummaryBucket.java | 46 + .../sparky/search/SearchEntityProperties.java | 49 + .../org/openecomp/sparky/search/Suggestion.java | 59 + .../openecomp/sparky/search/SuggestionList.java | 72 + .../sparky/search/VnfSearchQueryBuilder.java | 200 + .../openecomp/sparky/search/VnfSearchService.java | 348 ++ .../sparky/search/config/SuggestionConfig.java | 143 + .../org/openecomp/sparky/security/EcompSso.java | 160 + .../sparky/security/SecurityContextFactory.java | 79 + .../security/SecurityContextFactoryImpl.java | 206 + .../sparky/security/filter/CspCookieFilter.java | 271 ++ .../sparky/security/filter/LoginFilter.java | 230 + .../security/portal/PortalRestAPIServiceImpl.java | 229 + .../sparky/security/portal/UserManager.java | 171 + .../portal/config/PortalAuthenticationConfig.java | 99 + .../sparky/security/portal/config/RolesConfig.java | 91 + .../sparky/suggestivesearch/SuggestionEntity.java | 59 + .../synchronizer/AbstractEntitySynchronizer.java | 559 +++ .../AggregationSuggestionSynchronizer.java | 187 + .../synchronizer/AggregationSynchronizer.java | 772 ++++ .../synchronizer/AutosuggestionSynchronizer.java | 736 +++ .../CrossEntityReferenceSynchronizer.java | 879 ++++ .../synchronizer/ElasticSearchIndexCleaner.java | 642 +++ .../sparky/synchronizer/GeoSynchronizer.java | 469 ++ .../synchronizer/HistoricalEntitySummarizer.java | 374 ++ .../sparky/synchronizer/IndexCleaner.java | 58 + .../synchronizer/IndexIntegrityValidator.java | 165 + .../sparky/synchronizer/IndexSynchronizer.java | 68 + .../sparky/synchronizer/IndexValidator.java | 59 + .../sparky/synchronizer/MyErrorHandler.java | 94 + .../synchronizer/SearchableEntitySynchronizer.java | 760 ++++ .../sparky/synchronizer/SyncController.java | 480 ++ .../openecomp/sparky/synchronizer/SyncHelper.java | 705 +++ .../sparky/synchronizer/TaskProcessingStats.java | 136 + .../synchronizer/TransactionRateController.java | 113 + .../config/SynchronizerConfiguration.java | 444 ++ .../synchronizer/config/SynchronizerConstants.java | 63 + .../synchronizer/config/TaskProcessorConfig.java | 328 ++ .../synchronizer/entity/AggregationEntity.java | 116 + .../entity/AggregationSuggestionEntity.java | 86 + .../sparky/synchronizer/entity/IndexDocument.java | 45 + .../entity/IndexableCrossEntityReference.java | 119 + .../synchronizer/entity/IndexableEntity.java | 106 + .../sparky/synchronizer/entity/MergableEntity.java | 60 + .../synchronizer/entity/ObjectIdCollection.java | 79 + .../synchronizer/entity/SearchableEntity.java | 152 + .../synchronizer/entity/SelfLinkDescriptor.java | 91 + .../entity/SuggestionSearchEntity.java | 279 ++ .../entity/TransactionStorageType.java | 57 + .../synchronizer/enumeration/OperationState.java | 33 + .../enumeration/SynchronizerState.java | 33 + .../filter/ElasticSearchSynchronizerFilter.java | 111 + .../task/CollectEntitySelfLinkTask.java | 77 + .../task/CollectEntityTypeSelfLinksTask.java | 78 + .../task/GetCrossEntityReferenceEntityTask.java | 78 + .../task/PerformActiveInventoryRetrieval.java | 93 + .../synchronizer/task/PerformElasticSearchPut.java | 85 + .../task/PerformElasticSearchRetrieval.java | 69 + .../task/PerformElasticSearchUpdate.java | 83 + .../task/PersistOperationResultToDisk.java | 88 + .../task/RetrieveOperationResultFromDisk.java | 92 + .../synchronizer/task/StoreDocumentTask.java | 81 + .../org/openecomp/sparky/util/ConfigHelper.java | 194 + .../openecomp/sparky/util/EncryptConvertor.java | 150 + .../java/org/openecomp/sparky/util/Encryptor.java | 137 + .../java/org/openecomp/sparky/util/ErrorUtil.java | 63 + .../openecomp/sparky/util/JsonXmlConverter.java | 80 + .../org/openecomp/sparky/util/KeystoreBuilder.java | 525 +++ .../java/org/openecomp/sparky/util/NodeUtils.java | 714 +++ .../org/openecomp/sparky/util/RawByteHelper.java | 177 + .../org/openecomp/sparky/util/ServletUtils.java | 164 + .../sparky/util/SuggestionsPermutation.java | 82 + .../java/org/openecomp/sparky/util/TreeWalker.java | 137 + .../viewandinspect/EntityTypeAggregation.java | 94 + .../config/TierSupportUiConstants.java | 90 + .../viewandinspect/config/VisualizationConfig.java | 199 + .../viewandinspect/entity/ActiveInventoryNode.java | 778 ++++ .../entity/D3VisualizationOutput.java | 132 + .../sparky/viewandinspect/entity/EntityEntry.java | 82 + .../sparky/viewandinspect/entity/GraphMeta.java | 148 + .../viewandinspect/entity/InlineMessage.java | 71 + .../sparky/viewandinspect/entity/JsonNode.java | 197 + .../sparky/viewandinspect/entity/JsonNodeLink.java | 76 + .../sparky/viewandinspect/entity/NodeDebug.java | 60 + .../sparky/viewandinspect/entity/NodeMeta.java | 212 + .../entity/NodeProcessingTransaction.java | 103 + .../sparky/viewandinspect/entity/QueryParams.java | 58 + .../sparky/viewandinspect/entity/QueryRequest.java | 48 + .../viewandinspect/entity/QuerySearchEntity.java | 75 + .../viewandinspect/entity/RelatedToProperty.java | 65 + .../sparky/viewandinspect/entity/Relationship.java | 92 + .../viewandinspect/entity/RelationshipData.java | 64 + .../entity/RelationshipDirectionality.java | 43 + .../viewandinspect/entity/RelationshipList.java | 58 + .../viewandinspect/entity/SearchResponse.java | 93 + .../entity/SelfLinkDeterminationTransaction.java | 82 + .../sparky/viewandinspect/entity/Violations.java | 114 + .../enumeration/NodeProcessingAction.java | 36 + .../enumeration/NodeProcessingState.java | 33 + .../services/SearchServiceWrapper.java | 809 ++++ .../services/VisualizationContext.java | 1649 +++++++ .../services/VisualizationService.java | 387 ++ .../services/VisualizationTransformer.java | 320 ++ .../viewandinspect/servlet/SearchServlet.java | 194 + .../servlet/VisualizationServlet.java | 203 + .../task/CollectNodeSelfLinkTask.java | 60 + .../task/PerformNodeSelfLinkProcessingTask.java | 104 + .../task/PerformSelfLinkDeterminationTask.java | 96 + src/main/resources/authentication/tomcat_keystore | Bin 0 -> 7201 bytes src/main/resources/extApps/aai.war | Bin 0 -> 999673 bytes src/main/resources/extApps/aai.xml | 9 + src/main/resources/logging/AAIUIMsgs.properties | 797 ++++ ...ame__#__module.ajsc.namespace.version__.context | 1 + src/main/runtime/context/default#0.context | 1 + ...e.name__#__module.ajsc.namespace.version__.json | 1 + src/main/runtime/shiroRole/ajscadmin.json | 1 + ...ontextadmin#__module.ajsc.namespace.name__.json | 1 + .../runtime/shiroRole/contextadmin#default.json | 1 + src/main/runtime/shiroUser/ajsc.json | 1 + src/main/runtime/shiroUserRole/ajsc#ajscadmin.json | 1 + ...ontextadmin#__module.ajsc.namespace.name__.json | 1 + .../shiroUserRole/ajsc#contextadmin#default.json | 1 + src/main/scripts/encNameValue.sh | 20 + src/main/scripts/start.sh | 42 + .../sparky/analytics/AveragingRingBufferTest.java | 133 + .../sparky/analytics/HistogramSamplerTest.java | 90 + .../analytics/TransactionRateControllerTest.java | 217 + .../dal/aai/config/ActiveInventoryConfigTest.java | 182 + .../aai/config/ActiveInventoryRestConfigTest.java | 293 ++ .../aai/config/ActiveInventorySslConfigTest.java | 268 ++ .../dal/elasticsearch/ElasticSearchConfigTest.java | 272 ++ .../entity/AutoSuggestDocumentEntity.java | 44 + .../entity/AutoSuggestDocumentEntityFields.java | 81 + .../entity/AutoSuggestElasticHitEntity.java | 87 + .../entity/AutoSuggestElasticHitsEntity.java | 50 + .../entity/AutoSuggestElasticSearchResponse.java | 85 + .../dal/elasticsearch/entity/BucketEntity.java | 61 + .../dal/elasticsearch/entity/ElasticHit.java | 29 + .../elasticsearch/entity/ElasticHitsEntity.java | 74 + .../entity/ElasticSearchAggegrationResponse.java | 109 + .../entity/ElasticSearchAggregation.java | 74 + .../entity/ElasticSearchCountResponse.java | 60 + .../dal/elasticsearch/entity/PayloadEntity.java | 32 + .../sparky/dal/rest/RestClientBuilderTest.java | 180 + .../sparky/dal/rest/RestfulDataAccessorTest.java | 226 + .../sparky/dal/sas/entity/DocumentEntity.java | 68 + .../sparky/dal/sas/entity/EntityCountResponse.java | 55 + .../dal/sas/entity/GroupByAggregationEntity.java | 60 + .../entity/GroupByAggregationResponseEntity.java | 48 + .../openecomp/sparky/dal/sas/entity/HitEntity.java | 48 + .../sas/entity/SearchAbstractionEntityBuilder.java | 295 ++ .../dal/sas/entity/SearchAbstractionResponse.java | 39 + .../sparky/dal/sas/entity/SearchResult.java | 49 + .../sparky/inventory/GeoIndexDocumentTest.java | 121 + .../security/SecurityContextFactoryImplTest.java | 141 + .../portal/TestPortalRestAPIServiceImpl.java | 269 ++ .../sparky/security/portal/TestUserManager.java | 205 + .../synchronizer/AsyncRateControlTester.java | 245 + .../sparky/synchronizer/IndexDocumentTest.java | 107 + .../sparky/synchronizer/SyncControllerBuilder.java | 581 +++ .../sparky/util/CaptureLoggerAppender.java | 247 + .../sparky/util/ElasticEntitySummarizer.java | 173 + .../sparky/util/ElasticGarbageInjector.java | 170 + .../org/openecomp/sparky/util/ExceptionHelper.java | 62 + .../openecomp/sparky/util/HttpServletHelper.java | 161 + .../org/openecomp/sparky/util/LogValidator.java | 85 + .../openecomp/sparky/util/ModelLoaderTester.java | 46 + .../org/openecomp/sparky/util/NodeUtilsTest.java | 489 ++ .../openecomp/sparky/util/OxmModelLoaderTest.java | 166 + .../sparky/util/SuggestionsPermutationsTest.java | 35 + .../org/openecomp/sparky/util/TreeWalkerTest.java | 563 +++ .../viewandinspect/ActiveInventoryNodeTester.java | 379 ++ .../sparky/viewandinspect/SearchAdapterTest.java | 90 + .../sparky/viewandinspect/SearchResponseTest.java | 92 + .../sparky/viewandinspect/SearchServletTest.java | 786 ++++ .../viewandinspect/SearchableGroupsTest.java | 73 + .../SelfLinkNodeCollectorTester.java | 69 + .../ViewAndInspectSearchRequestTest.java | 81 + .../viewandinspect/entity/EntityEntryTest.java | 74 + .../etc/appprops/source-of-truth.properties | 47 + src/test/resources/es_test_scripts/commands.txt | 3 + .../resources/es_test_scripts/geoEntities.json | 6 + .../es_test_scripts/prepareGeoEntityBulkImport.pl | 41 + .../es_test_scripts/sampleGeoEntities.csv | 4 + .../es_test_scripts/topoHistoryBulkLoad.json | 24 + .../es_test_scripts/topoHistoryConfigSettings.json | 20 + .../topographicalConfigSettings.json | 24 + .../es_test_scripts/topographysearch_schema.json | 9 + .../portal/portal-authentication.properties | 2 + src/test/resources/portal/roles.config | 6 + 317 files changed, 48786 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .gitreview create mode 100644 LICENSE create mode 100644 README.MD create mode 100644 VIEW_INSPECT.MD create mode 100644 VNFS.MD create mode 100644 ajsc-shared-config/README.txt create mode 100644 ajsc-shared-config/etc/aft.properties create mode 100644 ajsc-shared-config/etc/basic-logback_root_logger_level_off.xml create mode 100644 ajsc-shared-config/etc/logback.xml create mode 100644 ajsc-shared-config/etc/spm2.jks create mode 100644 antBuild/build.xml create mode 100644 appconfig-local/aai.properties create mode 100644 appconfig-local/auth/SAS-client-cert.p12 create mode 100644 appconfig-local/auth/aai-client-cert-SDA.p12 create mode 100644 appconfig-local/auth/aai-client-cert.p12 create mode 100644 appconfig-local/auth/amdocs-il01-client-cert.p12 create mode 100644 appconfig-local/auth/inventory-ui-keystore create mode 100644 appconfig-local/auth/jssecacerts create mode 100644 appconfig-local/auth/synchronizer.jks create mode 100644 appconfig-local/auth/tabular-client-cert.p12 create mode 100644 appconfig-local/auth/tempcrt.pem create mode 100644 appconfig-local/auth/tomcat_keystore create mode 100644 appconfig-local/elasticsearch.properties create mode 100644 appconfig-local/model/aai_oxm_v9.xml create mode 100644 appconfig-local/portal-authentication.properties create mode 100644 appconfig-local/portal.properties create mode 100644 appconfig-local/portal/portal-authentication.properties create mode 100644 appconfig-local/portal/portal.properties create mode 100644 appconfig-local/roles.config create mode 100644 appconfig-local/search-service.properties create mode 100644 appconfig-local/suggestive-search.properties create mode 100644 appconfig-local/synchronizer.properties create mode 100644 bundleconfig-local/README.txt create mode 100644 bundleconfig-local/RELEASE_NOTES.txt create mode 100644 bundleconfig-local/etc/appprops/AAFUserRoles.properties create mode 100644 bundleconfig-local/etc/appprops/PostProcessorInterceptors.properties create mode 100644 bundleconfig-local/etc/appprops/PreProcessorInterceptors.properties create mode 100644 bundleconfig-local/etc/appprops/app-intercepts.properties create mode 100644 bundleconfig-local/etc/appprops/caet.properties create mode 100644 bundleconfig-local/etc/appprops/csp-cookie-filter.properties create mode 100644 bundleconfig-local/etc/appprops/methodMapper.properties create mode 100644 bundleconfig-local/etc/appprops/source-of-truth.properties.bak create mode 100644 bundleconfig-local/etc/appprops/visualization.properties create mode 100644 bundleconfig-local/etc/sysprops/sys-props.properties create mode 100644 eclipse-config/eclipse-java-google-style.xml create mode 100644 pom.xml create mode 100644 project-configs/code-tools/sonar-secret.txt create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/HelloWorldBeans.xml create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/jaxrsBeans.groovy create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/docs/README.txt create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/lib/README.txt create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/props/module.props create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloServlet.route create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloWorld.route create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/jaxrsExample.route create mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/serverStaticContent.route create mode 100644 src/main/assemble/ajsc_module_assembly.xml create mode 100644 src/main/assemble/ajsc_props_assembly.xml create mode 100644 src/main/assemble/ajsc_runtime_assembly.xml create mode 100644 src/main/config/aaiEntityNodeDescriptors.json create mode 100644 src/main/config/ajsc-chef.jks create mode 100644 src/main/config/ajsc-jetty.xml create mode 100644 src/main/config/ajsc-override-web.xml create mode 100644 src/main/config/ajscJetty.jks create mode 100644 src/main/config/autoSuggestMappings.json create mode 100644 src/main/config/autoSuggestSettings.json create mode 100644 src/main/config/cadi.properties create mode 100644 src/main/config/csp-cookie-filter.properties create mode 100644 src/main/config/dynamicMappings.json create mode 100644 src/main/config/entityCountHistoryMappings.json create mode 100644 src/main/config/es_mappings.json create mode 100644 src/main/config/es_settings.json create mode 100644 src/main/config/jul-redirect.properties create mode 100644 src/main/config/keyfile create mode 100644 src/main/config/runner-web.xml create mode 100644 src/main/docker/Dockerfile create mode 100644 src/main/java/org/openecomp/sparky/HelloWorld.java create mode 100644 src/main/java/org/openecomp/sparky/JaxrsEchoService.java create mode 100644 src/main/java/org/openecomp/sparky/JaxrsUserService.java create mode 100644 src/main/java/org/openecomp/sparky/analytics/AbstractStatistics.java create mode 100644 src/main/java/org/openecomp/sparky/analytics/AveragingRingBuffer.java create mode 100644 src/main/java/org/openecomp/sparky/analytics/ComponentStatistics.java create mode 100644 src/main/java/org/openecomp/sparky/analytics/HistogramSampler.java create mode 100644 src/main/java/org/openecomp/sparky/analytics/HistoricalCounter.java create mode 100644 src/main/java/org/openecomp/sparky/config/Configurable.java create mode 100644 src/main/java/org/openecomp/sparky/config/exception/ConfigurationException.java create mode 100644 src/main/java/org/openecomp/sparky/config/oxm/CrossEntityReference.java create mode 100644 src/main/java/org/openecomp/sparky/config/oxm/OxmEntityDescriptor.java create mode 100644 src/main/java/org/openecomp/sparky/config/oxm/OxmModelLoader.java create mode 100644 src/main/java/org/openecomp/sparky/config/oxm/OxmModelLoaderFilter.java create mode 100644 src/main/java/org/openecomp/sparky/dal/NetworkTransaction.java create mode 100644 src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryAdapter.java create mode 100644 src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryDataProvider.java create mode 100644 src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryEntityStatistics.java create mode 100644 src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryProcessingExceptionStatistics.java create mode 100644 src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryConfig.java create mode 100644 src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryRestConfig.java create mode 100644 src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventorySslConfig.java create mode 100644 src/main/java/org/openecomp/sparky/dal/aai/enums/RestAuthenticationMode.java create mode 100644 src/main/java/org/openecomp/sparky/dal/cache/EntityCache.java create mode 100644 src/main/java/org/openecomp/sparky/dal/cache/InMemoryEntityCache.java create mode 100644 src/main/java/org/openecomp/sparky/dal/cache/PersistentEntityCache.java create mode 100644 src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchAdapter.java create mode 100644 src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchDataProvider.java create mode 100644 src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchEntityStatistics.java create mode 100644 src/main/java/org/openecomp/sparky/dal/elasticsearch/HashQueryResponse.java create mode 100644 src/main/java/org/openecomp/sparky/dal/elasticsearch/SearchAdapter.java create mode 100644 src/main/java/org/openecomp/sparky/dal/elasticsearch/config/ElasticSearchConfig.java create mode 100644 src/main/java/org/openecomp/sparky/dal/exception/ElasticSearchOperationException.java create mode 100644 src/main/java/org/openecomp/sparky/dal/rest/HttpMethod.java create mode 100644 src/main/java/org/openecomp/sparky/dal/rest/OperationResult.java create mode 100644 src/main/java/org/openecomp/sparky/dal/rest/RestClientBuilder.java create mode 100644 src/main/java/org/openecomp/sparky/dal/rest/RestDataProvider.java create mode 100644 src/main/java/org/openecomp/sparky/dal/rest/RestOperationalStatistics.java create mode 100644 src/main/java/org/openecomp/sparky/dal/rest/RestfulDataAccessor.java create mode 100644 src/main/java/org/openecomp/sparky/dal/sas/config/SearchServiceConfig.java create mode 100644 src/main/java/org/openecomp/sparky/dal/servlet/ResettableStreamHttpServletRequest.java create mode 100644 src/main/java/org/openecomp/sparky/inventory/EntityHistoryQueryBuilder.java create mode 100644 src/main/java/org/openecomp/sparky/inventory/entity/GeoIndexDocument.java create mode 100644 src/main/java/org/openecomp/sparky/inventory/entity/TopographicalEntity.java create mode 100644 src/main/java/org/openecomp/sparky/inventory/servlet/EntityCountHistoryServlet.java create mode 100644 src/main/java/org/openecomp/sparky/inventory/servlet/GeoVisualizationServlet.java create mode 100644 src/main/java/org/openecomp/sparky/logging/AaiUiMsgs.java create mode 100644 src/main/java/org/openecomp/sparky/logging/util/LoggingUtils.java create mode 100644 src/main/java/org/openecomp/sparky/search/EntityTypeSummary.java create mode 100644 src/main/java/org/openecomp/sparky/search/EntityTypeSummaryBucket.java create mode 100644 src/main/java/org/openecomp/sparky/search/SearchEntityProperties.java create mode 100644 src/main/java/org/openecomp/sparky/search/Suggestion.java create mode 100644 src/main/java/org/openecomp/sparky/search/SuggestionList.java create mode 100644 src/main/java/org/openecomp/sparky/search/VnfSearchQueryBuilder.java create mode 100644 src/main/java/org/openecomp/sparky/search/VnfSearchService.java create mode 100644 src/main/java/org/openecomp/sparky/search/config/SuggestionConfig.java create mode 100644 src/main/java/org/openecomp/sparky/security/EcompSso.java create mode 100644 src/main/java/org/openecomp/sparky/security/SecurityContextFactory.java create mode 100644 src/main/java/org/openecomp/sparky/security/SecurityContextFactoryImpl.java create mode 100644 src/main/java/org/openecomp/sparky/security/filter/CspCookieFilter.java create mode 100644 src/main/java/org/openecomp/sparky/security/filter/LoginFilter.java create mode 100644 src/main/java/org/openecomp/sparky/security/portal/PortalRestAPIServiceImpl.java create mode 100644 src/main/java/org/openecomp/sparky/security/portal/UserManager.java create mode 100644 src/main/java/org/openecomp/sparky/security/portal/config/PortalAuthenticationConfig.java create mode 100644 src/main/java/org/openecomp/sparky/security/portal/config/RolesConfig.java create mode 100644 src/main/java/org/openecomp/sparky/suggestivesearch/SuggestionEntity.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/AbstractEntitySynchronizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/AggregationSuggestionSynchronizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/AggregationSynchronizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/AutosuggestionSynchronizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/CrossEntityReferenceSynchronizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/ElasticSearchIndexCleaner.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/GeoSynchronizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/HistoricalEntitySummarizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/IndexCleaner.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/IndexIntegrityValidator.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/IndexSynchronizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/IndexValidator.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/MyErrorHandler.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/SearchableEntitySynchronizer.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/SyncController.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/SyncHelper.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/TaskProcessingStats.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/TransactionRateController.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/config/SynchronizerConfiguration.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/config/SynchronizerConstants.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/config/TaskProcessorConfig.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/AggregationEntity.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/AggregationSuggestionEntity.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/IndexDocument.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/IndexableCrossEntityReference.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/IndexableEntity.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/MergableEntity.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/ObjectIdCollection.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/SearchableEntity.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/SelfLinkDescriptor.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/SuggestionSearchEntity.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/entity/TransactionStorageType.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/enumeration/OperationState.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/enumeration/SynchronizerState.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/filter/ElasticSearchSynchronizerFilter.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/CollectEntitySelfLinkTask.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/CollectEntityTypeSelfLinksTask.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/GetCrossEntityReferenceEntityTask.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/PerformActiveInventoryRetrieval.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchPut.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchRetrieval.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchUpdate.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/PersistOperationResultToDisk.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/RetrieveOperationResultFromDisk.java create mode 100644 src/main/java/org/openecomp/sparky/synchronizer/task/StoreDocumentTask.java create mode 100644 src/main/java/org/openecomp/sparky/util/ConfigHelper.java create mode 100644 src/main/java/org/openecomp/sparky/util/EncryptConvertor.java create mode 100644 src/main/java/org/openecomp/sparky/util/Encryptor.java create mode 100644 src/main/java/org/openecomp/sparky/util/ErrorUtil.java create mode 100644 src/main/java/org/openecomp/sparky/util/JsonXmlConverter.java create mode 100644 src/main/java/org/openecomp/sparky/util/KeystoreBuilder.java create mode 100644 src/main/java/org/openecomp/sparky/util/NodeUtils.java create mode 100644 src/main/java/org/openecomp/sparky/util/RawByteHelper.java create mode 100644 src/main/java/org/openecomp/sparky/util/ServletUtils.java create mode 100644 src/main/java/org/openecomp/sparky/util/SuggestionsPermutation.java create mode 100644 src/main/java/org/openecomp/sparky/util/TreeWalker.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/EntityTypeAggregation.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/config/TierSupportUiConstants.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/config/VisualizationConfig.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/ActiveInventoryNode.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/D3VisualizationOutput.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/EntityEntry.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/GraphMeta.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/InlineMessage.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/JsonNode.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/JsonNodeLink.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeDebug.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeMeta.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeProcessingTransaction.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/QueryParams.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/QueryRequest.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/QuerySearchEntity.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/RelatedToProperty.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/Relationship.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipData.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipDirectionality.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipList.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/SearchResponse.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/SelfLinkDeterminationTransaction.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/entity/Violations.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/enumeration/NodeProcessingAction.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/enumeration/NodeProcessingState.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/services/SearchServiceWrapper.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationContext.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationService.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationTransformer.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/servlet/SearchServlet.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/servlet/VisualizationServlet.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/task/CollectNodeSelfLinkTask.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTask.java create mode 100644 src/main/java/org/openecomp/sparky/viewandinspect/task/PerformSelfLinkDeterminationTask.java create mode 100644 src/main/resources/authentication/tomcat_keystore create mode 100644 src/main/resources/extApps/aai.war create mode 100644 src/main/resources/extApps/aai.xml create mode 100644 src/main/resources/logging/AAIUIMsgs.properties create mode 100644 src/main/runtime/context/__module.ajsc.namespace.name__#__module.ajsc.namespace.version__.context create mode 100644 src/main/runtime/context/default#0.context create mode 100644 src/main/runtime/deploymentPackage/__module.ajsc.namespace.name__#__module.ajsc.namespace.version__.json create mode 100644 src/main/runtime/shiroRole/ajscadmin.json create mode 100644 src/main/runtime/shiroRole/contextadmin#__module.ajsc.namespace.name__.json create mode 100644 src/main/runtime/shiroRole/contextadmin#default.json create mode 100644 src/main/runtime/shiroUser/ajsc.json create mode 100644 src/main/runtime/shiroUserRole/ajsc#ajscadmin.json create mode 100644 src/main/runtime/shiroUserRole/ajsc#contextadmin#__module.ajsc.namespace.name__.json create mode 100644 src/main/runtime/shiroUserRole/ajsc#contextadmin#default.json create mode 100644 src/main/scripts/encNameValue.sh create mode 100644 src/main/scripts/start.sh create mode 100644 src/test/java/org/openecomp/sparky/analytics/AveragingRingBufferTest.java create mode 100644 src/test/java/org/openecomp/sparky/analytics/HistogramSamplerTest.java create mode 100644 src/test/java/org/openecomp/sparky/analytics/TransactionRateControllerTest.java create mode 100644 src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryConfigTest.java create mode 100644 src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryRestConfigTest.java create mode 100644 src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventorySslConfigTest.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchConfigTest.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestDocumentEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestDocumentEntityFields.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticHitEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticHitsEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticSearchResponse.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/BucketEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticHit.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticHitsEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchAggegrationResponse.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchAggregation.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchCountResponse.java create mode 100644 src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/PayloadEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/rest/RestClientBuilderTest.java create mode 100644 src/test/java/org/openecomp/sparky/dal/rest/RestfulDataAccessorTest.java create mode 100644 src/test/java/org/openecomp/sparky/dal/sas/entity/DocumentEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/sas/entity/EntityCountResponse.java create mode 100644 src/test/java/org/openecomp/sparky/dal/sas/entity/GroupByAggregationEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/sas/entity/GroupByAggregationResponseEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/sas/entity/HitEntity.java create mode 100644 src/test/java/org/openecomp/sparky/dal/sas/entity/SearchAbstractionEntityBuilder.java create mode 100644 src/test/java/org/openecomp/sparky/dal/sas/entity/SearchAbstractionResponse.java create mode 100644 src/test/java/org/openecomp/sparky/dal/sas/entity/SearchResult.java create mode 100644 src/test/java/org/openecomp/sparky/inventory/GeoIndexDocumentTest.java create mode 100644 src/test/java/org/openecomp/sparky/security/SecurityContextFactoryImplTest.java create mode 100644 src/test/java/org/openecomp/sparky/security/portal/TestPortalRestAPIServiceImpl.java create mode 100644 src/test/java/org/openecomp/sparky/security/portal/TestUserManager.java create mode 100644 src/test/java/org/openecomp/sparky/synchronizer/AsyncRateControlTester.java create mode 100644 src/test/java/org/openecomp/sparky/synchronizer/IndexDocumentTest.java create mode 100644 src/test/java/org/openecomp/sparky/synchronizer/SyncControllerBuilder.java create mode 100644 src/test/java/org/openecomp/sparky/util/CaptureLoggerAppender.java create mode 100644 src/test/java/org/openecomp/sparky/util/ElasticEntitySummarizer.java create mode 100644 src/test/java/org/openecomp/sparky/util/ElasticGarbageInjector.java create mode 100644 src/test/java/org/openecomp/sparky/util/ExceptionHelper.java create mode 100644 src/test/java/org/openecomp/sparky/util/HttpServletHelper.java create mode 100644 src/test/java/org/openecomp/sparky/util/LogValidator.java create mode 100644 src/test/java/org/openecomp/sparky/util/ModelLoaderTester.java create mode 100644 src/test/java/org/openecomp/sparky/util/NodeUtilsTest.java create mode 100644 src/test/java/org/openecomp/sparky/util/OxmModelLoaderTest.java create mode 100644 src/test/java/org/openecomp/sparky/util/SuggestionsPermutationsTest.java create mode 100644 src/test/java/org/openecomp/sparky/util/TreeWalkerTest.java create mode 100644 src/test/java/org/openecomp/sparky/viewandinspect/ActiveInventoryNodeTester.java create mode 100644 src/test/java/org/openecomp/sparky/viewandinspect/SearchAdapterTest.java create mode 100644 src/test/java/org/openecomp/sparky/viewandinspect/SearchResponseTest.java create mode 100644 src/test/java/org/openecomp/sparky/viewandinspect/SearchServletTest.java create mode 100644 src/test/java/org/openecomp/sparky/viewandinspect/SearchableGroupsTest.java create mode 100644 src/test/java/org/openecomp/sparky/viewandinspect/SelfLinkNodeCollectorTester.java create mode 100644 src/test/java/org/openecomp/sparky/viewandinspect/ViewAndInspectSearchRequestTest.java create mode 100644 src/test/java/org/openecomp/sparky/viewandinspect/entity/EntityEntryTest.java create mode 100644 src/test/resources/bundleconfig/etc/appprops/source-of-truth.properties create mode 100644 src/test/resources/es_test_scripts/commands.txt create mode 100644 src/test/resources/es_test_scripts/geoEntities.json create mode 100644 src/test/resources/es_test_scripts/prepareGeoEntityBulkImport.pl create mode 100644 src/test/resources/es_test_scripts/sampleGeoEntities.csv create mode 100644 src/test/resources/es_test_scripts/topoHistoryBulkLoad.json create mode 100644 src/test/resources/es_test_scripts/topoHistoryConfigSettings.json create mode 100644 src/test/resources/es_test_scripts/topographicalConfigSettings.json create mode 100644 src/test/resources/es_test_scripts/topographysearch_schema.json create mode 100644 src/test/resources/portal/portal-authentication.properties create mode 100644 src/test/resources/portal/roles.config diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7984d45 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.jks binary +*.p12 binary \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..477d6d1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.classpath +.project +.settings/ +aaiOffline/ +ElasticSearchServletTest.txt +target/ +logs/ +debug-logs/ + diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..0ea5a18 --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=gerrit.onap.org +port=29418 +project=aai/sparky-be diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7f4ec50 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +============LICENSE_START=================================================== +SPARKY (AAI UI service) +============================================================================ +Copyright © 2017 AT&T Intellectual Property. +Copyright © 2017 Amdocs +All rights reserved. +============================================================================ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/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. +============LICENSE_END===================================================== + +ECOMP and OpenECOMP are trademarks +and service marks of AT&T Intellectual Property. \ No newline at end of file diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..0bc0439 --- /dev/null +++ b/README.MD @@ -0,0 +1,73 @@ +# Sparky - Inventory UI Service + +## Overview +_Sparky_ is a service that interacts with AAI and provides users a UI to view and analyze AAI data. The main goal behind _Sparky_ is providing a more user friendly and clear view of AAI data. + +At this time, _Sparky_ has two views available for use: + +[View and Inspect](./VIEW_INSPECT.md) - Graph based view of entities within AAI. + +[VNFs](./VNFS.md) - Aggregation based view of VNFs within AAI. + +## Getting Started + +### Building _Sparky_ + +After cloning the project, execute the following Maven command from the project's top level directory to build the project: + + > mvn clean install + +After a successful install, build the docker image: + + > docker build -t openecomp/sparky target + +### Deploying _Sparky_ + +Push the Docker image that you have built to your Docker repository and pull it down to the location that you will be running _Sparky_. + +**Create the following directories on the host machine:** + + /logs + /opt/app/sparky/appconfig + +You will be mounting these as data volumes when you start the Docker container. + +#### Clone Configuration Repo + +Clone the "test-config" repo to a seperate directory. +Navigate to /sparky/appconfig (will contain files such as aai.properties). +Copy the entire contents of /sparky/appconfig into the /opt/app/sparky/appconfig directory you created in an above step. + +#### Edits to property files in /opt/app/sparky/appconfig + +Listed below are the values that will need to be updated to make _Sparky_ operate properly. The config files contain comments on the contents not listed here. + +**search-service.properties** + +search-service.ipAddress= +search-service.httpPort= + +**aai.properties** + +aai.rest.host= +aai.rest.port= + +**elasticsearch.properties** + +elasticsearch.ipAddress= +elasticsearch.httpPort= +elasticsearch.javaApiPort= + +**portal/portal.properties** +**portal/portal-authentication.properties** + +If this instance of _Sparky_ will be served in an eCOMP Portal instance, use the two files above to configure against the proper Portal instance. + +### Dependencies + +_Sparky_ requires: + +- AAI instance as the main driver behind data. +- Elasticsearch instance for data storage. +- search-data-service instance for search functionality. +- eCOMP Portal instance for authentication. \ No newline at end of file diff --git a/VIEW_INSPECT.MD b/VIEW_INSPECT.MD new file mode 100644 index 0000000..fcd2cf9 --- /dev/null +++ b/VIEW_INSPECT.MD @@ -0,0 +1,27 @@ +# Sparky - Inventory UI Service + +### _View & Inspect_ Overview + +_View & Inspect_ provides a graph based view of elements within AAI. A single entity is the entry point into each graph, and from that base element a graph is generated based off relationships. + +### Navigation to _View & Inspect_ + +The _View & Inspect_ view can be reached by two means: + +1. Main navigation menu +2. Selecting a search result related to an entity instance (e.g. an entity called readme-entity) + +### Using _View & Inspect_ + +_View & Inspect_ is driven by using the search bar at the top of the UI to find and select entity instances. Once an instance has been slected, a request is proccessed in _Sparky's_ backend component that generates a graph representation of the selected entity. The graph data is returned to _View & Inspect_ and rendered on screen. + +#### Node Details + +Upon node selection, the selected graph node details will appear in a panel to the right of the graph titled, _Node Details_. + +### Interacting with the Graph + +The graph can be panned by clicking and holding empty space amongst the graph and moving the mouse. This will pan the entire graph. +The graph can be zoomed in and out by using a mouse scroll wheel. +Nodes in the graph can be select by clicking on them. +Nodes in the graph can be moved by clicking, holding, and dragging them using the mouse. \ No newline at end of file diff --git a/VNFS.MD b/VNFS.MD new file mode 100644 index 0000000..e83eb3e --- /dev/null +++ b/VNFS.MD @@ -0,0 +1,14 @@ +# Sparky - Inventory UI Service + +### _VNFs_ Overview + +_VNFs_ is an aggregation based view that provides aggregate counts of VNFs based off of provsioning status and orchestration status. + +### Navigation to _VNFs_ + +1. Main navigation menu +2. Selecting a search result related to an aggregation result (e.g. and VNFs) + +### Using _VNFs_ + +_VNFs_ is driven by using the search bar at the top of the UI to find and select aggregation queries. Once selected, the aggregation queries will be sent to the _Sparky_ backend component for processing. When a result set has been determined _VNFs_ will render the data. \ No newline at end of file diff --git a/ajsc-shared-config/README.txt b/ajsc-shared-config/README.txt new file mode 100644 index 0000000..37f2670 --- /dev/null +++ b/ajsc-shared-config/README.txt @@ -0,0 +1,2 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. +The bundleconfig-local directory contains the necessary configuration files \ No newline at end of file diff --git a/ajsc-shared-config/etc/aft.properties b/ajsc-shared-config/etc/aft.properties new file mode 100644 index 0000000..95c7762 --- /dev/null +++ b/ajsc-shared-config/etc/aft.properties @@ -0,0 +1,15 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. +# Flow test 319 +# The DEFAULT setup for this file is for deployment to soa cloud node which will use the "bundleconfig/etc/spm2.jks" location +# For Testing Locally, you can set the system property, csiEnable=true, found within bundleconfig-local/etc/sysprops/sys-props.properties +# and switch com.att.aft.keyStore and com.att.aft.trustStore values commented out below to "ajsc-shared-config/etc/spm2.jks" + +#replace proper values for the dummy values. +com.att.aft.discovery.client.environment=TEST +com.att.aft.discovery.client.latitude=35.318900 +com.att.aft.discovery.client.longitude=-80.762200 +com.att.aft.alias=fusionbus +com.att.aft.keyStore=bundleconfig/etc/key.jks +com.att.aft.keyStorePassword=password +com.att.aft.trustStore=bundleconfig/etc/key.jks +com.att.aft.trustStorePassword=password diff --git a/ajsc-shared-config/etc/basic-logback_root_logger_level_off.xml b/ajsc-shared-config/etc/basic-logback_root_logger_level_off.xml new file mode 100644 index 0000000..4ebe2db --- /dev/null +++ b/ajsc-shared-config/etc/basic-logback_root_logger_level_off.xml @@ -0,0 +1,87 @@ + + + + + + ERROR + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{1024} - %msg%n + + + + + + + DEBUG + + ${logDirectory}/info_ajsc.log + + ${logDirectory}/info_ajsc.%i.log.zip + + 1 + 9 + + + 5MB + + + "%d [%thread] %-5level %logger{1024} - %msg%n" + + + + + ERROR + + ${logDirectory}/error_ajsc.log + + ${logDirectory}/error_ajsc.%i.log.zip + + 1 + 9 + + + 5MB + + + + "%d [%thread] %-5level %logger{1024} - %msg%n" + + + + + + INFO + + localhost + USER + + AJSC_AUDIT: [%thread] [%logger] %msg + + + + INFO + + localhost + USER + + AJSC_AUDIT: [%thread] [%logger] mdc:[%mdc] %msg + + + + + + + + + diff --git a/ajsc-shared-config/etc/logback.xml b/ajsc-shared-config/etc/logback.xml new file mode 100644 index 0000000..9913e73 --- /dev/null +++ b/ajsc-shared-config/etc/logback.xml @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + ${errorLogPattern} + + + + + + + + + + + ${logDirectory}/${generalLogName}.log + + ${logDirectory}/${generalLogName}.%d{yyyy-MM-dd}.log.gz + + 60 + + + ${errorLogPattern} + + + + + + INFO + + 256 + + + + + + + + ${logDirectory}/${auditLogName}.log + + ${logDirectory}/${auditLogName}.%d{yyyy-MM-dd}.log.gz + 60 + + + ${auditMetricPattern} + + + + 256 + + + + + + ${logDirectory}/${metricsLogName}.log + + ${logDirectory}/${metricsLogName}.%d{yyyy-MM-dd}.log.gz + 60 + + + + ${auditMetricPattern} + + + + + 256 + + + + + + + + ${logDirectory}/${debugLogName}.log + + ${logDirectory}/${debugLogName}.%d{yyyy-MM-dd}.log.gz + 60 + + + ${errorLogPattern} + + + + + 256 + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ajsc-shared-config/etc/spm2.jks b/ajsc-shared-config/etc/spm2.jks new file mode 100644 index 0000000000000000000000000000000000000000..8ff2a00a105aab80a301cb1e67b567d272d71466 GIT binary patch literal 62008 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/appconfig-local/aai.properties b/appconfig-local/aai.properties new file mode 100644 index 0000000..70e3712 --- /dev/null +++ b/appconfig-local/aai.properties @@ -0,0 +1,76 @@ +# +# ActiveInventoryDataCollector TLS/SSL configuration +# +aai.rest.host=aai-ext1.test.att.com +aai.rest.port=8443 +aai.rest.resourceBasePath=/aai/v9 +aai.rest.connectTimeoutInMs=30000 +aai.rest.readTimeoutInMs=60000 +aai.rest.numRequestRetries=5 +aai.rest.numResolverWorkers=15 +# +aai.rest.cache.enabled=false +aai.rest.cache.numWorkers=10 +aai.rest.cache.cacheFailures=false +aai.rest.cache.useCacheOnly=false +aai.rest.cache.storageFolderOverride= +aai.rest.cache.maxTimeToLiveInMs=-1 +# +# +# The shallowEntity filter will display the entity in a visualization +# but will not collect it's relationships or complex attributes. +# +aai.rest.shallowEntities=cloud-region,complex,vnf-image,att-aic,image +# +aai.ssl.truststore.filename=synchronizer.jks +aai.ssl.truststore.type=jks +# +aai.ssl.keystore.filename=aai-client-cert.p12 +aai.ssl.keystore.pass=OBF:1i9a1u2a1unz1lr61wn51wn11lss1unz1u301i6o +aai.ssl.keystore.type=pkcs12 +# +aai.ssl.enableDebug=false +aai.ssl.validateServerHostName=false; +aai.ssl.validateServerCertificateChain=false; +# +# +# HTTP_NOAUTH - straight HTTP no user/pass +# SSL_BASIC - HTTP/S with user/pass +# SSL_CERT - HTTP/S with client cert +# +aai.rest.authenticationMode=SSL_BASIC +aai.ssl.basicAuth.username=AaiUI +aai.ssl.basicAuth.password=OBF:1gfr1p571unz1p4j1gg7 +# +# +aai.taskProcessor.maxConcurrentWorkers=5 +# +aai.taskProcessor.transactionRateControllerEnabled=false +aai.taskProcessor.numSamplesPerThreadForRunningAverage=100 +aai.taskProcessor.targetTPS=100 +# +aai.taskProcessor.bytesHistogramLabel="[Response Size In Bytes]" +aai.taskProcessor.bytesHistogramMaxYAxis=1000000 +aai.taskProcessor.bytesHistogramNumBins=20 +aai.taskProcessor.bytesHistogramNumDecimalPoints=2 +# +aai.taskProcessor.queueLengthHistogramLabel="[Queue Item Length]" +aai.taskProcessor.queueLengthHistogramMaxYAxis=20000 +aai.taskProcessor.queueLengthHistogramNumBins=20 +aai.taskProcessor.queueLengthHistogramNumDecimalPoints=2 +# +aai.taskProcessor.taskAgeHistogramLabel="[Task Age In Ms]" +aai.taskProcessor.taskAgeHistogramMaxYAxis=600000 +aai.taskProcessor.taskAgeHistogramNumBins=20 +aai.taskProcessor.taskAgeHistogramNumDecimalPoints=2 +# +aai.taskProcessor.responseTimeHistogramLabel="[Response Time In Ms]" +aai.taskProcessor.responseTimeHistogramMaxYAxis=10000 +aai.taskProcessor.responseTimeHistogramNumBins=20 +aai.taskProcessor.responseTimeHistogramNumDecimalPoints=2 +# +aai.taskProcessor.tpsHistogramLabel="[Transactions Per Second]" +aai.taskProcessor.tpsHistogramMaxYAxis=100 +aai.taskProcessor.tpsHistogramNumBins=20 +aai.taskProcessor.tpsHistogramNumDecimalPoints=2 + diff --git a/appconfig-local/auth/SAS-client-cert.p12 b/appconfig-local/auth/SAS-client-cert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..71d60c4cd399f93fe4fdc9c426b02a426eecd56f GIT binary patch literal 5813 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 literal 0 HcmV?d00001 diff --git a/appconfig-local/auth/aai-client-cert-SDA.p12 b/appconfig-local/auth/aai-client-cert-SDA.p12 new file mode 100644 index 0000000000000000000000000000000000000000..71d60c4cd399f93fe4fdc9c426b02a426eecd56f GIT binary patch literal 5813 zcmY+IRa6v!OVAJ0umUp*uuUVCe1+rMrgiE{S0PrBh<)M!I`{zjJo? zzx!}szI)Dnybm8wFljvsGAd6nDFhvhBU~x`78e-g|Gj{NjLw4xz%;N@7D{0lC`CcRL<$ThnDiE4*QF9+ z^ZK!J8Jid$vP<67)tUkrW?mhd1?}wc`7xqNTS!+Vn9SC7JeXDIUYHRFCc(SXP8XEQ zu`t*&gm-yjRqrIagRHj5-GNU20mx#J;fCwp1-UBC z3L$AHVLz&!D#O)5PzwazYcwc+1kGsNcmW5L)HNA0CIx+a2xZ zx|}c?HV-%-UmGEx_WR)<0Hn0qYT&%`o=KPP#wjlzZm5}g@ricnTV2-YYDcRAf3zXt z#~B)BK}ORfaWLXLC5nj9-(Zi3C5;c@?r6Nni$imzxLTDYoe8TgKG6!4$`WGt+C0@Z z14xA7?r3%B;c0H{)anfI&XTl!4 zWwo6}T z3e9P$hE3^kGjQgcJe;Nzvso<{HAxto03=l{Wd9os=FJ~Sc^gkb#HIAXC+wS`H5J5P z27pG#H%S!7&*QrjD(c1`4Lrg~OIN95igl5L#<8UBC$dx`zrzFvZfJDBcWc+KwN-Rec;u;Qw{uQ)|t%twj`$H2&gfc9h z-_`#*`Ak8HYY#fi&vdh!9yxQHK-eneP5nc*F?kZ%TeTb+OrB!!7DqExeJ+R**YNS# ztjhWI9inq_nT>~rEH>$5A*rWl(%ulPs`3h266x99H$#XZY2N=`^=p6X7+}MrtJ1J& zNCWfA)xDTZQP7cbA9!`C8?)bc$&C4;D|7_5yG zEC4ld7>S(erE6Z9-9y1oqt$-h@lIxHsI{R@wyKrER?T1mSy2) zaczF%dQy=uKVwgjW;#N;&5*`cz(rPKnfVLZ=rc>7ONNUU_BRjLD_Ka|A-fLN4Gd{b z%vt+~5ncrmQF;C5{X-wa^tyL?hUz356p>||q9^~uX7`w$=&$Jfv(no#q>NS#{Tjn@ z{&_s%ZG)}axj-QWV-DbB`cDOQ2cv>Dao9pR;_cbMn0IE*@;yf6;P|GH6BpePyLY~N zO%YCLt+~{~5NGDpDh1idy)HsotmnWav0&?@FbAJu8*?h57Mafwm*D9L@8Ug}pCXuP z!h`T36KpeqGyHB*PxL6q9|T`s!aD2zJFcccttb?&QA~S-Fp8aV;HuvEw(IvbO%HxI zx5g|mUNB{DWZeE(KhM@gvA#tdF87HWL5C@!-E66i;Y>U}m8Ni|D8p?VCjFJdo<0?$ z#ge@+gGPxoz58dU{y;Bsqop=U%F~Npy$j701*^}KQP&Xwr1EfC_aIr^EL=7_uh&(GP2;BSUrR4&K(WO;89y122skA-i%8L<6+?4E5U9} z$*W#MUZup9F}^IRWsrN3{HT+T8D&dNWxi=+->&kO`O{j9*Q)#LI5am^aVf7{+ENO;nAPhces2*w=GXJE4WXf6m-s=a17z|2lwDhp8OAqbTP@ zm4BRP1|80Zs+ie+&!S?WG(>l=df3+>Iy{j|wVw*+l-ZH5a*Ux}1%c6RG=PlAg|)l# zbCXDe_iy5~s_go{uzj_&lMsxRC!spxT5HP9k>Nw;wleI{80WIY#4AbOTz_}Y$B88? z%KpZBcTO%7`^s?a(nm&TB2>S~*0Xk@2_n*oXi%1xg${M^e)qk~pqF?{Lmg!xdc2Om zQ@gEoGES)t=Nn8%`>q&XQKc_rN9YLNzwKLg8O1Besk;5@MkCUdzT=)qh8_z(mPT`z z6M5-*K1CtCUHpyN;@kWzvW3HL1Yvt-*f`DI-J!d*>^)-+<`>&+jR`=Hk z==yfha%N|VP`QZjrnI!BsA6KH5!&AA*=bIkvD*9hbT#Q$Ox`8X@PJPh`}gaxs8*j` zc1h6v2gYsg>G^zY`U56rd``TBAXAY9!EJUKegA)oB^zP*(=H3-P6anq+qc8tn;T3P zBZ8w*MIBdjuGKn0FoIbh26m^CHN%cg3nwAIU%qHzeqwoA$&r_qsQ@R&u+MUZ9>aVC zEF>p_>@SDE|K7mCui4F9n5(iceDGE+5cquAc>;L2sxADRs4wKrxZvBcUg!q)0Z|jE z=C5vPjFd^_Rp%TY;v2C6gJ!0GB*LiP5;*uuov9-;5?5c{31dhkO;WQD5o7n?N~kA7vxsxpiB&uatB5`ZU{i4THs zMa=28xe%DyvfVRN_LK?CO3A1jd-TBLOG2Io;tC4q5C6TXR&r{xWS8& zVi{KK?@y;X9zrXqNgelNQ522d7aF0{?%xo+OF@SU*ZK}`Zfzuay;g_cpg7H*Q)<*izcQtlDP{zKB{%!BrM_d zFE`9b@|UVRL@_85vJM)hQoH5)(QB0hb^IMqFAa`oq}xo)=6p?9u%CM_Gw5Rdy%Vq$ zUw%SzTC0{XrNgxj6imF%NWt%|;sW<-sa?NxG<|ObhpfMO6i1@j?-6l(&tdd(Q}-yo z5JM$arGR>(9n}`#b@>VF7WFMvl6~2Fl4r=N9ufLPodtJOGPfQH0irJI`Hy)0kQ@cu zePKKah{ActSBFg{YDfrOMF}_FuLPI~;n~x59oM0_ef?T&|JVQSsx8ndhh^amxE0a;PA+NbwVCC@)MB zE0%zc{$tu;r&yX$wyTL3;YM`h)vjJkXfd#%_@ykLKZX^xe@V%GQXnMvI<4V~aEJug zQI{7^D1}|=>Fbn%GN+aO(s4+i5}!Bk<>ivSkF$M{jy>E_e?Vvwcm6?C6l2RF6@JY- z-WpNIH6~Z25c{lH0I!^O>e+&tv9(66IksTyS6FVSDya3w`TSud!>UfxtPI=En;1ry zlO&Lbh9}20%7zrbjbxm~I)>fZ;e-#K-)`Pt5cGhC2Zi5d5^O3GH10%6%DQ}29rcr2 zV4|Cwjb9ro8k-~s_BU2arCf8_&pJs2Hs7rhO$xE0tSDHsMneKeyGI6=z{=4U*5`O3 zY($BSaQo=Ukfx$%2|d1E9iue&=*g05s8;zs34C5cgt~m6V3zaRut{A5w=6k)?!nSx zr3s^zl1eL7XLa{Cui}ud1yW$(*n$sAE~B^4X=z8Gt+~m}Jjm;*`9sy;Hd|@|iLmdo zL)zYd>kIK}dtV4?J4myby3kJn|j>3KFP)c~X?n6C|L8P{fK4uQPSLuB6l_%f7!O#f`; zY!QOw!wHFYR+p=Z)gY;4m3b-{)&Ejyj;A*_Dvxo}&M?-5wl5Nm-SsQUPeC?0u$#Rq z4N>3d5u3$J;Xg1mG;>CZg~QnBTL%Tc>oi#mm!yBg6CT9?g3l5YouHyF|+wSg#qD6#pT?LJP>{Xq1mpU&PgTgw-X;Y zrmAOk5a?uNf21TQv)2c3+PIo*E-fy8Z{pindnHMI%USs(%u;3cS-bU0!xNtN66X={L?)&U(6+|u8JStUuVV4J=QwEwPIvcnjv9`znfa|Y(=R0%sz}+ zhY**4mTNauK=Il4D5~{oY+Q z5-&9Acn*+$8KP^uf_Ow}Ui39f)Ejo-KZ#bpYg|k#>cZvwy6Qq}XWq%Mv#C6pPZV2B zhe2DBVqMj-ofjYVdz);r%Ig42yx;Rrvh*_~jr3M)lhJx%y)io9dG^>ASD?Lh;;YKS z?F-_phkRCEkIL%irUN$7FP~&OI8H}v`UiWNQ%jcj2G1Wly2NX0)nPVCCelTAHc8qL z9vN+;mzfvq)ty6x%I~nJffK16vEC3r8`lhz=c+8RsS$Wlp6_IwsG+Px58EXJ(q=lm zcZ4ZkQX#>bVX$~qwB;k5pe7i&jo5+T8ZE$!eDJJ%Mj_5=&!!zaq`w+~<3_QGIsGR# zn4P0m?Q_iNMdEj*S1T&x63Zi9Rlid@6^NyNKIMmoCA6=Rd`c1>Wbk8E%URbnr~(PK zJgjnJ-@U0=i?Z_4h0(R-jF{;v9#4cEBBpH*NJ$x7OZ0PMd!7*Et*-`q<@`3WvCyeOD%Rn+=yd3Nz`Vljf)-%Q6#qG)s;JCP8(ofM!u zs*jnc7RV)l*Qnf&DXmH>9Im@}Y=qYs*E;}*0Sig<>S85;+svtN$ec|Ih4p!7sSj{F zgPrV8hG~rgNwnUl8E#8pv*Ih;vhmD8!nwM7K7Q4f$jnmRj!=9p(qR;{O1ohnjy(@Q?Q`5|l#qe_z%o5{`v3~v1A{7aJss!hCoI^U&SHVk0l)zLhyGvTa57sO?xZ4Qpr)M3L)suY{=bN2WR1K}@B4aYAq=vaFk< z(_IvO$sYNgMOrAh)|HbT0b9?4hlfoJ$h@O)x~(Q^*U$2C@H1}K}ON#bzG9}_a_@$9(zPz za7-Mj3~JG;1aC0#GV?J0-*?0!;UxutfcP4}abci^@`P%l&lG0*P=Pv09$p@L9xQaU uw|J;1lxRo*kZenjVewMN^R9M~UzTtc*m%@?4}dxMNfI0WY8dd}m;7IO4-k6* literal 0 HcmV?d00001 diff --git a/appconfig-local/auth/aai-client-cert.p12 b/appconfig-local/auth/aai-client-cert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..71d60c4cd399f93fe4fdc9c426b02a426eecd56f GIT binary patch literal 5813 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 literal 0 HcmV?d00001 diff --git a/appconfig-local/auth/amdocs-il01-client-cert.p12 b/appconfig-local/auth/amdocs-il01-client-cert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..a7766a13c1529322b9d743a4a84a7e742c093f62 GIT binary patch literal 5954 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 literal 0 HcmV?d00001 diff --git a/appconfig-local/auth/jssecacerts b/appconfig-local/auth/jssecacerts new file mode 100644 index 0000000000000000000000000000000000000000..f1ce4e845ce116226161bb97bdca557f618717b6 GIT binary patch literal 107287 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Š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 bL?6xPSI!R{!Hx6USt`^mo+}Y@pKkv@QOGy& literal 0 HcmV?d00001 diff --git a/appconfig-local/auth/synchronizer.jks b/appconfig-local/auth/synchronizer.jks new file mode 100644 index 0000000000000000000000000000000000000000..3cb6e7bf988f3dbc6a39da437251a0d7623a2cc0 GIT binary patch literal 94364 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 Q-p1;951WN%wcX-B03E`&p8x;= literal 0 HcmV?d00001 diff --git a/appconfig-local/auth/tabular-client-cert.p12 b/appconfig-local/auth/tabular-client-cert.p12 new file mode 100644 index 0000000000000000000000000000000000000000..71d60c4cd399f93fe4fdc9c426b02a426eecd56f GIT binary patch literal 5813 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 literal 0 HcmV?d00001 diff --git a/appconfig-local/auth/tempcrt.pem b/appconfig-local/auth/tempcrt.pem new file mode 100644 index 0000000..d843eba --- /dev/null +++ b/appconfig-local/auth/tempcrt.pem @@ -0,0 +1,108 @@ +Bag Attributes + localKeyID: 3B 07 33 B0 20 7E 0A 3A 03 79 55 40 86 F4 A3 BE 71 36 BD 88 + friendlyName: aai-client.dev.att.com +subject=/C=US/ST=Michigan/L=Southfield/O=ATT Services, Inc./OU=aai client dev/CN=aai-client.dev.att.com +issuer=/C=US/O=Symantec Corporation/OU=Symantec Trust Network/CN=Symantec Class 3 Secure Server CA - G4 +-----BEGIN CERTIFICATE----- +MIIGDDCCBPSgAwIBAgIQFwniuSEP6YmGivoe714QJTANBgkqhkiG9w0BAQsFADB+ +MQswCQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAd +BgNVBAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxLzAtBgNVBAMTJlN5bWFudGVj +IENsYXNzIDMgU2VjdXJlIFNlcnZlciBDQSAtIEc0MB4XDTE2MDEyMTAwMDAwMFoX +DTE3MDIxMjIzNTk1OVowgYwxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhNaWNoaWdh +bjETMBEGA1UEBwwKU291dGhmaWVsZDEbMBkGA1UECgwSQVRUIFNlcnZpY2VzLCBJ +bmMuMRcwFQYDVQQLDA5hYWkgY2xpZW50IGRldjEfMB0GA1UEAwwWYWFpLWNsaWVu +dC5kZXYuYXR0LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+h +vFF2ShYYph9f6nzqHziZARY88Cucj9PtwD7lz598AEQHQXhPw+G5iVZJ2QEm3CBs +z5Zki9G1kwX+rjdzPifZmmTtWfw0rkfTVoWJfAdWep/UHN3ijM7cpTF+ae1hV5oV +wpX4Uv0QvjNr5X3botLUIT84Mf4PFihVI+GI1F8QEeOakd5J1XTrU9rchXBywu6h +BElONz+9SMlpnpmy1BgXJyvg20ZA/QJgI1onohwcRzM7PNyh+qddM9XCiG0tyxFn +argcM6GUcgx+lF7FQ5Yk+VpIzE7RjH7CbLgvFxT75dRvgi0fjsyr9eQ2G/bYUmuA +VXVALXd3WYlaICJF/nUCAwEAAaOCAnUwggJxMCEGA1UdEQQaMBiCFmFhaS1jbGll +bnQuZGV2LmF0dC5jb20wCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMGEGA1UdIARaMFgwVgYGZ4EMAQICMEww +IwYIKwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3BzMCUGCCsGAQUFBwIC +MBkaF2h0dHBzOi8vZC5zeW1jYi5jb20vcnBhMB8GA1UdIwQYMBaAFF9gz2GQVd+E +QxSKYCqy9Xr0QxjvMCsGA1UdHwQkMCIwIKAeoByGGmh0dHA6Ly9zcy5zeW1jYi5j +b20vc3MuY3JsMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYTaHR0cDovL3Nz +LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL3NzLnN5bWNiLmNvbS9zcy5j +cnQwggEGBgorBgEEAdZ5AgQCBIH3BIH0APIAdwDd6x0reg1PpiCLga2BaHB+Lo6d +AdVciI09EcTNtuy+zAAAAVJhvIePAAAEAwBIMEYCIQCsll/uYo29Lxa1fdIbxGIW +60TSeVh9+G+obtU3j/N5AAIhAJcXdm1pbRzHdUF9RWEyWIAl3DPhqVDedgzzo4Nw +39xrAHcApLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BAAAAFSYbyHyQAA +BAMASDBGAiEAxRtrQ93smDwUJF7oD+3OsqoM/MgGFO/+At+lQGj0RUICIQD19yCs +aVtvES6Av4qqY5t6E9bD4UDwAOMUoZz3RuyWKzANBgkqhkiG9w0BAQsFAAOCAQEA +LbHqJVTcNzFH90crX9M/z/Fs0oTlpM/07IEnb5OOTqQ6G9hbfzcPps5rp3OTfIdc +MqAyPqDAxbar2P6ah3fR6HarkQO4ivnWA/Drxdjs5TVPjuC0d+5X7xb9a5uiImbm +zOb9tqHAOpwQFbkv2Z3BVFzzvPN7LBmFvBGb1TYuGoUuV/13ETM6JTLvwoc5CQPw +ojfjgESYgSAK7wnoWwpVejuGV674QGl+/xf1mtmMXajwn7GMY9Qz2XkVIXKKxgec +HeC1leCoTd54kRUZ/O0dxnUbYJJ4yfGKQrsqZbV+sklGhEPbLz3sEkHCNqUzBlrd +tVhzZOeDAKlrJNaB5aY3uQ== +-----END CERTIFICATE----- +Bag Attributes: +subject=/C=US/O=Symantec Corporation/OU=Symantec Trust Network/CN=Symantec Class 3 Secure Server CA - G4 +issuer=/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5 +-----BEGIN CERTIFICATE----- +MIIFODCCBCCgAwIBAgIQUT+5dDhwtzRAQY0wkwaZ/zANBgkqhkiG9w0BAQsFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMTMxMDMxMDAwMDAwWhcNMjMxMDMwMjM1OTU5WjB+MQsw +CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV +BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxLzAtBgNVBAMTJlN5bWFudGVjIENs +YXNzIDMgU2VjdXJlIFNlcnZlciBDQSAtIEc0MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAstgFyhx0LbUXVjnFSlIJluhL2AzxaJ+aQihiw6UwU35VEYJb +A3oNL+F5BMm0lncZgQGUWfm893qZJ4Itt4PdWid/sgN6nFMl6UgfRk/InSn4vnlW +9vf92Tpo2otLgjNBEsPIPMzWlnqEIRoiBAMnF4scaGGTDw5RgDMdtLXO637QYqzu +s3sBdO9pNevK1T2p7peYyo2qRA4lmUoVlqTObQJUHypqJuIGOmNIrLRM0XWTUP8T +L9ba4cYY9Z/JJV3zADreJk20KQnNDz0jbxZKgRb78oMQw7jW2FUyPfG9D72MUpVK +Fpd6UiFjdS8W+cRmvvW1Cdj/JwDNRHxvSz+w9wIDAQABo4IBYzCCAV8wEgYDVR0T +AQH/BAgwBgEB/wIBADAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vczEuc3ltY2Iu +Y29tL3BjYTMtZzUuY3JsMA4GA1UdDwEB/wQEAwIBBjAvBggrBgEFBQcBAQQjMCEw +HwYIKwYBBQUHMAGGE2h0dHA6Ly9zMi5zeW1jYi5jb20wawYDVR0gBGQwYjBgBgpg +hkgBhvhFAQc2MFIwJgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cuc3ltYXV0aC5jb20v +Y3BzMCgGCCsGAQUFBwICMBwaGmh0dHA6Ly93d3cuc3ltYXV0aC5jb20vcnBhMCkG +A1UdEQQiMCCkHjAcMRowGAYDVQQDExFTeW1hbnRlY1BLSS0xLTUzNDAdBgNVHQ4E +FgQUX2DPYZBV34RDFIpgKrL1evRDGO8wHwYDVR0jBBgwFoAUf9Nlp8Ld7LvwMAnz +Qzn6Aq8zMTMwDQYJKoZIhvcNAQELBQADggEBAF6UVkndji1l9cE2UbYD49qecxny +H1mrWH5sJgUs+oHXXCMXIiw3k/eG7IXmsKP9H+IyqEVv4dn7ua/ScKAyQmW/hP4W +Ko8/xabWo5N9Q+l0IZE1KPRj6S7t9/Vcf0uatSDpCr3gRRAMFJSaXaXjS5HoJJtG +QGX0InLNmfiIEfXzf+YzguaoxX7+0AjiJVgIcWjmzaLmFN5OUiQt/eV5E1PnXi8t +TRttQBVSK/eHiXgSgW7ZTaoteNTCLD0IX4eRnh8OsN4wUmSGiaqdZpwOdgyA8nTY +Kvi4Os7X1g8RvmurFPW9QaAiY4nxug9vKWNmLT+sjHLF+8fk1A/yO0+MKcc= +-----END CERTIFICATE----- +Bag Attributes: +subject=/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5 +issuer=/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority +-----BEGIN CERTIFICATE----- +MIIE0DCCBDmgAwIBAgIQJQzo4DBhLp8rifcFTXz4/TANBgkqhkiG9w0BAQUFADBf +MQswCQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xNzA1BgNVBAsT +LkNsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw +HhcNMDYxMTA4MDAwMDAwWhcNMjExMTA3MjM1OTU5WjCByjELMAkGA1UEBhMCVVMx +FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz +dCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZv +ciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAz +IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8 +RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbext0uz/o9+B1fs70Pb +ZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhDY2pSS9KP6HBR +TdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/ +Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNH +iDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMB +AAGjggGbMIIBlzAPBgNVHRMBAf8EBTADAQH/MDEGA1UdHwQqMCgwJqAkoCKGIGh0 +dHA6Ly9jcmwudmVyaXNpZ24uY29tL3BjYTMuY3JsMA4GA1UdDwEB/wQEAwIBBjA9 +BgNVHSAENjA0MDIGBFUdIAAwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cudmVy +aXNpZ24uY29tL2NwczAdBgNVHQ4EFgQUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwbQYI +KwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQU +j+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24uY29t +L3ZzbG9nby5naWYwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8v +b2NzcC52ZXJpc2lnbi5jb20wPgYDVR0lBDcwNQYIKwYBBQUHAwEGCCsGAQUFBwMC +BggrBgEFBQcDAwYJYIZIAYb4QgQBBgpghkgBhvhFAQgBMA0GCSqGSIb3DQEBBQUA +A4GBABMC3fjohgDyWvj4IAxZiGIHzs73Tvm7WaGY5eE43U68ZhjTresY8g3JbT5K +lCDDPLq9ZVTGr0SzEK0saz6r1we2uIFjxfleLuUqZ87NMwwq14lWAyMfs77oOghZ +tOxFNfeKW/9mz1Cvxm1XjRl4t7mi0VfqH5pLr7rJjhJ+xr3/ +-----END CERTIFICATE----- +Bag Attributes + localKeyID: 3B 07 33 B0 20 7E 0A 3A 03 79 55 40 86 F4 A3 BE 71 36 BD 88 + friendlyName: aai-client.dev.att.com +Key Attributes: diff --git a/appconfig-local/auth/tomcat_keystore b/appconfig-local/auth/tomcat_keystore new file mode 100644 index 0000000000000000000000000000000000000000..efa01f8d79fcee29a1cbeb19395ab776dcb6b46b GIT binary patch literal 7201 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 literal 0 HcmV?d00001 diff --git a/appconfig-local/elasticsearch.properties b/appconfig-local/elasticsearch.properties new file mode 100644 index 0000000..fedb340 --- /dev/null +++ b/appconfig-local/elasticsearch.properties @@ -0,0 +1,54 @@ +# +# ElasticSearch Config +# + +# For dev always use local instance of ES + +elasticsearch.ipAddress=localhost +elasticsearch.httpPort=9200 +elasticsearch.javaApiPort=8443 +elasticsearch.indexName=entitysearchindex-localhost +elasticsearch.type=default +elasticsearch.clusterName=ES_AAI_LOCALHOST +elasticsearch.mappingsFileName=/etc/es_mappings.json +elasticsearch.settingsFileName=/etc/es_settings.json +elasticsearch.auditIndexName=di-violations +elasticsearch.topographicalIndexName=topographicalsearchindex-localhost +elasticsearch.entityCountHistoryIndexName=entitycounthistoryindex-localhost +elasticsearch.entityCountHistoryMappingsFileName=/etc/entityCountHistoryMappings.json +# +elasticsearch.taskProcessor.maxConcurrentWorkers=5 +# +elasticsearch.taskProcessor.transactionRateControllerEnabled=false +elasticsearch.taskProcessor.numSamplesPerThreadForRunningAverage=100 +elasticsearch.taskProcessor.targetTPS=100 +# +elasticsearch.taskProcessor.bytesHistogramLabel="[Response Size In Bytes]" +elasticsearch.taskProcessor.bytesHistogramMaxYAxis=1000000 +elasticsearch.taskProcessor.bytesHistogramNumBins=20 +elasticsearch.taskProcessor.bytesHistogramNumDecimalPoints=2 +# +elasticsearch.taskProcessor.queueLengthHistogramLabel="[Queue Item Length]" +elasticsearch.taskProcessor.queueLengthHistogramMaxYAxis=20000 +elasticsearch.taskProcessor.queueLengthHistogramNumBins=20 +elasticsearch.taskProcessor.queueLengthHistogramNumDecimalPoints=2 +# +elasticsearch.taskProcessor.taskAgeHistogramLabel="[Task Age In Ms]" +elasticsearch.taskProcessor.taskAgeHistogramMaxYAxis=600000 +elasticsearch.taskProcessor.taskAgeHistogramNumBins=20 +elasticsearch.taskProcessor.taskAgeHistogramNumDecimalPoints=2 +# +elasticsearch.taskProcessor.responseTimeHistogramLabel="[Response Time In Ms]" +elasticsearch.taskProcessor.responseTimeHistogramMaxYAxis=1000 +elasticsearch.taskProcessor.responseTimeHistogramNumBins=20 +elasticsearch.taskProcessor.responseTimeHistogramNumDecimalPoints=2 +# +elasticsearch.taskProcessor.tpsHistogramLabel="[Transactions Per Second]" +elasticsearch.taskProcessor.tpsHistogramMaxYAxis=100 +elasticsearch.taskProcessor.tpsHistogramNumBins=20 +elasticsearch.taskProcessor.tpsHistogramNumDecimalPoints=2 +# +elasticsearch.autosuggestIndexname=entityautosuggestindex-localhost +elasticsearch.autosuggestSettingsFileName=/etc/autoSuggestSettings.json +elasticsearch.autosuggestMappingsFileName=/etc/autoSuggestMappings.json +elasticsearch.dynamicMappingsFileName=/etc/dynamicMappings.json \ No newline at end of file diff --git a/appconfig-local/model/aai_oxm_v9.xml b/appconfig-local/model/aai_oxm_v9.xml new file mode 100644 index 0000000..6337c32 --- /dev/null +++ b/appconfig-local/model/aai_oxm_v9.xml @@ -0,0 +1,4775 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/appconfig-local/portal-authentication.properties b/appconfig-local/portal-authentication.properties new file mode 100644 index 0000000..9c96aad --- /dev/null +++ b/appconfig-local/portal-authentication.properties @@ -0,0 +1,14 @@ +########################################################################################## +############################## eCOMP Portal Auth Properties ############################## +########################################################################################## + +############################## Auth ############################## +username=aaiui +password=aaiui + +############################## ############################## +# +# ONAP Cookie Processing - During initial development, this flag, if true, will +# prevent the portal interface's login processing from searching for a user +# specific cookie, and will instead allow passage if a valid session cookie is discovered. +onap_enabled=true \ No newline at end of file diff --git a/appconfig-local/portal.properties b/appconfig-local/portal.properties new file mode 100644 index 0000000..3092d1d --- /dev/null +++ b/appconfig-local/portal.properties @@ -0,0 +1,23 @@ +###################################################################################### +############################## eCOMP Portal properties ############################### +###################################################################################### + +# Java class that implements the ECOMP role and user mgt API +portal.api.impl.class = org.openecomp.sparky.security.portal.PortalRestAPIServiceImpl + +# Instance of ECOMP Portal where the app has been on-boarded +# use insecure http for dev purposes to avoid self-signed certificate +ecomp_rest_url = http://portal.api.simpledemo.openecomp.org:50580/ecompportal/auxapi + +# Standard global logon page +ecomp_redirect_url = http://portal.api.simpledemo.openecomp.org:8989/ECOMPPORTAL/login.htm + +# Name of cookie to extract on login request +csp_cookie_name = EPService +# Alternate values: DEVL, V_DEVL, V_PROD +csp_gate_keeper_prod_key = PROD + +# Toggles use of UEB +ueb_listeners_enable = false +# IDs application withing UEB flow +ueb_app_key = qFKles9N8gDTV0Zc diff --git a/appconfig-local/portal/portal-authentication.properties b/appconfig-local/portal/portal-authentication.properties new file mode 100644 index 0000000..9c96aad --- /dev/null +++ b/appconfig-local/portal/portal-authentication.properties @@ -0,0 +1,14 @@ +########################################################################################## +############################## eCOMP Portal Auth Properties ############################## +########################################################################################## + +############################## Auth ############################## +username=aaiui +password=aaiui + +############################## ############################## +# +# ONAP Cookie Processing - During initial development, this flag, if true, will +# prevent the portal interface's login processing from searching for a user +# specific cookie, and will instead allow passage if a valid session cookie is discovered. +onap_enabled=true \ No newline at end of file diff --git a/appconfig-local/portal/portal.properties b/appconfig-local/portal/portal.properties new file mode 100644 index 0000000..3092d1d --- /dev/null +++ b/appconfig-local/portal/portal.properties @@ -0,0 +1,23 @@ +###################################################################################### +############################## eCOMP Portal properties ############################### +###################################################################################### + +# Java class that implements the ECOMP role and user mgt API +portal.api.impl.class = org.openecomp.sparky.security.portal.PortalRestAPIServiceImpl + +# Instance of ECOMP Portal where the app has been on-boarded +# use insecure http for dev purposes to avoid self-signed certificate +ecomp_rest_url = http://portal.api.simpledemo.openecomp.org:50580/ecompportal/auxapi + +# Standard global logon page +ecomp_redirect_url = http://portal.api.simpledemo.openecomp.org:8989/ECOMPPORTAL/login.htm + +# Name of cookie to extract on login request +csp_cookie_name = EPService +# Alternate values: DEVL, V_DEVL, V_PROD +csp_gate_keeper_prod_key = PROD + +# Toggles use of UEB +ueb_listeners_enable = false +# IDs application withing UEB flow +ueb_app_key = qFKles9N8gDTV0Zc diff --git a/appconfig-local/roles.config b/appconfig-local/roles.config new file mode 100644 index 0000000..b8313bd --- /dev/null +++ b/appconfig-local/roles.config @@ -0,0 +1,6 @@ +[ + { + "id":1, + "name":"View" + } +] \ No newline at end of file diff --git a/appconfig-local/search-service.properties b/appconfig-local/search-service.properties new file mode 100644 index 0000000..d1f58a3 --- /dev/null +++ b/appconfig-local/search-service.properties @@ -0,0 +1,16 @@ +# +# Search Abstraction Service Config +# + +search-service.ipAddress=localhost +search-service.httpPort=9509 +search-service.indexName=entitysearchindex-localhost +search-service.auditIndexName=di-violations +search-service.topographicalIndexName=topographicalsearchindex-localhost +search-service.entityCountHistoryIndexName=entitycounthistoryindex-localhost +search-service.version=v1 +search-service.type=default +# +search-service.ssl.cert-name=aai-client-cert.p12 +search-service.ssl.keystore-password=OBF:1i9a1u2a1unz1lr61wn51wn11lss1unz1u301i6o +search-service.ssl.keystore=synchronizer.jks \ No newline at end of file diff --git a/appconfig-local/suggestive-search.properties b/appconfig-local/suggestive-search.properties new file mode 100644 index 0000000..60a60e6 --- /dev/null +++ b/appconfig-local/suggestive-search.properties @@ -0,0 +1,11 @@ +# +# Suggestive Search Config +# +suggestion.indexes=elasticsearch.autosuggestIndexname,elasticsearch.indexName +suggestion.stopwords=a,an,and,are,as,at,be,but,by,called,for,if,in,into,is,it,no,not,of,on,or,such,that,the,their,then,there,these,they,this,to,was,will,with +suggestion.routing=elasticsearch.autosuggestIndexname:SearchServiceWrapper,elasticsearch.indexName:VnfSearchService +suggestion.pairing.called.key=volume-group-id,volume-group-name,physical-location-id,data-center-code,complex-name,tenant-id,tenant-name,vserver-id,vserver-name,vserver-name2,hostname,pserver-name2,pserver-id,global-customer-id,subscriber-name,service-instance-id,service-instance-name,link-name,vpn-id,vpn-name,vpe-id,vnf-id,vnf-name,vnf-name2,vnfc-name,network-id,network-name,network-policy-id,vf-module-id,vf-module-name,vnf-id2,pnf-name,circuit-id +suggestion.pairing.called.value=called +suggestion.pairing.at.key=street1,street2,postal-code,ipv4-oam-address,network-policy-fqdn +suggestion.pairing.at.value=at +suggestion.pairing.default.value=with \ No newline at end of file diff --git a/appconfig-local/synchronizer.properties b/appconfig-local/synchronizer.properties new file mode 100644 index 0000000..b7270d6 --- /dev/null +++ b/appconfig-local/synchronizer.properties @@ -0,0 +1,30 @@ +# +# ElasticSearchSynchronizer Config +# + +synchronizer.syncTask.initialDelayInMs=60000 +synchronizer.syncTask.taskFrequencyInDay=2 + +#synchronizer.syncTask.startTimestamp=hh:mm:ss UTC(-/+)hh:mm +synchronizer.syncTask.startTimestamp=05:00:00 UTC+00:00 +# +synchronizer.historicalEntitySummarizerEnabled=true +synchronizer.historicalEntitySummarizedFrequencyInMinutes=60 +# +synchronizer.resolver.progressLogFrequencyInMs=60000 +synchronizer.resolver.queueMonitorFrequencyInMs=1000 +synchronizer.resolver.displayVerboseQueueManagerStats=false +# +synchronizer.indexIntegrityValidator.enabled=false +synchronizer.indexIntegrityValidatorFrequencyInMs=3600000 +# +synchronizer.scrollContextTimeToLiveInMinutes=5 +synchronizer.numScrollContextItemsToRetrievePerRequest=5000 +# +synchronizer.suppressResourceNotFoundErrors=true +# +# &nodes-only - to prevent relationship-list from being collected and returned during collection gets +synchronizer.applyNodesOnlyModifier=false +# +synchronizer.autosuggestSynchronizationEnabled=true + diff --git a/bundleconfig-local/README.txt b/bundleconfig-local/README.txt new file mode 100644 index 0000000..37f2670 --- /dev/null +++ b/bundleconfig-local/README.txt @@ -0,0 +1,2 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. +The bundleconfig-local directory contains the necessary configuration files \ No newline at end of file diff --git a/bundleconfig-local/RELEASE_NOTES.txt b/bundleconfig-local/RELEASE_NOTES.txt new file mode 100644 index 0000000..3cc5590 --- /dev/null +++ b/bundleconfig-local/RELEASE_NOTES.txt @@ -0,0 +1,2 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. +Place Release Notes here to provide updated Release information \ No newline at end of file diff --git a/bundleconfig-local/etc/appprops/AAFUserRoles.properties b/bundleconfig-local/etc/appprops/AAFUserRoles.properties new file mode 100644 index 0000000..adb7a10 --- /dev/null +++ b/bundleconfig-local/etc/appprops/AAFUserRoles.properties @@ -0,0 +1,13 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. + +#If using AAF for Role based authentication/authorization, define your routes/services which will utilize AAF. The AJSC will +#read this file and protect the routes given with the AAF role defined. + +#The following example would protect the JAXRS echo example service provided with the archetype. +#/services/${namespace}/v1/jaxrs-services/jaxrsExample/echo/*=com.att.ajsc.myper|mymachine|manage + +#The following example would protect ALL AJSC services running within your project. +#/**=com.att.ajsc.myperm|mymachine|manage + +#The following example would protect ALL REST services utilizing the Camel restlet routes. +#/rest/**=com.att.ajsc.myperm|mymachine|manage diff --git a/bundleconfig-local/etc/appprops/PostProcessorInterceptors.properties b/bundleconfig-local/etc/appprops/PostProcessorInterceptors.properties new file mode 100644 index 0000000..08ffefa --- /dev/null +++ b/bundleconfig-local/etc/appprops/PostProcessorInterceptors.properties @@ -0,0 +1,3 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. +#This properties file is for defining any PostProcessorInterceptors that have been created for your AJSC service. + diff --git a/bundleconfig-local/etc/appprops/PreProcessorInterceptors.properties b/bundleconfig-local/etc/appprops/PreProcessorInterceptors.properties new file mode 100644 index 0000000..1383071 --- /dev/null +++ b/bundleconfig-local/etc/appprops/PreProcessorInterceptors.properties @@ -0,0 +1,4 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. +#This properties file is for defining any PreProcessorInterceptors that have been created for your AJSC service. + +/**=com.att.ajsc.csi.restmethodmap.RestMethodMapInterceptor diff --git a/bundleconfig-local/etc/appprops/app-intercepts.properties b/bundleconfig-local/etc/appprops/app-intercepts.properties new file mode 100644 index 0000000..8778195 --- /dev/null +++ b/bundleconfig-local/etc/appprops/app-intercepts.properties @@ -0,0 +1,8 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. + +#This is where all your application intercept strategies must be configured. AJSC reads this property file and adds +#the list of intercepts specified here to the camel context. This can be useful for accessing every exchange object transferred from/to +#each endpoint in the request/response flow and can allow for more precise debugging and/or processing of the exchange. + +#e.g. +#intercepts=org.openecomp.inventory.JaxrsEchoService,packagename.class1name,packagename.class2name diff --git a/bundleconfig-local/etc/appprops/caet.properties b/bundleconfig-local/etc/appprops/caet.properties new file mode 100644 index 0000000..94540a4 --- /dev/null +++ b/bundleconfig-local/etc/appprops/caet.properties @@ -0,0 +1,6 @@ +#caet_service=http://DME2RESOLVE/service=com.att.csid.CAET/version=3/envContext=TEST/routeOffer=TEST_CAET +#caet_service=http://DME2RESOLVE/service=com.att.csid.CAET/version=3/envContext=TEST/routeOffer=D3A_CAET +#caet_service=dme2://DME2RESOLVE/service=com.att.csid.CAET/version=4.0/envContext=TEST/routeOffer=TEST_CAET +caet_service=http://DME2SEARCH/service=com.att.csid.CAET/version=4/envContext=TEST//partner=*/stickySelectorKey=Q23A;roundTripTimeoutInMs=240000 +timeoutMs=10000 + diff --git a/bundleconfig-local/etc/appprops/csp-cookie-filter.properties b/bundleconfig-local/etc/appprops/csp-cookie-filter.properties new file mode 100644 index 0000000..e12109a --- /dev/null +++ b/bundleconfig-local/etc/appprops/csp-cookie-filter.properties @@ -0,0 +1,18 @@ +# AT&T Global login page. This is the redirect URL +# Production login page: +# https://www.e-access.att.com/empsvcs/hrpinmgt/pagLogin/ +# +# Test login page: +# https://webtest.csp.att.com/empsvcs/hrpinmgt/pagLogin/ +global.login.url=https://www.e-access.att.com/empsvcs/hrpinmgt/pagLogin/ + +# valid domains for open redirect +redirect-domain=att.com,sbc.com,bls.com,cingular.net + +# MOTS ID of the application +application.id=24153 + +# Required by esGateKeeper. Valid values are: +# DEVL - used during development +# PROD - used in production +gatekeeper.environment=PROD \ No newline at end of file diff --git a/bundleconfig-local/etc/appprops/methodMapper.properties b/bundleconfig-local/etc/appprops/methodMapper.properties new file mode 100644 index 0000000..57e12b0 --- /dev/null +++ b/bundleconfig-local/etc/appprops/methodMapper.properties @@ -0,0 +1,46 @@ +// +//Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. +// Json object holds the method mapping.Update the JSON object with the proper route to logical mapping based +// on the example provided below : +// "helloWorld" = Service Name +// "method" = http method +// "url" = the url component from the route +// "logicalName"= When a combination of method and url from the route matches the json object , +// the logical name is put in the http header as "x-CSI-ServiceName" and "x-CSI-MethodName" +// "dme2url"= if provided it register the endpoint to GRM, it is optional. This is useful for JAX-RS services. + +{ + "helloWorld": [ + { + "method": "get", + "url": "/rest/inventory-ui-service/v1/helloWorld", + "logicalName": "GetMethod(Logical)" + }, + { + "method": "get", + "url": "/services/inventory-ui-service/v1/jaxrsExample/jaxrs-services/echo/{input}", + "logicalName": "GetJaxrsExampleEcho(Logical)", + "dme2url": "/services/inventory-ui-service/v1/jaxrsExample/jaxrs-services/echo/{input}" + }, + { + "method": "get", + "url": "/services/inventory-ui-service/v1/jaxrsExample/jaxrs-services/property/{fileName}/{input}", + "logicalName": "GetJaxrsExampleProperty(Logical)", + "dme2url": "/services/inventory-ui-service/v1/jaxrsExample/jaxrs-services/property/{fileName}/{input}" + } + ], + "errormessage": + [ + { + "method": "get", + "url": "/services/inventory-ui-service/v1/jaxrsExample/errormessage/emls", + "logicalName": "setCAETHeaders(Logical)" + }, + { + "method": "get", + "url": "/services/inventory-ui-service/v1/errorMessageLookupService2", + "logicalName": "setCAETHeaders(Logical)" + } + + ] +} \ No newline at end of file diff --git a/bundleconfig-local/etc/appprops/source-of-truth.properties.bak b/bundleconfig-local/etc/appprops/source-of-truth.properties.bak new file mode 100644 index 0000000..f08722f --- /dev/null +++ b/bundleconfig-local/etc/appprops/source-of-truth.properties.bak @@ -0,0 +1,47 @@ +# Source of Truth mappings. This file maps an enitity path to a source of truth identifier +# AAI v7 +/v7/network/ipsec-configurations/ipsec-configuration/requested-vig-address-type=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/requested-encryption-strength=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/requested-dmz-type=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/shared-dmz-network-address=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/requested-customer-name=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ike-version=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-authentication=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-encryption=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-dh-group=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-am-group-id=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-am-password=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-sa-lifetime=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ipsec-authentication=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ipsec-encryption=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ipsec-sa-lifetime=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ipsec-pfs=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/xauth-userid=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/xauth-user-password=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/dpd-interval=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/dpd-frequency=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/vig-servers=service-manager + +# AAI v8 +/v8/network/ipsec-configurations/ipsec-configuration/requested-vig-address-type=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/requested-encryption-strength=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/requested-dmz-type=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/shared-dmz-network-address=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/requested-customer-name=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ike-version=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-authentication=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-encryption=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-dh-group=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-am-group-id=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-am-password=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-sa-lifetime=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ipsec-authentication=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ipsec-encryption=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ipsec-sa-lifetime=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ipsec-pfs=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/xauth-userid=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/xauth-user-password=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/dpd-interval=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/dpd-frequency=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/vig-servers=service-manager + diff --git a/bundleconfig-local/etc/appprops/visualization.properties b/bundleconfig-local/etc/appprops/visualization.properties new file mode 100644 index 0000000..6091011 --- /dev/null +++ b/bundleconfig-local/etc/appprops/visualization.properties @@ -0,0 +1,20 @@ +# +# the visualization block is specifically for the VisualizationTransformer as part of building out +# configuration driven visualization. +# + +maxSelfLinkTraversalDepth=2 +makeAllNeighborsBidirectional=false +# +# +# +generalNodeClassName=generalNodeClass +searchedNodeClassName=searchedNodeClass +selectedSearchedNodeClassName=selectedSearchedNodeClass +visualizationDebugEnabled=false +nodeEnrichmentEnabled=false +aaiEntityNodeDescriptors=/etc/aaiEntityNodeDescriptors.json + +# +entityTypesToSummarize=customer,service-instance,complex,pserver,vserver,vnf +vnfEntityTypes=generic-vnf,newvce,vce,vpe \ No newline at end of file diff --git a/bundleconfig-local/etc/sysprops/sys-props.properties b/bundleconfig-local/etc/sysprops/sys-props.properties new file mode 100644 index 0000000..3ffc445 --- /dev/null +++ b/bundleconfig-local/etc/sysprops/sys-props.properties @@ -0,0 +1,118 @@ +#Copyright (c) 2016 AT&T Intellectual Property. All rights reserved. +#This file is used for defining AJSC system properties for different configuration schemes and is necessary for the AJSC to run properly. +#The sys-props.properties file is used for running locally. The template.sys-props.properties file will be used when deployed +#to a SOA/CSI Cloud node. + +#AJSC System Properties. The following properties are required for ALL AJSC services. If you are adding System Properties for your +#particular service, please add them AFTER all AJSC related System Properties. + +#For Cadi Authorization, use value="authentication-scheme-1 +CadiAuthN=authentication-scheme-1 + +#For Basic Authorization, use value="authentication-scheme-1 +authN=authentication-scheme-2 + +#Persistence used for AJSC meta-data storage. For most environments, "file" should be used. +ajscPersistence=file + +#For Direct Invocation to be enabled (values=true/false) +directInvocationEnable=false + +# If using hawtio for local development, these properties will allow for faster server startup and usage for local development + +hawtio.authenticationEnabled=false +hawtio.config.pullOnStartup=false + +#Removes the extraneous restlet console output +org.restlet.engine.loggerFacadeClass=org.restlet.ext.slf4j.Slf4jLoggerFacade + +#server.host property to be enabled for local DME2 related testing +#server.host= + +#Enable/disable SSL (values=true/false). This property also determines which protocol to use (https if true, http otherwise), to register services into GRM through DME2. +enableSSL=true + + +#Enable/disable EJB Container +ENABLE_EJB=false + +#Enable/disable OSGI +isOSGIEnable=false + +#Generate/Skip api docs +isApiDoc=false + +#CSI related variables for CSM framework +csm.hostname=servername + + +#SOA_CLOUD_ENV is used to register your service with dme2 and can be turned off for local development (values=true/false). +SOA_CLOUD_ENV=false + +#CONTINUE_ON_LISTENER_EXCEPTION will exit the application if there is a DME2 exception at the time of registration. +CONTINUE_ON_LISTENER_EXCEPTION=false + +#Jetty Container ThreadCount Configuration Variables +AJSC_JETTY_ThreadCount_MIN=1 +AJSC_JETTY_ThreadCount_MAX=200 +AJSC_JETTY_IDLETIME_MAX=3000 + +#Camel Context level default threadPool Profile configuration +CAMEL_POOL_SIZE=10 +CAMEL_MAX_POOL_SIZE=20 +CAMEL_KEEP_ALIVE_TIME=60 +CAMEL_MAX_QUEUE_SIZE=1000 + +#GRM/DME2 System Properties +AFT_DME2_CONN_IDLE_TIMEOUTMS=5000 +AJSC_ENV=SOACLOUD + +SOACLOUD_NAMESPACE=com.att.ajsc +SOACLOUD_ENV_CONTEXT=DEV +SOACLOUD_PROTOCOL=http +SOACLOUD_ROUTE_OFFER=DEFAULT + +AFT_LATITUDE=23.4 +AFT_LONGITUDE=33.6 +AFT_ENVIRONMENT=AFTUAT + +#Restlet Component Default Properties +RESTLET_COMPONENT_CONTROLLER_DAEMON=true +RESTLET_COMPONENT_CONTROLLER_SLEEP_TIME_MS=100 +RESTLET_COMPONENT_INBOUND_BUFFER_SIZE=8192 +RESTLET_COMPONENT_MIN_THREADS=1 +RESTLET_COMPONENT_MAX_THREADS=10 +RESTLET_COMPONENT_LOW_THREADS=8 +RESTLET_COMPONENT_MAX_QUEUED=0 +RESTLET_COMPONENT_MAX_CONNECTIONS_PER_HOST=-1 +RESTLET_COMPONENT_MAX_TOTAL_CONNECTIONS=-1 +RESTLET_COMPONENT_OUTBOUND_BUFFER_SIZE=8192 +RESTLET_COMPONENT_PERSISTING_CONNECTIONS=true +RESTLET_COMPONENT_PIPELINING_CONNECTIONS=false +RESTLET_COMPONENT_THREAD_MAX_IDLE_TIME_MS=60000 +RESTLET_COMPONENT_USE_FORWARDED_HEADER=false +RESTLET_COMPONENT_REUSE_ADDRESS=true + +#Externalized jar and properties file location. In CSI environments, there are a few libs that have been externalized to aid +#in CSTEM maintenance of the versions of these libs. The most important to the AJSC is the DME2 lib. Not only is this lib necessary +#for proper registration of your AJSC service on a node, but it is also necessary for running locally as well. Another framework +#used in CSI envs is the CSM framework. These 2 framework libs are shown as "provided" dependencies within the pom.xml. These +#dependencies will be copied into the target/commonLibs folder with the normal "mvn clean package" goal of the AJSC. They will +#then be added to the classpath via AJSC_EXTERNAL_LIB_FOLDERS system property. Any files (mainly property files) that need +#to be on the classpath should be added to the AJSC_EXTERNAL_PROPERTIES_FOLDERS system property. The default scenario when +#testing your AJSC service locally will utilize the target/commonLibs directory for DME2 and CSM related artifacts and 2 +#default csm properties files will be used for local testing with anything CSM knorelated. +#NOTE: we are using maven-replacer-plugin to replace "(doubleUnderscore)basedir(doubleUnderscore)" with ${basedir} within the +#target directory for running locally. Multiple folder locations can be separated by the pipe ("|") character. +#Please, NOTE: for running locally, we are setting this system property in the antBuild/build.xml "runLocal" target and in the +#"runAjsc" profile within the pom.xml. This is to most effectively use maven variables (${basedir}, most specifically. Therefore, +#when running locally, the following 2 properties should be set within the profile(s) themselves. +#Example: target/commonLibs|target/otherLibs +#AJSC_EXTERNAL_LIB_FOLDERS=__basedir__/target/commonLibs +#AJSC_EXTERNAL_PROPERTIES_FOLDERS=__basedir__/ajsc-shared-config/etc +#End of AJSC System Properties + +#Service System Properties. Please, place any Service related System Properties below. + +KEY_STORE_PASSWORD=OBF:1i9a1u2a1unz1lr61wn51wn11lss1unz1u301i6o +KEY_MANAGER_PASSWORD=OBF:1i9a1u2a1unz1lr61wn51wn11lss1unz1u301i6o \ No newline at end of file diff --git a/eclipse-config/eclipse-java-google-style.xml b/eclipse-config/eclipse-java-google-style.xml new file mode 100644 index 0000000..03c2420 --- /dev/null +++ b/eclipse-config/eclipse-java-google-style.xml @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..25f608a --- /dev/null +++ b/pom.xml @@ -0,0 +1,582 @@ + + 4.0.0 + + + ajsc-archetype-parent + com.att.ajsc + 2.0.0 + + org.openecomp.aai + inventory-ui-service + 1.0-SNAPSHOT + + + inventory-ui-service + v1 + 2.0.0 + /appl/${project.artifactId} + + + /appl/${project.artifactId}/${project.version} + ${basedir}/target/swm/package/nix/dist_files${distFilesRoot} + + + aaiadmin + aaiadmin + com.att.csid.lab + + + 9517 + 8000 + + workstation + DEV + google_checks.xml + https://nexus.onap.org + + + + + + org.mockito + mockito-all + 1.10.19 + test + + + org.powermock + powermock-module-junit4 + 1.6.2 + test + + + org.powermock + powermock-api-mockito + 1.6.2 + test + + + org.powermock + powermock-module-javaagent + 1.6.2 + test + + + org.powermock + powermock-module-junit4-rule-agent + 1.6.2 + test + + + + + dom4j + dom4j + 1.6.1 + provided + + + com.att.aft + dme2 + 3.1.200 + provided + + + + org.slf4j + slf4j-api + 1.7.20 + + + + org.openecomp.aai + rest-client + 1.1.0-SNAPSHOT + + + + + org.hamcrest + hamcrest-library + 1.3 + test + + + + + + org.eclipse.persistence + eclipselink + 2.6.2 + + + + com.fasterxml.jackson.core + jackson-core + 2.7.4 + + + + org.json + json + 20131018 + + + + com.fasterxml.jackson.core + jackson-databind + 2.7.4 + + + + + org.openecomp.aai.logging-service + common-logging + 1.0.0 + + + + com.google.code.gson + gson + 2.6.2 + + + + ch.qos.logback + logback-classic + 1.1.7 + + + + ch.qos.logback + logback-core + 1.1.7 + + + + commons-io + commons-io + 2.4 + + + + log4j + log4j + 1.2.17 + + + + org.openecomp.ecompsdkos + epsdk-fw + 1.1.0-SNAPSHOT + + + commons-logging + commons-logging + + + log4j + log4j + + + log4j + apache-log4j-extras + + + org.slf4j + slf4j-log4j12 + + + + + + + + + runAjsc + + initialize + + + + org.codehaus.mojo + exec-maven-plugin + 1.3.2 + + + initialize + + java + + + false + true + java + com.att.ajsc.runner.Runner + + com.att.ajsc + ajsc-runner + + + ${basedir}/ajsc-shared-config/etc + ${basedir}/appconfig-local + + + + ${runAjscHome} + + + + + + AJSC_HOME + ${runAjscHome} + + + CONFIG_HOME + ${basedir}/appconfig-local/ + + + AJSC_CONF_HOME + ${basedir}/bundleconfig-local + + + logback.configurationFile + ${basedir}/ajsc-shared-config/etc/logback.xml + + + AJSC_SHARED_CONFIG + ${basedir}/ajsc-shared-config + + + + AJSC_EXTERNAL_LIB_FOLDERS + ${basedir}/target/commonLibs + + + AJSC_EXTERNAL_PROPERTIES_FOLDERS + ${basedir}/ajsc-shared-config/etc + + + + AJSC_SERVICE_NAMESPACE + ${module.ajsc.namespace.name} + + + AJSC_SERVICE_VERSION + ${module.ajsc.namespace.version} + + + SOACLOUD_SERVICE_VERSION + ${project.version} + + + server.port + ${serverPort} + + + + + + context=/ + port=${serverPort} + sslport=${sslport} + + + + + + java + + + + com.att.ajsc + ajsc-runner + ${ajscRuntimeVersion} + + + + + + + + + + + + + + + org.codehaus.mojo + cobertura-maven-plugin + 2.7 + + true + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.codehaus.mojo + + + properties-maven-plugin + + + [1.0-alpha-2,) + + + + write-project-properties + + + + + + + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.0.0 + + + copy-installed + install + + copy + + + + + org.openecomp.aai + sparky-fe + 1.0.0-SNAPSHOT + war + ${basedir}/target/swm/package/nix/dist_files${distFilesRoot}/extApps/ + aai.war + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-docker-file + package + + copy-resources + + + target + true + + + ${basedir}/src/main/docker + true + + **/* + + + + ${basedir}/src/main/scripts/ + + + + + + + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.3 + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + + + checkstyle + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + + copy-docker-file + package + + copy-resources + + + target + true + + + ${basedir}/src/main/docker + true + + **/* + + + + ${basedir}/src/main/scripts/ + + + + + + + + com.spotify + docker-maven-plugin + 0.4.11 + + true + docker-hub + ${docker.push.registry}/openecomp/${project.artifactId} + ${docker.location} + + latest + + true + + + + + com.mycila + license-maven-plugin + 3.0 + +

LICENSE
+ + src/main/java/** + + + + + + format + + process-sources + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ${nexusproxy} + 176c31dfe190a + ecomp-staging + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.codehaus.mojo + sonar-maven-plugin + 3.2 + + + org.jacoco + jacoco-maven-plugin + 0.7.9 + + ${basedir}/target/coverage-reports/jacoco-unit.exec + ${basedir}/target/coverage-reports/jacoco-unit.exec + + + + prepare-agent + + prepare-agent + + + + jacoco-site + package + + report + + + + + + + + + diff --git a/project-configs/code-tools/sonar-secret.txt b/project-configs/code-tools/sonar-secret.txt new file mode 100644 index 0000000..9036e07 --- /dev/null +++ b/project-configs/code-tools/sonar-secret.txt @@ -0,0 +1 @@ +7TP5jKdtMb+0EtW4Trbbnw== \ No newline at end of file diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/HelloWorldBeans.xml b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/HelloWorldBeans.xml new file mode 100644 index 0000000..b311770 --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/HelloWorldBeans.xml @@ -0,0 +1,8 @@ + + + diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/jaxrsBeans.groovy b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/jaxrsBeans.groovy new file mode 100644 index 0000000..da9b558 --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/jaxrsBeans.groovy @@ -0,0 +1,11 @@ +beans{ + xmlns cxf: "http://camel.apache.org/schema/cxf" + xmlns jaxrs: "http://cxf.apache.org/jaxrs" + xmlns util: "http://www.springframework.org/schema/util" + + echoService(org.openecomp.sparky.JaxrsEchoService) + + util.list(id: 'jaxrsServices') { + ref(bean:'echoService') + } +} \ No newline at end of file diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/docs/README.txt b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/docs/README.txt new file mode 100644 index 0000000..3707179 --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/docs/README.txt @@ -0,0 +1 @@ +Place any docs here that you want to access within the ajsc upon deployment of your service. diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/lib/README.txt b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/lib/README.txt new file mode 100644 index 0000000..639e21b --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/lib/README.txt @@ -0,0 +1 @@ +3rd party JAR's needed by your jars (if any) for a ajsc deployment package go here... \ No newline at end of file diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/props/module.props b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/props/module.props new file mode 100644 index 0000000..17ebc08 --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/props/module.props @@ -0,0 +1 @@ +EXAMPLE.PROPERTY=EXAMLE_VALUE \ No newline at end of file diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloServlet.route b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloServlet.route new file mode 100644 index 0000000..5ede9c1 --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloServlet.route @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloWorld.route b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloWorld.route new file mode 100644 index 0000000..bc3e178 --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloWorld.route @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/jaxrsExample.route b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/jaxrsExample.route new file mode 100644 index 0000000..25c1977 --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/jaxrsExample.route @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/serverStaticContent.route b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/serverStaticContent.route new file mode 100644 index 0000000..bf221c6 --- /dev/null +++ b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/serverStaticContent.route @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/main/assemble/ajsc_module_assembly.xml b/src/main/assemble/ajsc_module_assembly.xml new file mode 100644 index 0000000..359f792 --- /dev/null +++ b/src/main/assemble/ajsc_module_assembly.xml @@ -0,0 +1,69 @@ + + + ${version} + false + + zip + + + + ${project.basedir}/target/versioned-ajsc/routes/ + ${module.ajsc.namespace.name}/${module.ajsc.namespace.version}/routes/ + + *.route + + + + + + ${project.basedir}/target/versioned-ajsc/docs/ + ${module.ajsc.namespace.name}/${module.ajsc.namespace.version}/docs/ + + *.* + + + + + + + ${project.basedir}/target/versioned-ajsc/lib/ + ${module.ajsc.namespace.name}/${module.ajsc.namespace.version}/lib/ + + *.jar + + + + + ${project.basedir}/target/versioned-ajsc/extJars/ + ${module.ajsc.namespace.name}/${module.ajsc.namespace.version}/extJars/ + + *.jar + + + + + + ${project.basedir}/target/ + ${module.ajsc.namespace.name}/${module.ajsc.namespace.version}/lib/ + + *.jar + + + + + ${project.basedir}/target/versioned-ajsc/conf/ + ${module.ajsc.namespace.name}/${module.ajsc.namespace.version}/conf/ + + *.* + + + + + + + diff --git a/src/main/assemble/ajsc_props_assembly.xml b/src/main/assemble/ajsc_props_assembly.xml new file mode 100644 index 0000000..6ee4093 --- /dev/null +++ b/src/main/assemble/ajsc_props_assembly.xml @@ -0,0 +1,26 @@ + + + ${version}_properties + false + + zip + + + + ${project.basedir}/target/versioned-ajsc/props + ${module.ajsc.namespace.name}/${module.ajsc.namespace.version}/props/ + + *.props + + + + + + + + diff --git a/src/main/assemble/ajsc_runtime_assembly.xml b/src/main/assemble/ajsc_runtime_assembly.xml new file mode 100644 index 0000000..c86d265 --- /dev/null +++ b/src/main/assemble/ajsc_runtime_assembly.xml @@ -0,0 +1,47 @@ + + + runtimeEnvironment + false + + zip + + + + ${project.basedir}/target/versioned-runtime/context/ + runtime/context/ + + *.context + + + + ${project.basedir}/target/versioned-runtime/serviceProperties/ + runtime/serviceProperties/ + + *.props + + + ${project.basedir}/target/versioned-runtime/shiroRole + runtime/shiroRole/ + + *.json + + + ${project.basedir}/target/versioned-runtime/shiroUser + runtime/shiroUser/ + + *.json + + + ${project.basedir}/target/versioned-runtime/shiroUserRole + runtime/shiroUserRole + + *.json + + + + \ No newline at end of file diff --git a/src/main/config/aaiEntityNodeDescriptors.json b/src/main/config/aaiEntityNodeDescriptors.json new file mode 100644 index 0000000..bf95f28 --- /dev/null +++ b/src/main/config/aaiEntityNodeDescriptors.json @@ -0,0 +1,188 @@ +{ + "generalNodeClass": { + "class": "aai-entity-node general-node", + "visualElements": [{ + "type": "circle", + "class": "outer", + "svgAttributes": { + "r": "16" + } + }, + { + "type": "circle", + "class": "inner", + "svgAttributes": { + "r": "10" + } + }, + { + "type": "text", + "class": "id-type-label", + "displayKey": "itemType", + "shapeAttributes": { + "offset": { + "x": "0", + "y": "33" + } + } + }, + { + "type": "text", + "class": "id-value-label", + "displayKey": "itemNameValue", + "shapeAttributes": { + "offset": { + "x": "0", + "y": "48" + } + } + }] + }, + "searchedNodeClass": { + "class": "aai-entity-node search-node", + "visualElements": [{ + "type": "circle", + "class": "outer", + "svgAttributes": { + "r": "16" + } + }, + { + "type": "circle", + "class": "inner", + "svgAttributes": { + "r": "10" + } + }, + { + "type": "text", + "class": "id-type-label", + "displayKey": "itemType", + "shapeAttributes": { + "offset": { + "x": "0", + "y": "33" + } + } + }, + { + "type": "text", + "class": "id-value-label", + "displayKey": "itemNameValue", + "shapeAttributes": { + "offset": { + "x": "0", + "y": "48" + } + } + }] + }, + "selectedSearchedNodeClass": { + "class": "aai-entity-node selected-search-node", + "visualElements": [{ + "type": "circle", + "class": "outer", + "svgAttributes": { + "r": "31" + } + }, + { + "type": "circle", + "class": "inner", + "svgAttributes": { + "r": "20" + } + }, + { + "type": "text", + "class": "id-type-label", + "displayKey": "itemType", + "shapeAttributes": { + "offset": { + "x": "0", + "y": "48" + } + } + }, + { + "type": "text", + "class": "id-value-label", + "displayKey": "itemNameValue", + "shapeAttributes": { + "offset": { + "x": "0", + "y": "63" + } + } + }, + { + "type": "button", + "name": "icon_ellipses", + "class": "node-button", + "shapeAttributes": { + "offset": { + "x": "33", + "y": "-35" + } + }, + "svgAttributes": { + "className": "node-button", + "r": "10" + } + }] + }, + "selectedNodeClass": { + "class": "aai-entity-node selected-node", + "visualElements": [{ + "type": "circle", + "class": "outer", + "svgAttributes": { + "r": "31" + } + }, + { + "type": "circle", + "class": "inner", + "svgAttributes": { + "r": "20" + } + }, + { + "type": "text", + "class": "id-type-label", + "displayKey": "itemType", + "shapeAttributes": { + "offset": { + "x": "0", + "y": "48" + } + } + }, + { + "type": "text", + "class": "id-value-label", + "displayKey": "itemNameValue", + "shapeAttributes": { + "offset": { + "x": "0", + "y": "63" + } + } + }, + { + "type": "button", + "name": "icon_ellipses", + "class": "node-button", + "shapeAttributes": { + "offset": { + "x": "33", + "y": "-35" + } + }, + "svgAttributes": { + "className": "node-button", + "r": "10" + } + }] + } +} \ No newline at end of file diff --git a/src/main/config/ajsc-chef.jks b/src/main/config/ajsc-chef.jks new file mode 100644 index 0000000000000000000000000000000000000000..aeca7700182fb1824b47fca285204122a879ff85 GIT binary patch literal 5256 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 literal 0 HcmV?d00001 diff --git a/src/main/config/ajsc-jetty.xml b/src/main/config/ajsc-jetty.xml new file mode 100644 index 0000000..1e026cd --- /dev/null +++ b/src/main/config/ajsc-jetty.xml @@ -0,0 +1,128 @@ + + + + + + + + true + + + /etc/runner-web.xml + /etc/ajsc-override-web.xml + true + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + /extApps + 10 + true + + + + + + + + + + + + + + + + + + + + + /auth/inventory-ui-keystore + + + + + + + + + + + false + false + + + + + + + + + + + + + http/1.1 + + + + + + + + + + + + + + + + + + + + + + 30000 + + + + + + + + + false + + + diff --git a/src/main/config/ajsc-override-web.xml b/src/main/config/ajsc-override-web.xml new file mode 100644 index 0000000..59071d6 --- /dev/null +++ b/src/main/config/ajsc-override-web.xml @@ -0,0 +1,78 @@ + + + + + + ElasticSearchSynchronizerFilter + /nothingShouldBeSentHere/* + + + + OxmModelLoaderFilter + /nothingShouldBeSentHereEither/* + + + + PortalRestAPIProxy + /api/v2/* + + + + VisualizationServlet + /visualization/* + + + + GeoVisualizationServlet + /visualization/geovisualization/* + + + + EntityCountHistoryServlet + /visualization/entityCountHistory/* + + + + springSecurityFilterChain + /* + + + + ManagementServlet + /mgmt + + + + RestletServlet + /rest/* + + + + CamelServlet + /services/* + + + + SearchServlet + /elasticSearchQuery/* + /search/* + + + + jsp + *.jsp + *.jspf + *.jspx + *.xsp + *.JSP + *.JSPF + *.JSPX + *.XSP + + + default + /* + + \ No newline at end of file diff --git a/src/main/config/ajscJetty.jks b/src/main/config/ajscJetty.jks new file mode 100644 index 0000000000000000000000000000000000000000..48cdbff4a8344b9bfa164b340c2fd3b4f7020c41 GIT binary patch literal 3736 zcmcIm`)`xy75+Zo;@EM*>Rd47o-~jPU}JKFn8b+*gxq6897y6^aPAj^NkW*tv>V%4 zyRF*Rc3M>xRjX8OrCU)iVyiZ!iWb^R6&;0+l1^fqRQ0F+3ws`)k1rMKualnhoO9my z^Y^~zyyrcizq$6eZyrKX6az#}iZwQUHo7o6AH_gG9_fKn6tm)nHbLC!7Kg3UqMFnS z!}nCxtZ~`P?CJNqmyXXx;aI2o792JhsgV548_!HZo2o&;_RpUv+ae~{GJf#q z!MdVh8R!xP4nfL{AQh4e5$xF~Iy}(66IPBsk$|-o5>E$Jn_VvLL1hkp0YpcwRi{JyRN4$I;F!}LQV-;fNJSJM_^pU+{Hynlzw2I@?=fy~jM<2ZK zYMg0YqvWj1I5XlGbA?L-Ou3FzW4(#FkNygOh_9TL3HA6+f68Y+hvRAh<;y5IQ>>7;EBd2S36TyJ}*wf9SB8?IRywf@kmB1)ixQLB5S$xjotN$7L)J zY8rA<+=HTYjwS9ruRHay3dFnnV`w8U64wb&uw;1R4}SN?zux3CtbXF@=fJ1(vcPqa z_;C=W8**~&SZ(DbJ;vQh0!~1fkCxM*20JtK!~~P zR{D;@%HsVhs$l7rP4gheg3(ed51PvZU835#YjL;8Y6yLur?!wkF7(58= z{_wy9!|lv~Yb65KL5G*4!sl1`j#<{vynA=Dxxo}@XUgDs8^-y1;PZ>^>`2=oGpxsM z2GiPBzwF|X@l=7rGcn#Ko?H3xV&1d1yKjJX)MhedfLR54xlO^@Vl$I}M!YN@Hoy`F z&O^9;mi{QqOxq>PdI;iy2;O@(8P1lLn{PU7EQ>&diCc!Szq%nhjAEU?{x0i*DFGG_ zBwui@T^nL}jqFcA#luwbKD$dcvm2KAIjA6Hh*Wq+d!vg*dnOIU1+`nh1Ev-nEWshD zi5~U)`}$zaSO!*ZKO2KJEVJOyHqd7mbf1X^_-gG7!Cg->P+uJziVp&?-E4Lh<7T!7 zTE}J!G3iMS#Mh=Cm#6bZ33u}nENn))zae|$7Y0^_fWIzbL1=Zmv8VAl3l40L&%-|CqzOtKIdAVhpm_jooBJElq=JFqjV2 z@v^!dHcKjlJput7HdVFb4^r4;cc)s6$4nNd;aKpYV|5x1`#oFmw+S;T&J=&B!`~J1 zd(!wC3^{C0qdye#IBae^eY?}t66^`+zR_v&9}Tv+^K%?F&px-;Q|GDn)>rR6A%2ag zM)UvA>myLL_`5>KUqGIVnvwjAs5)#0da42jPVN}&GM8>V0n=eCVQmHPzHDbtnq_eS zY;}k4yai^<2~zGq11`A3Is1@?xi+=$Na&vRSReQzet(b+d zr~LDZ!Kf+>uE<^B&O>qLrJJ%%nLH*zBM;=?KfN03=Z$6e5*Ty8t59%9l7*rw+jfN9 zmUJH;wrc^yteUGYfy#ueLwXT!ls`fcz3j_XfRey^6e+2yW?*8^z2Ar=h zvnn&#vUrn^L71LVrlOQ*|PW9frj&V8Ix8l3)CaF^zwDO~%1DUQGTe;RWhYSm*+l zN8zI>VTe_hLe2roM}OkE1N=7kiK%mgZoy#NyZwrcFj+i==)HHg$Ow3=w%0FS8TAIV d$UppJT1E%vmLWa}Z$I0*0!fDe1J|Dh{|_E2uSEa= literal 0 HcmV?d00001 diff --git a/src/main/config/autoSuggestMappings.json b/src/main/config/autoSuggestMappings.json new file mode 100644 index 0000000..7857617 --- /dev/null +++ b/src/main/config/autoSuggestMappings.json @@ -0,0 +1,10 @@ +{ + "properties" : { + "entity_suggest" : { + "type" : "completion", + "payloads" : true, + "analyzer" : "custom_analyzer", + "preserve_position_increments": false + } + } +} \ No newline at end of file diff --git a/src/main/config/autoSuggestSettings.json b/src/main/config/autoSuggestSettings.json new file mode 100644 index 0000000..4525be1 --- /dev/null +++ b/src/main/config/autoSuggestSettings.json @@ -0,0 +1,21 @@ +{ + "analysis": { + "filter": { + "eng_stop": { + "type": "stop", + "stopwords": "_english_" + } + }, + "analyzer": { + "custom_analyzer": { + "type": "custom", + "tokenizer": "standard", + "filter": [ + "lowercase", + "asciifolding", + "eng_stop" + ] + } + } + } + } \ No newline at end of file diff --git a/src/main/config/cadi.properties b/src/main/config/cadi.properties new file mode 100644 index 0000000..83a5ce0 --- /dev/null +++ b/src/main/config/cadi.properties @@ -0,0 +1,36 @@ +#This properties file is used for defining AAF properties related to the CADI framework. This file is used for running AAF framework + +#In order to test functionality of cadi-ajsc-plugin locally cross domain cookie. Cadi "should" find your hostname for you. +#However, we have seen some situations where this fails. A Local testing +#modification can include modifying your hosts file so that you can use "mywebserver.att.com" for your localhost in order +#to test/verify GLO functionality locally. If you are on a Windows machine, you will already have a machine name associated with +#it that will utilize an AT&T domain such as "sbc.com". You may need to add your domain to this as a comma separated list depending +#upon your particular machine domain. This property is commented out as cadi SHOULD find your machine name. With version 1.2.1 of cadi, +#it appears to resolve Mac machine names as well, now. But, this can be somewhat inconsistent depending on your specific working envrironment. +hostname=mywebserver.att.com + +#Setting csp_domain to PROD will allow for testing using your attuid and password through GLO. +csp_domain=PROD +csp_devl_localhost=true + +basic_realm=csp.att.com +#basic_realm=aaf.att.com +basic_warn=TRUE + +cadi_loglevel=WARN +cadi_keyfile=target/swm/package/nix/dist_files/appl/inventory-ui-service/etc/keyfile + +# Configure AAF +#These are dummy values add appropriate values required +aaf_url=url + +#AJSC - MECHID +#These are dummy values add appropriate values required +aaf_id=dummyid@ajsc.att.com +aaf_password=enc:277edqJCjT0RlUI3BtbDQa-3Ha-CQGd +aaf_timeout=5000 +aaf_clean_interval=30000 +aaf_user_expires=5000 +aaf_high_count=1000 + + diff --git a/src/main/config/csp-cookie-filter.properties b/src/main/config/csp-cookie-filter.properties new file mode 100644 index 0000000..e12109a --- /dev/null +++ b/src/main/config/csp-cookie-filter.properties @@ -0,0 +1,18 @@ +# AT&T Global login page. This is the redirect URL +# Production login page: +# https://www.e-access.att.com/empsvcs/hrpinmgt/pagLogin/ +# +# Test login page: +# https://webtest.csp.att.com/empsvcs/hrpinmgt/pagLogin/ +global.login.url=https://www.e-access.att.com/empsvcs/hrpinmgt/pagLogin/ + +# valid domains for open redirect +redirect-domain=att.com,sbc.com,bls.com,cingular.net + +# MOTS ID of the application +application.id=24153 + +# Required by esGateKeeper. Valid values are: +# DEVL - used during development +# PROD - used in production +gatekeeper.environment=PROD \ No newline at end of file diff --git a/src/main/config/dynamicMappings.json b/src/main/config/dynamicMappings.json new file mode 100644 index 0000000..09a00ac --- /dev/null +++ b/src/main/config/dynamicMappings.json @@ -0,0 +1,14 @@ +{ + "dynamic_templates": [ + { + "strings": { + "match_mapping_type": "string", + "match": "*", + "mapping": { + "type": "string", + "index": "not_analyzed" + } + } + } + ] +} \ No newline at end of file diff --git a/src/main/config/entityCountHistoryMappings.json b/src/main/config/entityCountHistoryMappings.json new file mode 100644 index 0000000..84e3aec --- /dev/null +++ b/src/main/config/entityCountHistoryMappings.json @@ -0,0 +1,16 @@ +{ + "properties": { + "count": { + "type": "long" + }, + "entityType": { + "type": "string", + "index": "not_analyzed" + }, + "timestamp": { + "type": "date", + "format": "MMM d y HH:m:s||dd-MM-yyyy HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSSZZ||MM/dd/yyyy||yyyyMMdd'T'HHmmssZ" + } + } +} + diff --git a/src/main/config/es_mappings.json b/src/main/config/es_mappings.json new file mode 100644 index 0000000..216e3d9 --- /dev/null +++ b/src/main/config/es_mappings.json @@ -0,0 +1,32 @@ +{ + "properties": { + "entityType": { + "type": "string", + "analyzer": "ngram_analyzer", + "search_analyzer": "ngram_analyzer" + }, + "entityPrimaryKeyValue": { + "type": "string", + "index": "not_analyzed" + }, + "searchTagIDs": { + "type": "string" + }, + "searchTags": { + "type": "string", + "analyzer": "ngram_analyzer" + }, + "crossEntityReferenceValues": { + "type": "string", + "analyzer": "ngram_analyzer" + }, + "link": { + "type": "string", + "index": "not_analyzed" + }, + "lastmodTimestamp": { + "type": "date", + "format": "MMM d y HH:m:s||dd-MM-yyyy HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSSZZ||yyyy-MM-dd HH:mm:ss||MM/dd/yyyy||yyyyMMdd'T'HHmmssZ" + } + } +} \ No newline at end of file diff --git a/src/main/config/es_settings.json b/src/main/config/es_settings.json new file mode 100644 index 0000000..21a357c --- /dev/null +++ b/src/main/config/es_settings.json @@ -0,0 +1,36 @@ +{ + "analysis": { + "filter": { + "ngram_filter": { + "type": "nGram", + "min_gram": 1, + "max_gram": 50, + "token_chars": [ + "letter", + "digit", + "punctuation", + "symbol" + ] + } + }, + "analyzer": { + "ngram_analyzer": { + "type": "custom", + "tokenizer": "whitespace", + "filter": [ + "lowercase", + "asciifolding", + "ngram_filter" + ] + }, + "whitespace_analyzer": { + "type": "custom", + "tokenizer": "whitespace", + "filter": [ + "lowercase", + "asciifolding" + ] + } + } + } +} \ No newline at end of file diff --git a/src/main/config/jul-redirect.properties b/src/main/config/jul-redirect.properties new file mode 100644 index 0000000..8b6624d --- /dev/null +++ b/src/main/config/jul-redirect.properties @@ -0,0 +1,13 @@ + +# Bridge JUL->slf4j Logging Configuration File +# +# This file bridges the JUL logging infrastructure into +# SLF4J so JUL logs go to logback implementation provided +# in this project. SLF4J also captures log4j and has +# other framework options as well providing a common +# logging infrastructure for capturing all logs from different +# libraries using different frameworks in one place. + +# Global properties +handlers=org.slf4j.bridge.SLF4JBridgeHandler +.level= ALL diff --git a/src/main/config/keyfile b/src/main/config/keyfile new file mode 100644 index 0000000..882e86a --- /dev/null +++ b/src/main/config/keyfile @@ -0,0 +1,27 @@ +ZuIwp0TkyVPDeX1Up-8JtkMWvjsCpoiu1_VKeWrtrvxunvAke8_tiFyHPPyb2nkhepFYj6tXzpfS +rGz5XF_TH9NbsKaP8u0HV5clz2WriYQRvHS85vjY7hXxkpFuLb7zkLAPqTyIDpj7FiW61NzsRUAq +TM8jH16jr7mBNnb56w24mNGOwznMPcIZKcjgZU1ekaPDFpWyhQElU7Y0q_94P_Gkk45r66Hj22sU +OiOaaftmudZlswLw8-8Zaakqf2yW9HjMVfuYCwSodBHCW5rdB3Ctb5W36rnD_AQco3Ky2PgPmqvk +QkJYuUHpbuDqVHqLOajlKSIGMTIqAIBg51fRaaONtD-Q5xzY8E5wO1YWTLKcP5tsNvUpzM8Wu3NS +ynpGpUcvlTqWWsGzTbzOyamyKkdNdx97sSqjM25Zh1-ps48h6cddGYWpab7SUvqRCS11QBUyLTry +2iwTEHMhHRIbo7PO99ALQfuq9gI1zKGfurJdvLBeBaFs5SCF0AiCZ3WcDO8Rv3HpxVZ2_ShbDxb0 +eMoO6SotXu51fj8Y3-WqsfZziQyEsHyqpg5uQ6yUtz01h5YHLEoVuotF1U4agmQR6kEkYk-wNOiZ +v-8gaA9gtbLoAdKhuKFxQgQLNMf6GzVzZNujbmDzLoZAP_mXAv29aBPaf64Ugzv-Oa5GZdBgD-Xd +_pahML-ionw99r0TnkpShYmDqMKhMdjaP3m87WIAZkIB-L-VTyKcEsJ4340VSzCOsv3waiM0S89u +4cMcG5y-PLY8IoipIlLUPTWD3SjcQ9DV1Dt3T5KjdWLsj48D3W4K4e9PB8yxs0gtUjgVUR2_xEir +G5eDO9Ac1eHFWGDFFP0SgG-TbHJUKlvy9mwLzmU0fC3xPjhqmIr-v0HxF7HN-tmb1LHDorno8tSN +u7kUGcKSchIiFfvkd066crUb2mH7PnXTaWmAjyVj9VsBExFUYEdpHMAV4sAP9-RxZGDRt46UhrDK +QZvvNhBVyOEjHPHWI4vl1r1v8HNH1_2jZu5DVJWyHWR56aCo1lhFH9_X6UAHUHbnXViDONZOVXlT +9-WD0tk2zJGuwrhdZDAnPnAmjfwbwbpnr5Hmex1i1JiD7WVyP1kbfoej2TmdiYbxr9oBYaGQ29JI +aHod7MQCLtvL1z5XgnDPLZ4y3_9SbqHKYbNa8UgZkTLF5EacGThYVFDLA9cbafHDtR1kMGE3vv4D +EJ-0pAYTOGmKlVI7DwNyKsY9JTyudrxTqhOxi9jgcJNWiUaNe9yhL8Pyc2YBqUTTYhh_a2d1rvkZ +0Gh1crviVxqBrIkRKaMRXZ4f1vDLz-3NvG_vwPOo8WRFo5nGmSdTw7CjBaigJ_cYCfDhoP11pEnw +cndsZNcHs-v05LlxeIIMDD_f5Bvz-il_DLA4eK2HqgLdxh8ziSDl2azk14MJY4amzz6reEXUuKLV +RsZGf_jbDGKhE2HuDQ5ovoLOi4OqE1oRuqh-dGxitrYouP2SN1l_1tCEMRth86FMV-6AQtZsvdUo +y9MtQ7e35atjA8nHtgADlDTmJBKQiUHUsOZ77p1qp17HAFMovUkc739opfEYnKUn6Itpw5Ipm_Is +ra6chJUfMpOFof5rb5OjqFAN27c_-mPo1lQU3ndYlKGh_n5V8ufX6v2Yri8WzOPf6hjVYotkmoMP +NPAICDCB8W5ddBjsopzLVVEtaXDu9Qj6-zf77hT4iQ7rBd2Ner8iLqN3Kis0dvkNM3_uH8onau1G +Y_YYw7PPSZyd2S_7Dd6G-IG4ayO6e5DD6oUwwekyiQI_3rTXNa_wldGxqW9u818010ekE4Qdlfcj +beIn7fAeaOjReZ87hRgWyMs-EgTVHw8RL3yI_O6VvRTVRONRF1Y4C_-IYa8z-bfrwXx3BBd9TTgb +EnS9wVOyC2OgUN6BhPLGLhxzkJ05nEjizXEc9t5EPYoSRwesajGGrrG_0-qWbuU5hKLPLkyeJLHb +5HXOTVsrUR59Vov2M3_EswkxcImblox3k3VS2yihZMGyfqLzZIUXgd8ufkevKKU6DxwacGTb \ No newline at end of file diff --git a/src/main/config/runner-web.xml b/src/main/config/runner-web.xml new file mode 100644 index 0000000..abfdf74 --- /dev/null +++ b/src/main/config/runner-web.xml @@ -0,0 +1,124 @@ + + + + + + contextConfigLocation + /WEB-INF/spring-servlet.xml, + classpath:applicationContext.xml + + + + + spring.profiles.default + nooauth + + + + org.springframework.web.context.ContextLoaderListener + + + + ManagementServlet + ajsc.ManagementServlet + + + + VisualizationServlet + org.openecomp.sparky.viewandinspect.servlet.VisualizationServlet + + + + GeoVisualizationServlet + org.openecomp.sparky.inventory.servlet.GeoVisualizationServlet + + + + EntityCountHistoryServlet + org.openecomp.sparky.inventory.servlet.EntityCountHistoryServlet + + + + + + ElasticSearchSynchronizerFilter + org.openecomp.sparky.synchronizer.filter.ElasticSearchSynchronizerFilter + + + + OxmModelLoaderFilter + org.openecomp.sparky.config.oxm.OxmModelLoaderFilter + + + + WriteableRequestFilter + com.att.ajsc.csi.writeablerequestfilter.WriteableRequestFilter + + + + RestletServlet + ajsc.restlet.RestletSpringServlet + + org.restlet.component + restletComponent + + + + + CamelServlet + ajsc.servlet.AjscCamelServlet + + + + SearchServlet + org.openecomp.sparky.viewandinspect.servlet.SearchServlet + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + + spring + org.springframework.web.servlet.DispatcherServlet + 1 + + + + PortalRestAPIProxy + org.openecomp.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy + + + + + + + + + jsp + org.apache.jasper.servlet.JspServlet + + + + + + + default + org.eclipse.jetty.servlet.DefaultServlet + + dirAllowed + true + + + + + + + + diff --git a/src/main/docker/Dockerfile b/src/main/docker/Dockerfile new file mode 100644 index 0000000..6ca51ae --- /dev/null +++ b/src/main/docker/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:14.04 + +ARG MICRO_HOME=/opt/app/sparky +ARG BIN_HOME=$MICRO_HOME/bin + +RUN apt-get update + +# Install and setup java8 +RUN apt-get update && apt-get install -y software-properties-common +## sudo -E is required to preserve the environment. If you remove that line, it will most like freeze at this step +RUN sudo -E add-apt-repository ppa:openjdk-r/ppa && apt-get update && apt-get install -y openjdk-8-jdk +## Setup JAVA_HOME, this is useful for docker commandline +ENV JAVA_HOME usr/lib/jvm/java-8-openjdk-amd64 +RUN export JAVA_HOME + +# Build up the deployment folder structure +RUN mkdir -p $MICRO_HOME +copy swm/package/nix/dist_files/appl/inventory-ui-service/1.0-SNAPSHOT/ $MICRO_HOME/ +RUN ls -la $MICRO_HOME/ +RUN mkdir -p $BIN_HOME +COPY *.sh $BIN_HOME/ +RUN chmod 755 $BIN_HOME/* +RUN ln -s /logs $MICRO_HOME/logs + +EXPOSE 8000 8000 + +CMD tail -F -n0 /etc/hosts +CMD /opt/app/sparky/bin/start.sh +#CMD top \ No newline at end of file diff --git a/src/main/java/org/openecomp/sparky/HelloWorld.java b/src/main/java/org/openecomp/sparky/HelloWorld.java new file mode 100644 index 0000000..6719307 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/HelloWorld.java @@ -0,0 +1,49 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky; + +import org.apache.camel.Exchange; + +/** + * The Class HelloWorld. + */ +public class HelloWorld { + + /** + * Instantiates a new hello world. + */ + public HelloWorld() {} + + /** + * Speak. + * + * @param exc the exc + */ + public final void speak(Exchange exc) { + exc.setOut(exc.getIn()); + exc.getOut().setBody("Hello World!"); + } +} diff --git a/src/main/java/org/openecomp/sparky/JaxrsEchoService.java b/src/main/java/org/openecomp/sparky/JaxrsEchoService.java new file mode 100644 index 0000000..ff70fbc --- /dev/null +++ b/src/main/java/org/openecomp/sparky/JaxrsEchoService.java @@ -0,0 +1,83 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky; + +import com.att.ajsc.beans.PropertiesMapBean; +import com.att.ajsc.filemonitor.AJSCPropertiesMap; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + + +/** + * The Class JaxrsEchoService. + */ +@Path("/jaxrs-services") +public class JaxrsEchoService { + + /** + * Ping. + * + * @param input the input + * @return the string + */ + @GET + @Path("/echo/{input}") + @Produces("text/plain") + public String ping(@PathParam("input") String input) { + return "Hello"; + } + + /** + * Gets the property. + * + * @param fileName the file name + * @param input the input + * @return the property + */ + @GET + @Path("/property/{fileName}/{input:.*}") + @Produces("text/plain") + public String getProperty(@PathParam("fileName") String fileName, + @PathParam("input") String input) { + String val = null; + try { + val = AJSCPropertiesMap.getProperty(fileName, input); + if (val == null || val.isEmpty() || val.length() < 1) { + val = PropertiesMapBean.getProperty(fileName, input); + } + } catch (Exception ex) { + System.out.println("*** Error retrieving property " + input + ": " + ex); + } + if (val == null) { + return "Property is not available"; + } + return "Property value is, " + val + "."; + } + +} diff --git a/src/main/java/org/openecomp/sparky/JaxrsUserService.java b/src/main/java/org/openecomp/sparky/JaxrsUserService.java new file mode 100644 index 0000000..bf9f7b6 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/JaxrsUserService.java @@ -0,0 +1,64 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky; + +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +/** + * The Class JaxrsUserService. + */ +@Path("/user") +public class JaxrsUserService { + + private static final Map userIdToNameMap; + + static { + userIdToNameMap = new HashMap(); + userIdToNameMap.put("dw113c", "Doug Wait"); + userIdToNameMap.put("so401q", "Stuart O'Day"); + } + + /** + * Lookup user. + * + * @param userId the user id + * @return the string + */ + @GET + @Path("/{userId}") + @Produces("text/plain") + public String lookupUser(@PathParam("userId") String userId) { + String name = userIdToNameMap.get(userId); + return name != null ? name : "unknown id"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/analytics/AbstractStatistics.java b/src/main/java/org/openecomp/sparky/analytics/AbstractStatistics.java new file mode 100644 index 0000000..e599165 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/analytics/AbstractStatistics.java @@ -0,0 +1,180 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.analytics; + +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The Class AbstractStatistics. + */ +public class AbstractStatistics implements ComponentStatistics { + + private HashMap namedCounters; + private HashMap namedHistograms; + + /** + * Instantiates a new abstract statistics. + */ + protected AbstractStatistics() { + namedCounters = new HashMap(); + namedHistograms = new HashMap(); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.analytics.ComponentStatistics#addCounter(java.lang.String) + */ + /* + * sync-lock the creation of counters during initialization, but run time should not use lock + * synchronization, only thread safe types + * + * @see com.att.ecomp.uicommon.resolver.stat.ComponentStatistics#addCounter(java.lang.String) + */ + @Override + public synchronized void addCounter(String key) { + + AtomicInteger counter = namedCounters.get(key); + + if (counter == null) { + counter = new AtomicInteger(0); + namedCounters.put(key, counter); + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.analytics.ComponentStatistics#pegCounter(java.lang.String) + */ + @Override + public void pegCounter(String key) { + + AtomicInteger counter = namedCounters.get(key); + + if (counter != null) { + counter.incrementAndGet(); + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.analytics.ComponentStatistics#incrementCounter(java.lang.String, int) + */ + @Override + public void incrementCounter(String key, int value) { + + AtomicInteger counter = namedCounters.get(key); + + if (counter != null) { + counter.addAndGet(value); + } + + } + + + /* (non-Javadoc) + * @see org.openecomp.sparky.analytics.ComponentStatistics#addHistogram(java.lang.String, java.lang.String, long, int, int) + */ + @Override + public synchronized void addHistogram(String key, String histName, long maxYValue, int numBins, + int numDecimalPoints) { + HistogramSampler histSampler = namedHistograms.get(key); + + if (histSampler == null) { + histSampler = new HistogramSampler(histName, maxYValue, numBins, numDecimalPoints); + namedHistograms.put(key, histSampler); + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.analytics.ComponentStatistics#updateHistogram(java.lang.String, long) + */ + @Override + public void updateHistogram(String key, long value) { + HistogramSampler histSampler = namedHistograms.get(key); + + if (histSampler != null) { + histSampler.track(value); + } + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.analytics.ComponentStatistics#reset() + */ + @Override + public void reset() { + + for (HistogramSampler h : namedHistograms.values()) { + h.clear(); + } + + for (AtomicInteger c : namedCounters.values()) { + c.set(0); + } + + } + + /** + * Gets the counter value. + * + * @param key the key + * @return the counter value + */ + protected int getCounterValue(String key) { + + AtomicInteger counter = namedCounters.get(key); + + if (counter == null) { + return -1; + } + + return counter.get(); + + } + + /** + * Gets the histogram stats. + * + * @param key the key + * @param verboseEnabled the verbose enabled + * @param indentPadding the indent padding + * @return the histogram stats + */ + protected String getHistogramStats(String key, boolean verboseEnabled, String indentPadding) { + + HistogramSampler histSampler = namedHistograms.get(key); + + if (histSampler == null) { + return null; + } + + return histSampler.getStats(verboseEnabled, indentPadding); + + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/analytics/AveragingRingBuffer.java b/src/main/java/org/openecomp/sparky/analytics/AveragingRingBuffer.java new file mode 100644 index 0000000..18f5dcf --- /dev/null +++ b/src/main/java/org/openecomp/sparky/analytics/AveragingRingBuffer.java @@ -0,0 +1,122 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.analytics; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * TODO: Fill in description. + * + * @author davea + */ +public class AveragingRingBuffer { + + private int numElements; + + private long[] data; + + private AtomicInteger index; + + private long average; + + private boolean initialAverageCalculated; + + /** + * Instantiates a new averaging ring buffer. + * + * @param size the size + */ + public AveragingRingBuffer(int size) { + + if (size == 0) { + throw new IllegalArgumentException("Size must be greater than zero"); + } + + this.initialAverageCalculated = false; + this.numElements = size; + this.data = new long[this.numElements]; + this.index = new AtomicInteger(-1); + } + + /** + * Calculate average. + * + * @param maxArrayIndex the max array index + */ + private void calculateAverage(int maxArrayIndex) { + + long sum = 0; + + for (int i = 0; i <= maxArrayIndex; i++) { + sum += data[i]; + } + + average = (sum / (maxArrayIndex + 1)); + + } + + public long getAvg() { + + if (!initialAverageCalculated) { + /* + * until the index rolls once we will calculate the average from the data that has been added + * to the array, not including the zero elements + */ + if (index.get() < 0) { + calculateAverage(0); + } else { + calculateAverage(index.get()); + } + + } + + return average; + } + + /** + * Adds the sample. + * + * @param value the value + */ + public synchronized void addSample(long value) { + + index.incrementAndGet(); + + data[index.get()] = value; + + if (index.get() == (numElements - 1)) { + calculateAverage(numElements - 1); + + if (!initialAverageCalculated) { + initialAverageCalculated = true; + } + + index.set(-1); + } + + } + +} diff --git a/src/main/java/org/openecomp/sparky/analytics/ComponentStatistics.java b/src/main/java/org/openecomp/sparky/analytics/ComponentStatistics.java new file mode 100644 index 0000000..285661f --- /dev/null +++ b/src/main/java/org/openecomp/sparky/analytics/ComponentStatistics.java @@ -0,0 +1,81 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.analytics; + + +/** + * The Interface ComponentStatistics. + */ +public interface ComponentStatistics { + + /** + * Adds the counter. + * + * @param key the key + */ + public void addCounter(String key); + + /** + * Peg counter. + * + * @param key the key + */ + public void pegCounter(String key); + + /** + * Increment counter. + * + * @param key the key + * @param value the value + */ + public void incrementCounter(String key, int value); + + /** + * Adds the histogram. + * + * @param key the key + * @param name the name + * @param maxYValue the max Y value + * @param numBins the num bins + * @param numDecimalPoints the num decimal points + */ + public void addHistogram(String key, String name, long maxYValue, int numBins, + int numDecimalPoints); + + /** + * Update histogram. + * + * @param key the key + * @param value the value + */ + public void updateHistogram(String key, long value); + + /** + * Reset. + */ + public void reset(); + +} diff --git a/src/main/java/org/openecomp/sparky/analytics/HistogramSampler.java b/src/main/java/org/openecomp/sparky/analytics/HistogramSampler.java new file mode 100644 index 0000000..7f87bea --- /dev/null +++ b/src/main/java/org/openecomp/sparky/analytics/HistogramSampler.java @@ -0,0 +1,287 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.analytics; + +/** + * A class that models a histogram for reporting and tracking long values with variable steps, bins, + * and floating point accuracy. + * + * @author davea. + */ +public final class HistogramSampler { + + private String label; + + private long binMaxValue; + + private int numBins; + + private double stepSize; + + private long sampleValueTotal; + + private long minValue = -1; + + private long maxValue = 0; + + private long numSamples = 0; + + private long decimalPointAccuracy = 0; + + private static String FORMAT_FLOAT_TEMPLATE = "%%.%df"; + + private String floatFormatStr; + + private long[] histogramBins; + + /** + * Instantiates a new histogram sampler. + * + * @param label the label + * @param maxValue the max value + * @param numBins the num bins + * @param decimalPointAccuracy the decimal point accuracy + */ + public HistogramSampler(String label, long maxValue, int numBins, int decimalPointAccuracy) { + this.label = label; + this.binMaxValue = maxValue; + this.numBins = numBins; + this.stepSize = ((double) binMaxValue / (double) numBins); + this.decimalPointAccuracy = decimalPointAccuracy; + this.floatFormatStr = String.format(FORMAT_FLOAT_TEMPLATE, this.decimalPointAccuracy); + + /* + * [numBins + 1] => last bin is catch-all for outliers + */ + + initializeHistogramBins(numBins + 1); + + } + + /** + * Initialize histogram bins. + * + * @param numBins the num bins + */ + private void initializeHistogramBins(int numBins) { + + histogramBins = new long[numBins]; + int counter = 0; + while (counter < numBins) { + histogramBins[counter] = 0; + counter++; + } + + } + + /* + * Is it really necessary to synchronize the collection, or should we simply switch the underlying + * data type to an AtomicLong + */ + + /** + * Track. + * + * @param value the value + */ + public synchronized void track(long value) { + + if (value < 0) { + return; + } + + sampleValueTotal += value; + numSamples++; + + if (minValue == -1) { + minValue = value; + } + + if (value < minValue) { + minValue = value; + } + + if (value > maxValue) { + maxValue = value; + } + + /* + * One step bin determination + */ + + if (value < (numBins * stepSize)) { + + int index = (int) (value / stepSize); + histogramBins[index]++; + + } else { + // peg the metric in the outlier bin + histogramBins[numBins - 1]++; + } + + } + + /** + * Clear. + */ + public void clear() { + + int counter = 0; + while (counter < numBins) { + histogramBins[counter] = 0; + counter++; + } + + minValue = -1; + maxValue = 0; + numSamples = 0; + sampleValueTotal = 0; + + } + + /** + * Re initialize bins. + * + * @param label the label + * @param numBins the num bins + * @param maxValue the max value + * @param decimalPointAccuracy the decimal point accuracy + */ + public void reInitializeBins(String label, int numBins, long maxValue, int decimalPointAccuracy) { + this.label = label; + this.decimalPointAccuracy = decimalPointAccuracy; + this.floatFormatStr = String.format(FORMAT_FLOAT_TEMPLATE, this.decimalPointAccuracy); + this.numBins = numBins; + this.minValue = -1; + this.maxValue = 0; + initializeHistogramBins(numBins); + this.stepSize = (maxValue / numBins); + clear(); + } + + public long getNumberOfSamples() { + return numSamples; + } + + public long getTotalValueSum() { + return sampleValueTotal; + } + + /** + * Gets the stats. + * + * @param formatted the formatted + * @param indentPadding the indent padding + * @return the stats + */ + public String getStats(boolean formatted, String indentPadding) { + + StringBuilder sb = new StringBuilder(128); + + + if (!formatted) { + // generate CSV in the following format + + /* + * label,minValue,maxValue,avgValue,numSamples,stepSize,numSteps,stepCounters + */ + sb.append(indentPadding); + sb.append(label).append(","); + sb.append(minValue).append(","); + sb.append(maxValue).append(","); + if (numSamples == 0) { + sb.append(0).append(","); + } else { + sb.append((sampleValueTotal / numSamples)).append(","); + } + sb.append(numSamples).append(","); + sb.append(numBins).append(","); + sb.append(String.format(floatFormatStr, stepSize)); + + int counter = 0; + while (counter < numBins) { + + if (counter != (numBins)) { + sb.append(","); + } + + sb.append(histogramBins[counter]); + + counter++; + + } + + return sb.toString(); + + } + + sb.append("\n"); + sb.append(indentPadding).append("Label = ").append(label).append("\n"); + sb.append(indentPadding).append("Min = ").append(minValue).append("\n"); + sb.append(indentPadding).append("Max = ").append(maxValue).append("\n"); + sb.append(indentPadding).append("numSamples = ").append(numSamples).append("\n"); + + if (numSamples == 0) { + sb.append(indentPadding).append("Avg = ").append(0).append("\n"); + } else { + sb.append(indentPadding).append("Avg = ").append((sampleValueTotal / numSamples)) + .append("\n"); + } + + sb.append(indentPadding).append("StepSize = ").append(String.format(floatFormatStr, stepSize)) + .append("\n"); + + sb.append(indentPadding).append("Sample Histogram:").append("\n"); + + int counter = 0; + while (counter < numBins) { + + if (counter == (numBins - 1)) { + // outlier bin + double leftBound = (stepSize * counter); + sb.append(indentPadding).append("\t") + .append(" x >= " + String.format(floatFormatStr, leftBound) + " : " + + histogramBins[counter]) + .append("\n"); + + } else { + double leftBound = (stepSize * counter); + double rightBound = ((stepSize) * (counter + 1)); + sb.append(indentPadding).append("\t") + .append((String.format(floatFormatStr, leftBound) + " < x < " + + String.format(floatFormatStr, rightBound) + " : " + histogramBins[counter])) + .append("\n"); + } + + counter++; + + } + + return sb.toString(); + + } + +} diff --git a/src/main/java/org/openecomp/sparky/analytics/HistoricalCounter.java b/src/main/java/org/openecomp/sparky/analytics/HistoricalCounter.java new file mode 100644 index 0000000..f8c5f05 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/analytics/HistoricalCounter.java @@ -0,0 +1,155 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + + +package org.openecomp.sparky.analytics; + +/** + * A simple class to model a historical counter. A set of values will be tracked and basic + * statistics will be calculated in real time (n, min, max, avg). + * + * @author davea + */ +public class HistoricalCounter { + + private double min; + + private double max; + + private double totalOfSamples; + + private long numSamples; + + private double value; + + private boolean maintainSingleValue; + + /** + * Instantiates a new historical counter. + * + * @param trackSingleValue the track single value + */ + public HistoricalCounter(boolean trackSingleValue) { + min = -1; + max = 0; + totalOfSamples = 0; + value = 0.0; + numSamples = 0; + this.maintainSingleValue = trackSingleValue; + } + + public boolean isSingleValue() { + return maintainSingleValue; + } + + /** + * Update. + * + * @param value the value + */ + public synchronized void update(double value) { + + if (value < 0) { + return; + } + + if (maintainSingleValue) { + + this.value = value; + + } else { + + if (min == -1) { + min = value; + } + + if (value < min) { + min = value; + } + + if (value > max) { + max = value; + } + + totalOfSamples += value; + numSamples++; + } + } + + public double getValue() { + return value; + } + + public double getMin() { + return min; + } + + public double getMax() { + return max; + } + + public long getNumSamples() { + return numSamples; + } + + public double getAvg() { + if (numSamples == 0) { + return 0; + } + + return (totalOfSamples / numSamples); + } + + /** + * Reset. + */ + public synchronized void reset() { + min = -1; + max = 0; + numSamples = 0; + totalOfSamples = 0; + value = 0.0; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(32); + + if (maintainSingleValue) { + sb.append("[ Val=").append(value).append(" ]"); + } else { + sb.append("[ NumSamples=").append(numSamples).append(","); + sb.append(" Min=").append(min).append(","); + sb.append(" Max=").append(max).append(","); + sb.append(" Avg=").append(getAvg()).append(" ]"); + } + + return sb.toString(); + } + +} diff --git a/src/main/java/org/openecomp/sparky/config/Configurable.java b/src/main/java/org/openecomp/sparky/config/Configurable.java new file mode 100644 index 0000000..4ea02ff --- /dev/null +++ b/src/main/java/org/openecomp/sparky/config/Configurable.java @@ -0,0 +1,46 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.config; + +import org.openecomp.sparky.config.exception.ConfigurationException; + +/** + * The Interface Configurable. + */ +public interface Configurable { + + public boolean isValid(); + + public boolean isInitialized(); + + /** + * Load config. + * + * @throws ConfigurationException the configuration exception + */ + public void loadConfig() throws ConfigurationException; + +} diff --git a/src/main/java/org/openecomp/sparky/config/exception/ConfigurationException.java b/src/main/java/org/openecomp/sparky/config/exception/ConfigurationException.java new file mode 100644 index 0000000..23f3666 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/config/exception/ConfigurationException.java @@ -0,0 +1,34 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.config.exception; + + +/** + * The Class ConfigurationException. + */ +public class ConfigurationException extends Exception { + +} diff --git a/src/main/java/org/openecomp/sparky/config/oxm/CrossEntityReference.java b/src/main/java/org/openecomp/sparky/config/oxm/CrossEntityReference.java new file mode 100644 index 0000000..855eea4 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/config/oxm/CrossEntityReference.java @@ -0,0 +1,80 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + + +package org.openecomp.sparky.config.oxm; + +import java.util.ArrayList; +import java.util.List; + +/** + * The Class CrossEntityReference. + */ +public class CrossEntityReference { + private String targetEntityType; + private List referenceAttributes; + + /** + * Instantiates a new cross entity reference. + */ + public CrossEntityReference() { + targetEntityType = null; + referenceAttributes = new ArrayList(); + } + + public String getTargetEntityType() { + return targetEntityType; + } + + public void setTargetEntityType(String targetEntityType) { + this.targetEntityType = targetEntityType; + } + + public List getReferenceAttributes() { + return referenceAttributes; + } + + public void setReferenceAttributes(List referenceAttributes) { + this.referenceAttributes = referenceAttributes; + } + + /** + * Adds the reference attribute. + * + * @param additionalAttribute the additional attribute + */ + public void addReferenceAttribute(String additionalAttribute) { + referenceAttributes.add(additionalAttribute); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "CrossEntityReference [targetEntityType=" + targetEntityType + ", referenceAttributes=" + + referenceAttributes + "]"; + } +} diff --git a/src/main/java/org/openecomp/sparky/config/oxm/OxmEntityDescriptor.java b/src/main/java/org/openecomp/sparky/config/oxm/OxmEntityDescriptor.java new file mode 100644 index 0000000..c38fa40 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/config/oxm/OxmEntityDescriptor.java @@ -0,0 +1,179 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.config.oxm; + +import java.util.List; + +import org.openecomp.sparky.synchronizer.entity.SuggestionSearchEntity; + +/** + * The Class OxmEntityDescriptor. + */ +public class OxmEntityDescriptor { + + private String entityName; + + private List primaryKeyAttributeName; + + private List searchableAttributes; + + private CrossEntityReference crossEntityReference; + + private String geoLatName; + + private String geoLongName; + + private SuggestionSearchEntity suggestionSearchEntity; + + public String getEntityName() { + return entityName; + } + + public void setEntityName(String entityName) { + this.entityName = entityName; + } + + public List getPrimaryKeyAttributeName() { + return primaryKeyAttributeName; + } + + public void setPrimaryKeyAttributeName(List primaryKeyAttributeName) { + this.primaryKeyAttributeName = primaryKeyAttributeName; + } + + public List getSearchableAttributes() { + return searchableAttributes; + } + + public void setSearchableAttributes(List searchableAttributes) { + this.searchableAttributes = searchableAttributes; + } + + /** + * Checks for searchable attributes. + * + * @return true, if successful + */ + public boolean hasSearchableAttributes() { + + if (this.searchableAttributes == null) { + return false; + } + + if (this.searchableAttributes.size() > 0) { + return true; + } + + return false; + + } + + public CrossEntityReference getCrossEntityReference() { + return crossEntityReference; + } + + public void setCrossEntityReference(CrossEntityReference crossEntityReference) { + this.crossEntityReference = crossEntityReference; + } + + /** + * Checks for cross entity references. + * + * @return true, if successful + */ + public boolean hasCrossEntityReferences() { + if (this.crossEntityReference == null) { + return false; + } + if (!this.crossEntityReference.getReferenceAttributes().isEmpty()) { + return true; + } + return false; + } + + public String getGeoLatName() { + return geoLatName; + } + + public void setGeoLatName(String geoLatName) { + this.geoLatName = geoLatName; + } + + public String getGeoLongName() { + return geoLongName; + } + + public void setGeoLongName(String geoLongName) { + this.geoLongName = geoLongName; + } + + /** + * Checks for geo entity. + * + * @return true, if successful + */ + public boolean hasGeoEntity() { + + if (this.geoLongName != null && this.geoLatName != null) { + return true; + } + + return false; + + } + + public SuggestionSearchEntity getSuggestionSearchEntity() { + return this.suggestionSearchEntity; + } + + public void setSuggestionSearchEntity(SuggestionSearchEntity suggestionSearchEntity) { + this.suggestionSearchEntity = suggestionSearchEntity; + } + + /** + * Checks for non-null, populated SuggestionSearchEntity. + * + * @return true, if successful + */ + public boolean hasSuggestionSearchEntity() { + if (this.suggestionSearchEntity == null) { + return false; + } + if (!this.suggestionSearchEntity.getSuggestionConnectorWords().isEmpty()) { + return true; + } + return false; + } + + @Override + public String toString() { + return "OxmEntityDescriptor [entityName=" + entityName + ", primaryKeyAttributeName=" + + primaryKeyAttributeName + ", searchableAttributes=" + searchableAttributes + + ", crossEntityReference=" + crossEntityReference + ", geoLatName=" + geoLatName + + ", geoLongName=" + geoLongName + ", suggestionSearchEntity=" + suggestionSearchEntity + + "]"; + } +} diff --git a/src/main/java/org/openecomp/sparky/config/oxm/OxmModelLoader.java b/src/main/java/org/openecomp/sparky/config/oxm/OxmModelLoader.java new file mode 100644 index 0000000..eef8c93 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/config/oxm/OxmModelLoader.java @@ -0,0 +1,534 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.config.oxm; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.persistence.dynamic.DynamicType; +import org.eclipse.persistence.internal.oxm.mappings.Descriptor; +import org.eclipse.persistence.jaxb.JAXBContextProperties; +import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; +import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory; +import org.eclipse.persistence.mappings.DatabaseMapping; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.entity.SuggestionSearchEntity; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + + +/** + * The Class OxmModelLoader. + */ +public class OxmModelLoader { + + private static OxmModelLoader instance; + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(OxmModelLoader.class); + + private Map> oxmModel = + new LinkedHashMap>(); + + private Map entityTypeLookup = new LinkedHashMap(); + + private Map> searchableOxmModel = + new LinkedHashMap>(); + + private Map> crossReferenceEntityOxmModel = + new LinkedHashMap>(); + + private Map> geoEntityOxmModel = + new LinkedHashMap>(); + + private Map> suggestionSearchEntityOxmModel = + new LinkedHashMap>(); + + private Map entityDescriptors = + new HashMap(); + + private Map searchableEntityDescriptors = + new HashMap(); + + private Map crossReferenceEntityDescriptors = + new HashMap(); + + private Map geoEntityDescriptors = + new HashMap(); + + private Map suggestionSearchEntityDescriptors = + new HashMap(); + + public static OxmModelLoader getInstance() { + if (instance == null) { + instance = new OxmModelLoader(); + LOG.info(AaiUiMsgs.INITIALIZE_OXM_MODEL_LOADER); + instance.loadModels(); + } + + return instance; + + } + + /** + * Instantiates a new oxm model loader. + */ + public OxmModelLoader() { + + } + + /** + * Load models. + */ + private void loadModels() { + // find latest version of OXM file in folder + String version = findLatestOxmVersion(); + if (version == null) { + LOG.error(AaiUiMsgs.OXM_FILE_NOT_FOUND, TierSupportUiConstants.CONFIG_OXM_LOCATION); + return; + } + + // load the latest version based on file name + loadModel(version); + + } + + /** + * Load model. + * + * @param version the version + */ + public void loadModel(String version) { + String fileName = loadOxmFileName(version); + InputStream inputStream; + try { + inputStream = new FileInputStream(new File(fileName)); + } catch (FileNotFoundException fnf) { + LOG.info(AaiUiMsgs.OXM_READ_ERROR_NONVERBOSE); + LOG.error(AaiUiMsgs.OXM_READ_ERROR_VERBOSE, fileName); + return; + } + + Map properties = new HashMap(); + properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, inputStream); + try { + final DynamicJAXBContext oxmContext = DynamicJAXBContextFactory + .createContextFromOXM(Thread.currentThread().getContextClassLoader(), properties); + + parseOxmContext(oxmContext); + // populateSearchableOxmModel(); + LOG.info(AaiUiMsgs.OXM_LOAD_SUCCESS); + } catch (Exception exc) { + LOG.info(AaiUiMsgs.OXM_PARSE_ERROR_NONVERBOSE); + LOG.error(AaiUiMsgs.OXM_PARSE_ERROR_VERBOSE, fileName, exc.getMessage()); + } + } + + /** + * Parses the oxm context. + * + * @param oxmContext the oxm context + */ + private void parseOxmContext(DynamicJAXBContext oxmContext) { + @SuppressWarnings("rawtypes") + List descriptorsList = oxmContext.getXMLContext().getDescriptors(); + + for (@SuppressWarnings("rawtypes") + Descriptor desc : descriptorsList) { + + DynamicType entity = oxmContext.getDynamicType(desc.getAlias()); + + LinkedHashMap oxmProperties = new LinkedHashMap(); + + // Not all fields have key attributes + if (desc.getPrimaryKeyFields() != null) { + oxmProperties.put("primaryKeyAttributeNames", desc.getPrimaryKeyFields().toString() + .replaceAll("/text\\(\\)", "").replaceAll("\\[", "").replaceAll("\\]", "")); + } + + String entityName = desc.getDefaultRootElement(); + + entityTypeLookup.put(entityName, entity); + + // add entityName + oxmProperties.put("entityName", entityName); + + Map properties = entity.getDescriptor().getProperties(); + if (properties != null) { + for (Map.Entry entry : properties.entrySet()) { + + if (entry.getKey().equalsIgnoreCase("searchable")) { + oxmProperties.put("searchableAttributes", entry.getValue()); + } else if (entry.getKey().equalsIgnoreCase("crossEntityReference")) { + oxmProperties.put("crossEntityReference", entry.getValue()); + } else if (entry.getKey().equalsIgnoreCase("geoLat")) { + if (entry.getValue().length() > 0) { + oxmProperties.put("geoLat", entry.getValue()); + } + } else if (entry.getKey().equalsIgnoreCase("geoLong")) { + if (entry.getValue().length() > 0) { + oxmProperties.put("geoLong", entry.getValue()); + } + } else if (entry.getKey().equalsIgnoreCase("containsSuggestibleProps")) { + + oxmProperties.put("containsSuggestibleProps", "true"); + + Vector descriptorMaps = entity.getDescriptor().getMappings(); + List listOfSuggestableAttributes = new ArrayList(); + + for (DatabaseMapping descMap : descriptorMaps) { + if (descMap.isAbstractDirectMapping()) { + + if (descMap.getProperties().get("suggestibleOnSearch") != null) { + String suggestableOnSearchString = String.valueOf( + descMap.getProperties().get("suggestibleOnSearch")); + + boolean isSuggestibleOnSearch = Boolean.valueOf(suggestableOnSearchString); + + if (isSuggestibleOnSearch) { + /* Grab attribute types for suggestion */ + String attributeName = descMap.getField().getName() + .replaceAll("/text\\(\\)", ""); + listOfSuggestableAttributes.add(attributeName); + + if (descMap.getProperties().get("suggestionVerbs") != null) { + String suggestionVerbsString = String.valueOf( + descMap.getProperties().get("suggestionVerbs")); + + oxmProperties.put("suggestionVerbs", suggestionVerbsString); + } + } + } + } + } + if (!listOfSuggestableAttributes.isEmpty()) { + oxmProperties.put("suggestibleAttributes", String.join(",", + listOfSuggestableAttributes)); + } + } else if (entry.getKey().equalsIgnoreCase("suggestionAliases")) { + oxmProperties.put("suggestionAliases", entry.getValue()); + } + } + } + + oxmModel.put(entityName, oxmProperties); + + // Add all searchable entity types for reserve lookup + if (oxmProperties.containsKey("searchableAttributes")) { + searchableOxmModel.put(entityName, oxmProperties); + } + + if (oxmProperties.containsKey("crossEntityReference")) { + crossReferenceEntityOxmModel.put(entityName, oxmProperties); + } + + if (oxmProperties.containsKey("geoLat") && oxmProperties.containsKey("geoLong")) { + geoEntityOxmModel.put(entityName, oxmProperties); + } + + if (oxmProperties.containsKey("containsSuggestibleProps")) { + suggestionSearchEntityOxmModel.put(entityName, oxmProperties); + } + } + + for (Entry> entityModel : oxmModel.entrySet()) { + HashMap attribute = entityModel.getValue(); + OxmEntityDescriptor entity = new OxmEntityDescriptor(); + entity.setEntityName(attribute.get("entityName")); + if (attribute.containsKey("primaryKeyAttributeNames")) { + + entity.setPrimaryKeyAttributeName( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + if (attribute.containsKey("searchableAttributes")) { + entity.setSearchableAttributes( + Arrays.asList(attribute.get("searchableAttributes").split(","))); + } else if (attribute.containsKey("crossEntityReference")) { + List crossEntityRefTokens = + Arrays.asList(attribute.get("crossEntityReference").split(",")); + + if (crossEntityRefTokens.size() >= 2) { + CrossEntityReference entityRef = new CrossEntityReference(); + entityRef.setTargetEntityType(crossEntityRefTokens.get(0)); + + for (int i = 1; i < crossEntityRefTokens.size(); i++) { + entityRef.addReferenceAttribute(crossEntityRefTokens.get(i)); + } + + entity.setCrossEntityReference(entityRef); + } else { + LOG.error(AaiUiMsgs.OXM_PROP_DEF_ERR_CROSS_ENTITY_REF, attribute.get("entityName"), + attribute.get("crossEntityReference")); + } + } + + if (attribute.containsKey("geoLat") || attribute.containsKey("geoLong")) { + entity.setGeoLatName(attribute.get("geoLat")); + entity.setGeoLongName(attribute.get("geoLong")); + } + + if (attribute.containsKey("suggestionVerbs")) { + String entityName = attribute.get("entityName"); + SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity(this); + suggestionSearchEntity.setEntityType(entityName); + + entity.setSuggestionSearchEntity(suggestionSearchEntity); + } + + entityDescriptors.put(attribute.get("entityName"), entity); + } + } + + + for (Entry> searchableModel : searchableOxmModel.entrySet()) { + HashMap attribute = searchableModel.getValue(); + OxmEntityDescriptor entity = new OxmEntityDescriptor(); + entity.setEntityName(attribute.get("entityName")); + entity.setPrimaryKeyAttributeName( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + entity + .setSearchableAttributes(Arrays.asList(attribute.get("searchableAttributes").split(","))); + searchableEntityDescriptors.put(attribute.get("entityName"), entity); + } + + for (Entry> geoEntityModel : geoEntityOxmModel.entrySet()) { + HashMap attribute = geoEntityModel.getValue(); + OxmEntityDescriptor entity = new OxmEntityDescriptor(); + entity.setEntityName(attribute.get("entityName")); + entity.setPrimaryKeyAttributeName( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + entity.setGeoLatName(attribute.get("geoLat")); + entity.setGeoLongName(attribute.get("geoLong")); + geoEntityDescriptors.put(attribute.get("entityName"), entity); + } + + for (Entry> crossRefModel : crossReferenceEntityOxmModel + .entrySet()) { + HashMap attribute = crossRefModel.getValue(); + OxmEntityDescriptor entity = new OxmEntityDescriptor(); + entity.setEntityName(attribute.get("entityName")); + entity.setPrimaryKeyAttributeName( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + + + List crossEntityRefTokens = + Arrays.asList(attribute.get("crossEntityReference").split(",")); + + if (crossEntityRefTokens.size() >= 2) { + CrossEntityReference entityRef = new CrossEntityReference(); + entityRef.setTargetEntityType(crossEntityRefTokens.get(0)); + + for (int i = 1; i < crossEntityRefTokens.size(); i++) { + entityRef.addReferenceAttribute(crossEntityRefTokens.get(i)); + } + + entity.setCrossEntityReference(entityRef); + } + crossReferenceEntityDescriptors.put(attribute.get("entityName"), entity); + } + + for (Entry> suggestionEntityModel : + suggestionSearchEntityOxmModel.entrySet()) { + HashMap attribute = suggestionEntityModel.getValue(); + + String entityName = attribute.get("entityName"); + SuggestionSearchEntity suggestionSearchEntity = new SuggestionSearchEntity(this); + suggestionSearchEntity.setEntityType(entityName); + + if (attribute.get("suggestionVerbs") != null) { + suggestionSearchEntity.setSuggestionConnectorWords(Arrays.asList( + attribute.get("suggestionVerbs").split(","))); + } + + if (attribute.get("suggestionAliases") != null) { + suggestionSearchEntity.setSuggestionAliases(Arrays.asList( + attribute.get("suggestionAliases").split(","))); + } + + if (attribute.get("suggestibleAttributes") != null) { + suggestionSearchEntity.setSuggestionPropertyTypes(Arrays.asList( + attribute.get("suggestibleAttributes").split(","))); + } + + OxmEntityDescriptor entity = new OxmEntityDescriptor(); + entity.setSuggestionSearchEntity(suggestionSearchEntity); + entity.setEntityName(entityName); + + if (attribute.get("primaryKeyAttributeNames") != null) { + entity.setPrimaryKeyAttributeName( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + } + + suggestionSearchEntityDescriptors.put(entityName, entity); + } + } + + /** + * Find latest oxm version. + * + * @return the string + */ + public String findLatestOxmVersion() { + File[] listOxmFiles = loadOxmFolder().listFiles(); + + if (listOxmFiles == null) { + return null; + } + + Integer latestVersion = -1; + + Pattern oxmFileNamePattern = Pattern.compile("^aai_oxm_v([0-9]*).xml"); + + for (File file : listOxmFiles) { + if (file.isFile()) { + String fileName = file.getName(); + Matcher matcher = oxmFileNamePattern.matcher(fileName); + if (matcher.matches()) { + if (latestVersion <= Integer.parseInt(matcher.group(1))) { + latestVersion = Integer.parseInt(matcher.group(1)); + } + } + } + + } + if (latestVersion != -1) { + return "v" + latestVersion.toString(); + } else { + return null; + } + + } + + /** + * Load oxm folder. + * + * @return the file + */ + public File loadOxmFolder() { + return new File(TierSupportUiConstants.CONFIG_OXM_LOCATION); + } + + /** + * Load oxm file name. + * + * @param version the version + * @return the string + */ + public String loadOxmFileName(String version) { + return new String(TierSupportUiConstants.CONFIG_OXM_LOCATION + "aai_oxm_" + version + ".xml"); + } + + /* + * Get the original representation of the OXM Model + */ + public Map> getOxmModel() { + return oxmModel; + } + + /* + * Get the searchable raw map entity types + */ + public Map> getSearchableOxmModel() { + return searchableOxmModel; + } + + public Map> getCrossReferenceEntityOxmModel() { + return crossReferenceEntityOxmModel; + } + + public Map getEntityDescriptors() { + return entityDescriptors; + } + + /** + * Gets the entity descriptor. + * + * @param type the type + * @return the entity descriptor + */ + public OxmEntityDescriptor getEntityDescriptor(String type) { + return entityDescriptors.get(type); + } + + public Map getSearchableEntityDescriptors() { + return searchableEntityDescriptors; + } + + /** + * Gets the searchable entity descriptor. + * + * @param entityType the entity type + * @return the searchable entity descriptor + */ + public OxmEntityDescriptor getSearchableEntityDescriptor(String entityType) { + return searchableEntityDescriptors.get(entityType); + } + + public Map getCrossReferenceEntityDescriptors() { + return crossReferenceEntityDescriptors; + } + + public Map getGeoEntityDescriptors() { + return geoEntityDescriptors; + } + + public Map getSuggestionSearchEntityDescriptors() { + return suggestionSearchEntityDescriptors; + } + + /** + * The main method. + * + * @param args the arguments + */ + public static void main(String[] args) { + try { + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + } catch (IOException exc) { + // TODO Auto-generated catch block + exc.printStackTrace(); + } + Map temp = + OxmModelLoader.getInstance().getSearchableEntityDescriptors(); + Map temp2 = OxmModelLoader.getInstance().getEntityDescriptors(); + + System.out.println("Completed"); + } + +} diff --git a/src/main/java/org/openecomp/sparky/config/oxm/OxmModelLoaderFilter.java b/src/main/java/org/openecomp/sparky/config/oxm/OxmModelLoaderFilter.java new file mode 100644 index 0000000..ac29199 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/config/oxm/OxmModelLoaderFilter.java @@ -0,0 +1,88 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.config.oxm; + +import java.io.IOException; +import java.net.UnknownHostException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.openecomp.sparky.util.NodeUtils; + +import org.openecomp.cl.mdc.MdcContext; + +/** + * The Class OxmModelLoaderFilter. + */ +public class OxmModelLoaderFilter implements Filter { + /* (non-Javadoc) + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + /* + * However, we will setup the filtermap with a url that should never get it, so we shouldn't + * ever be in here. + */ + + chain.doFilter(request, response); + } + + /* (non-Javadoc) + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "OxmModelLoaderFilter", "", + "Init", ""); + + try { + OxmModelLoader.getInstance(); + } catch (Exception exc) { + throw new ServletException("Caught an exception while initializing OXM model loader filter", + exc); + } + + } + + /* (non-Javadoc) + * @see javax.servlet.Filter#destroy() + */ + @Override + public void destroy() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/NetworkTransaction.java b/src/main/java/org/openecomp/sparky/dal/NetworkTransaction.java new file mode 100644 index 0000000..0a679cf --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/NetworkTransaction.java @@ -0,0 +1,135 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal; + +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class NetworkTransaction. + */ +public class NetworkTransaction { + + private OperationResult operationResult; + + private String entityType; + + private String link; + + private HttpMethod operationType; + + private OxmEntityDescriptor descriptor; + + private long createdTimeStampInMs; + + private long taskAgeInMs; + + /** + * Instantiates a new network transaction. + */ + public NetworkTransaction() { + this.createdTimeStampInMs = System.currentTimeMillis(); + } + + /** + * Instantiates a new network transaction. + * + * @param method the method + * @param entityType the entity type + * @param or the or + */ + public NetworkTransaction(HttpMethod method, String entityType, OperationResult or) { + this(); + this.operationType = method; + this.entityType = entityType; + this.operationResult = or; + } + + public HttpMethod getOperationType() { + return operationType; + } + + public long getTaskAgeInMs() { + return taskAgeInMs; + } + + /** + * Sets the task age in ms. + */ + public void setTaskAgeInMs() { + this.taskAgeInMs = (System.currentTimeMillis() - createdTimeStampInMs); + } + + public void setOperationType(HttpMethod operationType) { + this.operationType = operationType; + } + + public OperationResult getOperationResult() { + return operationResult; + } + + public void setOperationResult(OperationResult operationResult) { + this.operationResult = operationResult; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + + public OxmEntityDescriptor getDescriptor() { + return descriptor; + } + + public void setDescriptor(OxmEntityDescriptor descriptor) { + this.descriptor = descriptor; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "NetworkTransaction [operationResult=" + operationResult.toString() + ", entityType=" + + entityType + ", link=" + link + ", operationType=" + operationType + ", descriptor=" + + descriptor.toString() + ", createdTimeStampInMs=" + createdTimeStampInMs + + ", taskAgeInMs=" + taskAgeInMs + "]"; + } + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryAdapter.java b/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryAdapter.java new file mode 100644 index 0000000..de2085c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryAdapter.java @@ -0,0 +1,418 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.aai; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.NoSuchElementException; + +import org.apache.http.client.utils.URIBuilder; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryRestConfig; +import org.openecomp.sparky.dal.aai.enums.RestAuthenticationMode; +import org.openecomp.sparky.dal.exception.ElasticSearchOperationException; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestClientBuilder; +import org.openecomp.sparky.dal.rest.RestfulDataAccessor; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.security.SecurityContextFactory; +import org.openecomp.sparky.util.NodeUtils; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.WebResource.Builder; + + +/** + * The Class ActiveInventoryAdapter. + */ + +/** + * @author davea + * + */ +public class ActiveInventoryAdapter extends RestfulDataAccessor + implements ActiveInventoryDataProvider { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(ActiveInventoryAdapter.class); + + private static final String HEADER_TRANS_ID = "X-TransactionId"; + private static final String HEADER_FROM_APP_ID = "X-FromAppId"; + private static final String HEADER_AUTHORIZATION = "Authorization"; + + private static final String TRANSACTION_ID_PREFIX = "txnId-"; + private static final String UI_APP_NAME = "AAI-UI"; + + + private ActiveInventoryConfig config; + + /** + * Instantiates a new active inventory adapter. + * + * @param restClientBuilder the rest client builder + * @throws ElasticSearchOperationException the elastic search operation exception + * @throws IOException Signals that an I/O exception has occurred. + */ + public ActiveInventoryAdapter(RestClientBuilder restClientBuilder) + throws ElasticSearchOperationException, IOException { + super(restClientBuilder); + + try { + this.config = ActiveInventoryConfig.getConfig(); + } catch (Exception exc) { + throw new ElasticSearchOperationException("Error getting active inventory configuration", + exc); + } + + clientBuilder.setUseHttps(true); + + clientBuilder.setValidateServerHostname(config.getAaiSslConfig().isValidateServerHostName()); + + SecurityContextFactory sslContextFactory = clientBuilder.getSslContextFactory(); + + sslContextFactory.setServerCertificationChainValidationEnabled( + config.getAaiSslConfig().isValidateServerCertificateChain()); + + if (config.getAaiRestConfig().getAuthenticationMode() == RestAuthenticationMode.SSL_CERT) { + sslContextFactory.setClientCertFileName(config.getAaiSslConfig().getKeystoreFilename()); + sslContextFactory.setClientCertPassword(config.getAaiSslConfig().getKeystorePassword()); + sslContextFactory.setTrustStoreFileName(config.getAaiSslConfig().getTruststoreFilename()); + } + + clientBuilder.setConnectTimeoutInMs(config.getAaiRestConfig().getConnectTimeoutInMs()); + clientBuilder.setReadTimeoutInMs(config.getAaiRestConfig().getReadTimeoutInMs()); + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestfulDataAccessor#setClientDefaults(com.sun.jersey.api.client.Client, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + protected Builder setClientDefaults(Client client, String url, String payloadContentType, + String acceptContentType) { + Builder builder = super.setClientDefaults(client, url, payloadContentType, acceptContentType); + + builder = builder.header(HEADER_FROM_APP_ID, UI_APP_NAME); + byte bytes[] = new byte[6]; + txnIdGenerator.nextBytes(bytes); + builder = + builder.header(HEADER_TRANS_ID, TRANSACTION_ID_PREFIX + ByteBuffer.wrap(bytes).getInt()); + + if (config.getAaiRestConfig().getAuthenticationMode() == RestAuthenticationMode.SSL_BASIC) { + builder = builder.header(HEADER_AUTHORIZATION, + config.getAaiSslConfig().getBasicAuthenticationCredentials()); + } + + return builder; + } + + /** + * The main method. + * + * @param args the arguments + */ + public static void main(String[] args) { + + // TODO Auto-generated method stub + RestClientBuilder builder = new RestClientBuilder(); + RestfulDataAccessor accessor; + try { + accessor = new ActiveInventoryAdapter(builder); + OperationResult or = + accessor.doGet("/cloud-infrastructure/pservers/pserver/SQLTEST006", "application/json"); + String jsonPatch = "{ \"hostname\" : \"SQLTEST006\", \"prov-status\" : \"PREPROV\"," + + " \"in-maint\" : \"false\", \"is-closed-loop\" : \"false\" }"; + or = accessor.doPatch("/cloud-infrastructure/pservers/pserver/SQLTEST006", jsonPatch, + "application/json"); + // System.out.println("PATCH or = " + or.getResultCode() + " : " + or.toString()); + } catch (ElasticSearchOperationException | IOException exc) { + // TODO Auto-generated catch block + exc.printStackTrace(); + } + + } + + /** + * Gets the full url. + * + * @param resourceUrl the resource url + * @return the full url + * @throws Exception the exception + */ + private String getFullUrl(String resourceUrl) throws Exception { + ActiveInventoryRestConfig aaiRestConfig = ActiveInventoryConfig.getConfig().getAaiRestConfig(); + final String host = aaiRestConfig.getHost(); + final String port = aaiRestConfig.getPort(); + final String basePath = aaiRestConfig.getResourceBasePath(); + return String.format("https://%s:%s%s%s", host, port, basePath, resourceUrl); + } + + public String getGenericQueryForSelfLink(String startNodeType, List queryParams) throws Exception { + + URIBuilder urlBuilder = new URIBuilder(getFullUrl("/search/generic-query")); + + for( String queryParam : queryParams) { + urlBuilder.addParameter("key", queryParam); + } + + urlBuilder.addParameter("start-node-type", startNodeType); + urlBuilder.addParameter("include", startNodeType); + + final String constructedLink = urlBuilder.toString(); + + // TODO: debug log for constructed link + + return constructedLink; + +} + + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider#getSelfLinksByEntityType(java.lang.String) + */ + @Override + public OperationResult getSelfLinksByEntityType(String entityType) throws Exception { + + /* + * For this one, I want to dynamically construct the nodes-query for self-link discovery as a + * utility method that will use the OXM model entity data to drive the query as well. + */ + + if (entityType == null) { + throw new NullPointerException( + "Failed to getSelfLinksByEntityType() because entityType is null"); + } + + OxmEntityDescriptor entityDescriptor = + OxmModelLoader.getInstance().getEntityDescriptor(entityType); + + if (entityDescriptor == null) { + throw new NoSuchElementException("Failed to getSelfLinksByEntityType() because could" + + " not find entity descriptor from OXM with type = " + entityType); + } + + String link = null; + final String primaryKeyStr = + NodeUtils.concatArray(entityDescriptor.getPrimaryKeyAttributeName(), "/"); + + link = getFullUrl("/search/nodes-query?search-node-type=" + entityType + "&filter=" + + primaryKeyStr + ":EXISTS"); + + + + return doGet(link, "application/json"); + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider#getSelfLinkForEntity(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult getSelfLinkForEntity(String entityType, String primaryKeyName, + String primaryKeyValue) throws Exception { + + if (entityType == null) { + throw new NullPointerException("Failed to getSelfLinkForEntity() because entityType is null"); + } + + if (primaryKeyName == null) { + throw new NullPointerException( + "Failed to getSelfLinkForEntity() because primaryKeyName is null"); + } + + if (primaryKeyValue == null) { + throw new NullPointerException( + "Failed to getSelfLinkForEntity() because primaryKeyValue is null"); + } + + // https://aai-int1.test.att.com:8443/aai/v8/search/generic-query?key=complex.physical-location-id:atlngade&start-node-type=complex + + /* + * Try to protect ourselves from illegal URI formatting exceptions caused by characters that + * aren't natively supported in a URI, but can be escaped to make them legal. + */ + + String encodedEntityType = URLEncoder.encode(entityType, "UTF-8"); + String encodedPrimaryKeyName = URLEncoder.encode(primaryKeyName, "UTF-8"); + String encodedPrimaryKeyValue = URLEncoder.encode(primaryKeyValue, "UTF-8"); + + String link = null; + + if ("service-instance".equals(entityType)) { + + link = getFullUrl("/search/generic-query?key=" + encodedEntityType + "." + + encodedPrimaryKeyName + ":" + encodedPrimaryKeyValue + "&start-node-type=" + + encodedEntityType + "&include=customer&depth=2"); + + } else { + + link = + getFullUrl("/search/generic-query?key=" + encodedEntityType + "." + encodedPrimaryKeyName + + ":" + encodedPrimaryKeyValue + "&start-node-type=" + encodedEntityType); + + } + + return queryActiveInventoryWithRetries(link, "application/json", + this.config.getAaiRestConfig().getNumRequestRetries()); + + } + + + /** + * Our retry conditions should be very specific. + * + * @param r the r + * @return true, if successful + */ + private boolean shouldRetryRequest(OperationResult r) { + + if (r == null) { + return true; + } + + int rc = r.getResultCode(); + + if (rc == 200) { + return false; + } + + if (rc == 404) { + return false; + } + + return true; + + } + + /** + * Query active inventory. + * + * @param url the url + * @param acceptContentType the accept content type + * @return the operation result + */ + // package protected for test classes instead of private + OperationResult queryActiveInventory(String url, String acceptContentType) { + return doGet(url, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider#queryActiveInventoryWithRetries(java.lang.String, java.lang.String, int) + */ + @Override + public OperationResult queryActiveInventoryWithRetries(String url, String responseType, + int numRetries) { + + OperationResult result = null; + + for (int x = 0; x < numRetries; x++) { + + LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_SEQ, url, String.valueOf(x + 1)); + + result = queryActiveInventory(url, responseType); + + /** + * Record number of times we have attempted the request to later summarize how many times we + * are generally retrying over thousands of messages in a sync. + * + * If the number of retries is surprisingly high, then we need to understand why that is as + * the number of retries is also causing a heavier load on AAI beyond the throttling controls + * we already have in place in term of the transaction rate controller and number of + * parallelized threads per task processor. + */ + + result.setNumRequestRetries(x); + + if (!shouldRetryRequest(result)) { + + /* + * if (myConfig.getAaiRestConfig().isCacheEnabled()) { + * + * CachedHttpRequest cachedRequest = new CachedHttpRequest(); + * cachedRequest.setHttpRequestMethod("GET"); cachedRequest.setPayload(""); + * cachedRequest.setPayloadMimeType(""); cachedRequest.setUrl(url); + * cachedRequest.setOperationType( TransactionStorageType.ACTIVE_INVENTORY_QUERY.getIndex() + * ); + * + * CachedHttpResponse cachedResponse = new CachedHttpResponse(); + * cachedResponse.setPayload(result.getResult()); + * cachedResponse.setPayloadMimeType("application/json"); + * cachedResponse.setStatusCode(result.getResultCode()); + * + * CachedHttpTransaction txn = new CachedHttpTransaction(cachedRequest, cachedResponse); + * storageProvider.persistTransaction(txn); + * + * } + */ + + + result.setResolvedLinkFromServer(true); + LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_DONE_SEQ, url, String.valueOf(x + 1)); + + return result; + } + + try { + /* + * Sleep between re-tries to be nice to the target system. + */ + Thread.sleep(50); + } catch (InterruptedException exc) { + LOG.error(AaiUiMsgs.QUERY_AAI_WAIT_INTERRUPTION, exc.getLocalizedMessage()); + break; + } + LOG.error(AaiUiMsgs.QUERY_AAI_RETRY_FAILURE_WITH_SEQ, url, String.valueOf(x + 1)); + } + + + result.setResolvedLinkFailure(true); + LOG.info(AaiUiMsgs.QUERY_AAI_RETRY_MAXED_OUT, url); + + return result; + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestfulDataAccessor#shutdown() + */ + @Override + public void shutdown() { + // TODO Auto-generated method stub + + if (entityCache != null) { + entityCache.shutdown(); + } + + } + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryDataProvider.java b/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryDataProvider.java new file mode 100644 index 0000000..8be4a65 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryDataProvider.java @@ -0,0 +1,91 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.aai; + +import java.util.List; + +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestDataProvider; + +/** + * The Interface ActiveInventoryDataProvider. + */ +public interface ActiveInventoryDataProvider extends RestDataProvider { + + /** + * Gets the self links by entity type. + * + * @param entityType the entity type + * @return the self links by entity type + * @throws Exception the exception + */ + /* + * This one will do the nodes-query and understand enough to make that happen + */ + OperationResult getSelfLinksByEntityType(String entityType) throws Exception; + + /** + * Gets the self link for entity. + * + * @param entityType the entity type + * @param primaryKeyName the primary key name + * @param primaryKeyValue the primary key value + * @return the self link for entity + * @throws Exception the exception + */ + OperationResult getSelfLinkForEntity(String entityType, String primaryKeyName, + String primaryKeyValue) throws Exception; + + /** + * Query active inventory with retries. + * + * @param url the url + * @param responseType the response type + * @param numRetries the num retries + * @return the operation result + */ + OperationResult queryActiveInventoryWithRetries(String url, String responseType, int numRetries); + + + /** + * Determines the self-link for an entity with passed-in key-value pairs. + * + * @param startNodeType + * @param keyParams + * @return + * @throws Exception + */ + String getGenericQueryForSelfLink(String startNodeType, List queryKeyParams) throws Exception; + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.dal.rest.RestDataProvider#shutdown() + */ + @Override + void shutdown(); + +} diff --git a/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryEntityStatistics.java b/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryEntityStatistics.java new file mode 100644 index 0000000..0671b3e --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryEntityStatistics.java @@ -0,0 +1,307 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.aai; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class ActiveInventoryEntityStatistics. + */ +public class ActiveInventoryEntityStatistics { + + private static final String TOTAL = "Total"; + + private static final String FOUND = "Found"; + + private static final String NO_PAYLOAD = "NoPayload"; + + private static final String NOT_FOUND = "NotFound"; + + private static final String NUM_RETRIES = "NumRetries"; + + private static final String ERROR = "Error"; + + private OxmModelLoader loader; + + + private Map> activeInventoryEntityStatistics; + + /** + * Creates the entity op stats. + * + * @return the hash map + */ + private HashMap createEntityOpStats() { + + HashMap opStats = new HashMap(); + + opStats.put(TOTAL, new AtomicInteger()); + opStats.put(FOUND, new AtomicInteger()); + opStats.put(NO_PAYLOAD, new AtomicInteger()); + opStats.put(NOT_FOUND, new AtomicInteger()); + opStats.put(NUM_RETRIES, new AtomicInteger()); + opStats.put(ERROR, new AtomicInteger()); + + return opStats; + + } + + /* + * private void createSearchableActiveInventoryEntityStatistics() { + * + * Map descriptors = loader.getSearchableEntityDescriptors(); + * + * if(descriptors == null) { return; } + * + * OxmEntityDescriptor d = null; for ( String key : descriptors.keySet() ) { d = + * descriptors.get(key); activeInventoryEntityStatistics.put(d.getEntityName(), + * createEntityOpStats()); } + * + * } + */ + + /* + * private void createCrossEntityReferenceActiveInventoryEntityStatistics() { + * + * Map descriptors = loader.getCrossReferenceEntityDescriptors(); + * + * + * } + */ + + + /** + * Initializecreate active inventory entity statistics. + */ + private void initializecreateActiveInventoryEntityStatistics() { + Set keys = activeInventoryEntityStatistics.keySet(); + + Set opStatKeySet = null; + Map opStats = null; + + for (String k : keys) { + + opStats = activeInventoryEntityStatistics.get(k); + + opStatKeySet = opStats.keySet(); + + for (String opStatKey : opStatKeySet) { + opStats.get(opStatKey).set(0); + } + } + } + + /** + * Instantiates a new active inventory entity statistics. + * + * @param loader the loader + */ + public ActiveInventoryEntityStatistics(OxmModelLoader loader) { + this.loader = loader; + activeInventoryEntityStatistics = new HashMap>(); + // createSearchableActiveInventoryEntityStatistics(); + // createCrossEntityReferenceActiveInventoryEntityStatistics(); + reset(); + } + + /** + * Initialize counters from oxm entity descriptors. + * + * @param descriptors the descriptors + */ + public void initializeCountersFromOxmEntityDescriptors( + Map descriptors) { + + if (descriptors == null) { + return; + } + + OxmEntityDescriptor descriptor = null; + for (String key : descriptors.keySet()) { + descriptor = descriptors.get(key); + activeInventoryEntityStatistics.put(descriptor.getEntityName(), createEntityOpStats()); + } + } + + + /** + * Reset. + */ + public void reset() { + initializecreateActiveInventoryEntityStatistics(); + } + + /** + * Gets the result code. + * + * @param txn the txn + * @return the result code + */ + private int getResultCode(NetworkTransaction txn) { + + + if (txn == null) { + return -1; + } + + OperationResult or = txn.getOperationResult(); + + if (or == null) { + return -1; + } + + return or.getResultCode(); + + } + + /** + * Update active inventory entity counters. + * + * @param txn the txn + */ + private void updateActiveInventoryEntityCounters(NetworkTransaction txn) { + + if (txn == null) { + return; + } + + Map opStats = activeInventoryEntityStatistics.get(txn.getEntityType()); + + int rc = getResultCode(txn); + + switch (txn.getOperationType()) { + + case GET: { + + opStats.get(TOTAL).incrementAndGet(); + + if (200 <= rc && rc <= 299) { + opStats.get(FOUND).incrementAndGet(); + } else if (rc == 404) { + opStats.get(NOT_FOUND).incrementAndGet(); + } else { + opStats.get(ERROR).incrementAndGet(); + } + + break; + } + + default: { + // nothing else for now + } + + } + + OperationResult or = txn.getOperationResult(); + + if (or != null && or.wasSuccessful()) { + + if (or.getResult() == null || or.getResult().length() == 0) { + opStats.get(NO_PAYLOAD).incrementAndGet(); + } + + if (or.getNumRequestRetries() > 0) { + opStats.get(NUM_RETRIES).addAndGet(or.getNumRequestRetries()); + } + + } + + + } + + /** + * Update counters. + * + * @param txn the txn + */ + public void updateCounters(NetworkTransaction txn) { + + updateActiveInventoryEntityCounters(txn); + + } + + public String getStatisticsReport() { + + StringBuilder sb = new StringBuilder(128); + + /* + * sort entities, then sort nested op codes + */ + + TreeMap> activeInventoryEntitySortedTreeMap = + new TreeMap>(new Comparator() { + + @Override + public int compare(String o1, String o2) { + return o1.toLowerCase().compareTo(o2.toLowerCase()); + } + }); + + activeInventoryEntitySortedTreeMap.putAll(activeInventoryEntityStatistics); + + for (String counterEntityKey : activeInventoryEntitySortedTreeMap.keySet()) { + + HashMap entityCounters = + activeInventoryEntitySortedTreeMap.get(counterEntityKey); + + AtomicInteger total = entityCounters.get(TOTAL); + AtomicInteger found = entityCounters.get(FOUND); + AtomicInteger noPayload = entityCounters.get(NO_PAYLOAD); + AtomicInteger notFound = entityCounters.get(NOT_FOUND); + AtomicInteger numRetries = entityCounters.get(NUM_RETRIES); + AtomicInteger error = entityCounters.get(ERROR); + + int totalValue = (total == null) ? 0 : total.get(); + int foundValue = (found == null) ? 0 : found.get(); + int noPayloadValue = (noPayload == null) ? 0 : noPayload.get(); + int notFoundValue = (notFound == null) ? 0 : notFound.get(); + int numRetriesValue = (numRetries == null) ? 0 : numRetries.get(); + int errorValue = (error == null) ? 0 : error.get(); + + sb.append("\n ") + .append(String.format( + "%-30s TOTAL: %-12d FOUND: %-12d NO_PAYLOAD:" + + " %-12d NOT_FOUND: %-12d NUM_RETRIES: %-12d ERROR: %-12d", + counterEntityKey, totalValue, foundValue, noPayloadValue, notFoundValue, + numRetriesValue, errorValue)); + } + + return sb.toString(); + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryProcessingExceptionStatistics.java b/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryProcessingExceptionStatistics.java new file mode 100644 index 0000000..7a61972 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/aai/ActiveInventoryProcessingExceptionStatistics.java @@ -0,0 +1,139 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.aai; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.analytics.AbstractStatistics; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; + +/** + * The Class ActiveInventoryProcessingExceptionStatistics. + */ +public class ActiveInventoryProcessingExceptionStatistics extends AbstractStatistics { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(ActiveInventoryAdapter.class); + + private static final String NATIVE_SOCKET_CONNECT_EXCEPTION = "NativeSocketConnectException"; + private static final String NATIVE_SOCKET_CONNECTION_RESET = "NativeSocketConnectionReset"; + private static final String NATIVE_SOCKET_CONNECTION_REFUSED = "NativeSocketConnectionRefused"; + private static final String CLIENT_TIMEOUT_EXCEPTION = "JerseyClientTimoutException"; + private static final String UNKNOWN_EXCEPTION = "UnknownException"; + + /** + * Creates the counters. + */ + private void createCounters() { + addCounter(NATIVE_SOCKET_CONNECT_EXCEPTION); + addCounter(NATIVE_SOCKET_CONNECTION_RESET); + addCounter(NATIVE_SOCKET_CONNECTION_REFUSED); + addCounter(CLIENT_TIMEOUT_EXCEPTION); + addCounter(UNKNOWN_EXCEPTION); + } + + /** + * Instantiates a new active inventory processing exception statistics. + */ + public ActiveInventoryProcessingExceptionStatistics() { + createCounters(); + reset(); + } + + /** + * Update counters. + * + * @param txn the txn + */ + public void updateCounters(NetworkTransaction txn) { + + if (txn == null) { + return; + } + + OperationResult or = txn.getOperationResult(); + + if (or != null && !or.wasSuccessful()) { + + if (or.getResultCode() != 404) { + + String result = or.getResult(); + + if (result != null) { + + /* + * Try to classify exceptions and peg counters + */ + + if (result.contains("java.net.SocketTimeoutException: connect timed out")) { + pegCounter(CLIENT_TIMEOUT_EXCEPTION); + } else if (result.contains("java.net.ConnectException: Connection timed out: connect")) { + pegCounter(NATIVE_SOCKET_CONNECT_EXCEPTION); + } else if (result.contains("java.net.ConnectException: Connection refused: connect")) { + pegCounter(NATIVE_SOCKET_CONNECTION_REFUSED); + } else if (result.contains("java.net.SocketException: Connection reset")) { + pegCounter(NATIVE_SOCKET_CONNECTION_RESET); + } else { + pegCounter(UNKNOWN_EXCEPTION); + LOG.error(AaiUiMsgs.PEGGING_ERROR, result.toString()); + } + + } + } + + } + + } + + public String getStatisticsReport() { + + StringBuilder sb = new StringBuilder(128); + + int nativeConnect = getCounterValue(NATIVE_SOCKET_CONNECT_EXCEPTION); + int nativeCxnReset = getCounterValue(NATIVE_SOCKET_CONNECTION_RESET); + int nativeCxnRefused = getCounterValue(NATIVE_SOCKET_CONNECTION_REFUSED); + int clientTimeout = getCounterValue(CLIENT_TIMEOUT_EXCEPTION); + int unknown = getCounterValue(UNKNOWN_EXCEPTION); + + sb.append("\n ") + .append(String.format("%-40s: %-12d", NATIVE_SOCKET_CONNECT_EXCEPTION, nativeConnect)); + sb.append("\n ") + .append(String.format("%-40s: %-12d", NATIVE_SOCKET_CONNECTION_RESET, nativeCxnReset)); + sb.append("\n ") + .append(String.format("%-40s: %-12d", NATIVE_SOCKET_CONNECTION_REFUSED, nativeCxnRefused)); + sb.append("\n ") + .append(String.format("%-40s: %-12d", CLIENT_TIMEOUT_EXCEPTION, clientTimeout)); + sb.append("\n ").append(String.format("%-40s: %-12d", UNKNOWN_EXCEPTION, unknown)); + + return sb.toString(); + + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryConfig.java b/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryConfig.java new file mode 100644 index 0000000..c0f5db8 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryConfig.java @@ -0,0 +1,159 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.aai.config; + +import java.net.URI; +import java.util.Properties; + +import javax.ws.rs.core.UriBuilder; + +import org.openecomp.sparky.synchronizer.config.TaskProcessorConfig; +import org.openecomp.sparky.util.ConfigHelper; +import org.openecomp.sparky.util.Encryptor; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +/** + * The Class ActiveInventoryConfig. + */ +public class ActiveInventoryConfig { + + + + public static final String CONFIG_FILE = + TierSupportUiConstants.DYNAMIC_CONFIG_APP_LOCATION + "aai.properties"; + private static ActiveInventoryConfig instance; + + private static final String HTTP_SCHEME = "http"; + private static final String HTTPS_SCHEME = "https"; + + public static ActiveInventoryConfig getConfig() throws Exception { + if (instance == null) { + instance = new ActiveInventoryConfig(); + } + + return instance; + } + + private ActiveInventoryRestConfig aaiRestConfig; + private ActiveInventorySslConfig aaiSslConfig; + private TaskProcessorConfig taskProcessorConfig; + + /** + * Instantiates a new active inventory config. + * + * @throws Exception the exception + */ + protected ActiveInventoryConfig() throws Exception { + + Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); + aaiRestConfig = new ActiveInventoryRestConfig(props); + aaiSslConfig = new ActiveInventorySslConfig(props, new Encryptor()); + + taskProcessorConfig = new TaskProcessorConfig(); + taskProcessorConfig + .initializeFromProperties(ConfigHelper.getConfigWithPrefix("aai.taskProcessor", props)); + + + } + + protected ActiveInventoryConfig(Properties props) throws Exception { + + aaiRestConfig = new ActiveInventoryRestConfig(props); + aaiSslConfig = new ActiveInventorySslConfig(props, new Encryptor()); + + taskProcessorConfig = new TaskProcessorConfig(); + taskProcessorConfig + .initializeFromProperties(ConfigHelper.getConfigWithPrefix("aai.taskProcessor", props)); + + + } + + public TaskProcessorConfig getTaskProcessorConfig() { + return taskProcessorConfig; + } + + public void setTaskProcessorConfig(TaskProcessorConfig taskProcessorConfig) { + this.taskProcessorConfig = taskProcessorConfig; + } + + + public ActiveInventoryRestConfig getAaiRestConfig() { + return aaiRestConfig; + } + + public void setAaiRestConfig(ActiveInventoryRestConfig aaiRestConfig) { + this.aaiRestConfig = aaiRestConfig; + } + + public ActiveInventorySslConfig getAaiSslConfig() { + return aaiSslConfig; + } + + public void setAaiSslConfig(ActiveInventorySslConfig aaiSslConfig) { + this.aaiSslConfig = aaiSslConfig; + } + + public String repairSelfLink(String selflink) { + + if (selflink == null) { + return selflink; + } + + UriBuilder builder = UriBuilder.fromPath(selflink).host(aaiRestConfig.getHost()) + .port(Integer.parseInt(aaiRestConfig.getPort())); + + switch (aaiRestConfig.getAuthenticationMode()) { + + case SSL_BASIC: + case SSL_CERT: { + builder.scheme(HTTPS_SCHEME); + break; + } + + default: { + builder.scheme(HTTP_SCHEME); + } + } + + return builder.build().toString(); + + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ActiveInventoryConfig [aaiRestConfig=" + aaiRestConfig + ", aaiSslConfig=" + + aaiSslConfig + "]"; + } + + public URI getBaseUri() { + return UriBuilder.fromUri("https://" + aaiRestConfig.getHost() + ":" + aaiRestConfig.getPort() + + aaiRestConfig.getResourceBasePath()).build(); + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryRestConfig.java b/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryRestConfig.java new file mode 100644 index 0000000..d609f16 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryRestConfig.java @@ -0,0 +1,283 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.aai.config; + +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import org.openecomp.sparky.dal.aai.enums.RestAuthenticationMode; +import org.openecomp.sparky.util.ConfigHelper; + +/** + * The Class ActiveInventoryRestConfig. + */ +public class ActiveInventoryRestConfig { + + private String host; + + private String port; + + private int connectTimeoutInMs; + + private int readTimeoutInMs; + + private int numRequestRetries; + + private int numResolverWorkers; + + private boolean useCacheOnly; + + private boolean cacheEnabled; + + private boolean cacheFailures; + + private String storageFolderOverride; + + int numCacheWorkers; + + private long maxTimeToLiveInMs; + + private String resourceBasePath; + + private List shallowEntities; + + private RestAuthenticationMode authenticationMode; + + public List getShallowEntities() { + return shallowEntities; + } + + /** + * Instantiates a new active inventory rest config. + * + * @param props the props + */ + public ActiveInventoryRestConfig(Properties props) { + + if (props == null) { + return; + } + + Properties restProps = ConfigHelper.getConfigWithPrefix("aai.rest", props); + + resourceBasePath = restProps.getProperty("resourceBasePath", "/aai/v7"); + host = restProps.getProperty("host", "localhost"); + port = restProps.getProperty("port", "8443"); + numRequestRetries = Integer.parseInt(restProps.getProperty("numRequestRetries", "5")); + numResolverWorkers = Integer.parseInt(restProps.getProperty("numResolverWorkers", "15")); + + connectTimeoutInMs = Integer.parseInt(restProps.getProperty("connectTimeoutInMs", "5000")); + readTimeoutInMs = Integer.parseInt(restProps.getProperty("readTimeoutInMs", "10000")); + + String shallowEntitiesProperty = restProps.getProperty("shallowEntities", ""); + shallowEntities = Arrays.asList(shallowEntitiesProperty.split(",")); + + Properties cacheProps = ConfigHelper.getConfigWithPrefix("aai.rest.cache", props); + cacheEnabled = Boolean.parseBoolean(cacheProps.getProperty("enabled", "false")); + storageFolderOverride = cacheProps.getProperty("storageFolderOverride", null); + cacheFailures = Boolean.parseBoolean(cacheProps.getProperty("cacheFailures", "false")); + useCacheOnly = Boolean.parseBoolean(cacheProps.getProperty("useCacheOnly", "false")); + numCacheWorkers = Integer.parseInt(cacheProps.getProperty("numWorkers", "5")); + + + if (storageFolderOverride != null && storageFolderOverride.length() == 0) { + storageFolderOverride = null; + } + /* + * The expectation of this parameter is that if the value > 0, then the cached resources will be + * served back instead of dipping AAI/DataLayer as long as the current resource age from the + * cached instance is < maxTimeToLiveInMs. + */ + maxTimeToLiveInMs = Long.parseLong(cacheProps.getProperty("maxTimeToLiveInMs", "-1")); + authenticationMode = RestAuthenticationMode.getRestAuthenticationMode(restProps.getProperty("authenticationMode", RestAuthenticationMode.SSL_CERT.getAuthenticationModeLabel())); + + /* + * In any kind of error scenario, set the authentication mode to SSL_CERT as our default. + * This is an arbitrary default, but was chosen based on the way this code worked before + * introduction of the SSL Basic Auth settings. + */ + if ( authenticationMode == RestAuthenticationMode.UNKNOWN_MODE) { + authenticationMode = RestAuthenticationMode.SSL_CERT; + } + + } + + public RestAuthenticationMode getAuthenticationMode() { + return authenticationMode; + } + + public void setAuthenticationMode(RestAuthenticationMode authenticationMode) { + this.authenticationMode = authenticationMode; + } + + public int getNumCacheWorkers() { + return numCacheWorkers; + } + + public void setNumCacheWorkers(int numCacheWorkers) { + this.numCacheWorkers = numCacheWorkers; + } + + /** + * Should cache failures. + * + * @return true, if successful + */ + public boolean shouldCacheFailures() { + return cacheFailures; + } + + public void setShouldCacheFailures(boolean enabled) { + this.cacheFailures = enabled; + } + + /** + * Checks if is shallow entity. + * + * @param entityType the entity type + * @return true, if is shallow entity + */ + public boolean isShallowEntity(String entityType) { + if (entityType == null) { + return false; + } + + for (String entity : shallowEntities) { + if (entityType.equalsIgnoreCase(entity)) { + return true; + } + } + + return false; + } + + public boolean isUseCacheOnly() { + return useCacheOnly; + } + + public void setUseCacheOnly(boolean useCacheOnly) { + this.useCacheOnly = useCacheOnly; + } + + public int getNumResolverWorkers() { + return numResolverWorkers; + } + + public void setNumResolverWorkers(int numResolverWorkers) { + this.numResolverWorkers = numResolverWorkers; + } + + public long getMaxTimeToLiveInMs() { + return maxTimeToLiveInMs; + } + + public void setMaxTimeToLiveInMs(long maxTimeToLiveInMs) { + this.maxTimeToLiveInMs = maxTimeToLiveInMs; + } + + public boolean isCacheEnabled() { + return cacheEnabled; + } + + public void setCacheEnabled(boolean cacheEnabled) { + this.cacheEnabled = cacheEnabled; + } + + public String getStorageFolderOverride() { + return storageFolderOverride; + } + + public void setStorageFolderOverride(String storageFolderOverride) { + this.storageFolderOverride = storageFolderOverride; + } + + public String getHost() { + return host; + } + + public String getPort() { + return port; + } + + public String getResourceBasePath() { + return resourceBasePath; + } + + public void setHost(String host) { + this.host = host; + } + + public void setPort(String port) { + this.port = port; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + + + public void setResourceBasePath(String resourceBasePath) { + this.resourceBasePath = resourceBasePath; + } + + @Override + public String toString() { + return "ActiveInventoryRestConfig [host=" + host + ", port=" + port + ", connectTimeoutInMs=" + + connectTimeoutInMs + ", readTimeoutInMs=" + readTimeoutInMs + ", numRequestRetries=" + + numRequestRetries + ", numResolverWorkers=" + numResolverWorkers + ", useCacheOnly=" + + useCacheOnly + ", cacheEnabled=" + cacheEnabled + ", cacheFailures=" + cacheFailures + + ", storageFolderOverride=" + storageFolderOverride + ", numCacheWorkers=" + + numCacheWorkers + ", maxTimeToLiveInMs=" + maxTimeToLiveInMs + ", resourceBasePath=" + + resourceBasePath + ", shallowEntities=" + shallowEntities + ", authenticationMode=" + + authenticationMode + "]"; + } + + public int getConnectTimeoutInMs() { + return connectTimeoutInMs; + } + + public void setConnectTimeoutInMs(int connectTimeoutInMs) { + this.connectTimeoutInMs = connectTimeoutInMs; + } + + public int getReadTimeoutInMs() { + return readTimeoutInMs; + } + + public void setReadTimeoutInMs(int readTimeoutInMs) { + this.readTimeoutInMs = readTimeoutInMs; + } + + public int getNumRequestRetries() { + return numRequestRetries; + } + + public void setNumRequestRetries(int numRequestRetries) { + this.numRequestRetries = numRequestRetries; + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventorySslConfig.java b/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventorySslConfig.java new file mode 100644 index 0000000..272e351 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/aai/config/ActiveInventorySslConfig.java @@ -0,0 +1,217 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.aai.config; + +import java.util.Properties; + +import org.eclipse.jetty.util.security.Password; +import org.openecomp.sparky.util.ConfigHelper; +import org.openecomp.sparky.util.Encryptor; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +/** + * The Class ActiveInventorySslConfig. + */ +public class ActiveInventorySslConfig { + + private Encryptor encryptor; + + private boolean enableSslDebug; + private boolean validateServerHostName; + private boolean validateServerCertificateChain; + + private String keystoreType; + private String keystoreFilename; + private String keystorePassword; + private String truststoreType; + private String truststoreFilename; + + private String basicAuthUsername; + private String basicAuthPassword; + + /** + * Instantiates a new active inventory ssl config. + * + * @param props the props + */ + public ActiveInventorySslConfig(Properties props, Encryptor encryptor) { + + if (props == null) { + return; + } + + Properties sslProps = ConfigHelper.getConfigWithPrefix("aai.ssl", props); + + enableSslDebug = Boolean.parseBoolean(sslProps.getProperty("enableDebug", "false")); + validateServerHostName = + Boolean.parseBoolean(sslProps.getProperty("validateServerHostName", "false")); + validateServerCertificateChain = + Boolean.parseBoolean(sslProps.getProperty("validateServerCertificateChain", "false")); + + if (enableSslDebug) { + System.setProperty("javax.net.debug", "ssl"); + } else { + System.setProperty("javax.net.debug", ""); + } + + this.encryptor = encryptor; + + + keystoreType = sslProps.getProperty("keystore.type", "pkcs12"); + + keystoreFilename = + TierSupportUiConstants.CONFIG_AUTH_LOCATION + sslProps.getProperty("keystore.filename"); + keystorePassword = encryptor.decryptValue(sslProps.getProperty("keystore.pass", "")); + truststoreType = sslProps.getProperty("truststore.type", "jks"); + + truststoreFilename = + TierSupportUiConstants.CONFIG_AUTH_LOCATION + sslProps.getProperty("truststore.filename"); + + basicAuthUsername = sslProps.getProperty("basicAuth.username"); + basicAuthPassword = decryptPassword(sslProps.getProperty("basicAuth.password")); + + } + + private String decryptPassword(String encryptedPassword) { + + try { + + if (encryptedPassword == null) { + return null; + } + + return Password.deobfuscate(encryptedPassword); + + } catch (Exception exc) { + + return encryptedPassword; + + } + + } + + public String getBasicAuthUsername() { + return basicAuthUsername; + } + + public void setBasicAuthUsername(String basicAuthUsername) { + this.basicAuthUsername = basicAuthUsername; + } + + public String getBasicAuthPassword() { + return basicAuthPassword; + } + + public void setBasicAuthPassword(String basicAuthPassword) { + this.basicAuthPassword = basicAuthPassword; + } + + + public Encryptor getEncryptor() { + return encryptor; + } + + public void setEncryptor(Encryptor encryptor) { + this.encryptor = encryptor; + } + + public String getKeystoreType() { + return keystoreType; + } + + public void setKeystoreType(String keystoreType) { + this.keystoreType = keystoreType; + } + + public String getKeystoreFilename() { + return keystoreFilename; + } + + public void setKeystoreFilename(String keystoreFilename) { + this.keystoreFilename = keystoreFilename; + } + + public String getKeystorePassword() { + return keystorePassword; + } + + public void setKeystorePassword(String keystorePassword) { + this.keystorePassword = keystorePassword; + } + + public String getTruststoreType() { + return truststoreType; + } + + public void setTruststoreType(String truststoreType) { + this.truststoreType = truststoreType; + } + + public String getTruststoreFilename() { + return truststoreFilename; + } + + public void setTruststoreFilename(String truststoreFilename) { + this.truststoreFilename = truststoreFilename; + } + + public boolean isValidateServerHostName() { + return validateServerHostName; + } + + public void setValidateServerHostName(boolean validateServerHostName) { + this.validateServerHostName = validateServerHostName; + } + + public boolean isValidateServerCertificateChain() { + return validateServerCertificateChain; + } + + public void setValidateServerCertificateChain(boolean validateServerCertificateChain) { + this.validateServerCertificateChain = validateServerCertificateChain; + } + + public String getBasicAuthenticationCredentials() { + + String usernameAndPassword = getBasicAuthUsername() + ":" + + getBasicAuthPassword(); + return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes()); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ActiveInventorySslConfig [enableSslDebug=" + enableSslDebug + + ", validateServerHostName=" + validateServerHostName + ", validateServerCertificateChain=" + + validateServerCertificateChain + ", keystoreType=" + keystoreType + ", keystoreFilename=" + + keystoreFilename + ", truststoreType=" + truststoreType + ", truststoreFilename=" + + truststoreFilename + "]"; + } + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/aai/enums/RestAuthenticationMode.java b/src/main/java/org/openecomp/sparky/dal/aai/enums/RestAuthenticationMode.java new file mode 100644 index 0000000..af2f884 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/aai/enums/RestAuthenticationMode.java @@ -0,0 +1,69 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.aai.enums; + +/** + * Authentication Modes: + *
  • HTTP_NOAUTH - intended to represent basic HTTP no authentication + *
  • SSL_BASIC - HTTP/S with username/password + *
  • SSL_CERT - HTTP/S with client cert + */ + +public enum RestAuthenticationMode { + HTTP_NOAUTH("HTTP_NO_AUTH"), + SSL_BASIC("SSL_BASIC"), + SSL_CERT("SSL_CERT"), + UNKNOWN_MODE("UNKNOWN_MODE"); + + private String authenticationModeLabel; + + private RestAuthenticationMode(String authModelLabel) { + this.authenticationModeLabel = authModelLabel; + } + + public String getAuthenticationModeLabel() { + return authenticationModeLabel; + } + + public static RestAuthenticationMode getRestAuthenticationMode(String authenticationMode) { + + RestAuthenticationMode mappedMode = RestAuthenticationMode.UNKNOWN_MODE; + + if (authenticationMode == null) { + return mappedMode; + } + + try { + mappedMode = RestAuthenticationMode.valueOf(authenticationMode); + } catch ( Exception exc) { + mappedMode = RestAuthenticationMode.UNKNOWN_MODE; + } + + return mappedMode; + + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/cache/EntityCache.java b/src/main/java/org/openecomp/sparky/dal/cache/EntityCache.java new file mode 100644 index 0000000..ccecc2d --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/cache/EntityCache.java @@ -0,0 +1,63 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.cache; + +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Interface EntityCache. + * + * @author davea. + */ +public interface EntityCache { + + /** + * Gets the. + * + * @param entityKey the entity key + * @param link the link + * @return the operation result + */ + public OperationResult get(String entityKey, String link); + + /** + * Put. + * + * @param entityKey the entity key + * @param result the result + */ + public void put(String entityKey, OperationResult result); + + /** + * Shutdown. + */ + public void shutdown(); + + /** + * Clear. + */ + public void clear(); +} diff --git a/src/main/java/org/openecomp/sparky/dal/cache/InMemoryEntityCache.java b/src/main/java/org/openecomp/sparky/dal/cache/InMemoryEntityCache.java new file mode 100644 index 0000000..68378db --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/cache/InMemoryEntityCache.java @@ -0,0 +1,101 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.cache; + +import java.util.concurrent.ConcurrentHashMap; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; + +/** + * The Class InMemoryEntityCache. + * + * @author davea. + */ +public class InMemoryEntityCache implements EntityCache { + + private ConcurrentHashMap cachedEntityData; + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(InMemoryEntityCache.class); + + /** + * Instantiates a new in memory entity cache. + */ + public InMemoryEntityCache() { + cachedEntityData = new ConcurrentHashMap(); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.cache.EntityCache#put(java.lang.String, org.openecomp.sparky.dal.rest.OperationResult) + */ + @Override + public void put(String key, OperationResult data) { + if (data == null) { + return; + } + + if (cachedEntityData.putIfAbsent(key, data) != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DATA_CACHE_SUCCESS, key); + } + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.cache.EntityCache#get(java.lang.String, java.lang.String) + */ + @Override + public OperationResult get(String entityKey, String link) { + + if (link != null) { + return cachedEntityData.get(link); + } + + return null; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.cache.EntityCache#shutdown() + */ + @Override + public void shutdown() { + // TODO Auto-generated method stub + // nothing to do + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.cache.EntityCache#clear() + */ + @Override + public void clear() { + cachedEntityData.clear(); + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/cache/PersistentEntityCache.java b/src/main/java/org/openecomp/sparky/dal/cache/PersistentEntityCache.java new file mode 100644 index 0000000..1456b05 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/cache/PersistentEntityCache.java @@ -0,0 +1,305 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.cache; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.aai.ActiveInventoryAdapter; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.task.PersistOperationResultToDisk; +import org.openecomp.sparky.synchronizer.task.RetrieveOperationResultFromDisk; +import org.openecomp.sparky.util.NodeUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The Class PersistentEntityCache. + */ +public class PersistentEntityCache implements EntityCache { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(ActiveInventoryAdapter.class); + + /* + * TODO:
  • implement time-to-live on the cache, maybe pull in one of Guava's eviction caches? + *
  • implement abstract-base-cache to hold common cach-y things (like ttl) + */ + + private static final String DEFAULT_OUTPUT_PATH = "offlineEntityCache"; + private ExecutorService persistentExecutor; + private ObjectMapper mapper; + private String storagePath; + + /** + * Instantiates a new persistent entity cache. + */ + public PersistentEntityCache() { + this(null, 10); + } + + /** + * Instantiates a new persistent entity cache. + * + * @param numWorkers the num workers + */ + public PersistentEntityCache(int numWorkers) { + this(null, numWorkers); + } + + /** + * Instantiates a new persistent entity cache. + * + * @param storageFolderOverride the storage folder override + * @param numWorkers the num workers + */ + public PersistentEntityCache(String storageFolderOverride, int numWorkers) { + persistentExecutor = NodeUtils.createNamedExecutor("PEC", numWorkers, LOG); + mapper = new ObjectMapper(); + + if (storageFolderOverride != null && storageFolderOverride.length() > 0) { + this.storagePath = storageFolderOverride; + } else { + this.storagePath = DEFAULT_OUTPUT_PATH; + } + } + + /** + * Generate offline storage path from uri. + * + * @param link the link + * @return the string + */ + private String generateOfflineStoragePathFromUri(String link) { + + try { + URI uri = new URI(link); + + String modHost = uri.getHost().replace(".", "_"); + + String[] tokens = uri.getPath().split("\\/"); + List resourcePathAndDomain = new ArrayList(); + + if (tokens.length >= 4) { + + int numElements = 0; + for (String w : tokens) { + + if (numElements > 3) { + break; + } + + if (w.length() > 0) { + resourcePathAndDomain.add(w); + numElements++; + } + + } + } else { + return this.storagePath + "\\"; + } + + return this.storagePath + "\\" + modHost + "\\" + + NodeUtils.concatArray(resourcePathAndDomain, "_") + "\\"; + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.OFFLINE_STORAGE_PATH_ERROR, link, exc.getMessage()); + } + + return this.storagePath + "\\"; + + } + + /** + * Creates the dirs. + * + * @param directoryPath the directory path + */ + private void createDirs(String directoryPath) { + if (directoryPath == null) { + return; + } + + Path path = Paths.get(directoryPath); + // if directory exists? + if (!Files.exists(path)) { + try { + Files.createDirectories(path); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.DISK_CREATE_DIR_IO_ERROR, exc.getMessage()); + } + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.cache.EntityCache#get(java.lang.String, java.lang.String) + */ + @Override + public OperationResult get(String key, String link) { + + final String storagePath = generateOfflineStoragePathFromUri(link); + createDirs(storagePath); + final String persistentFileName = storagePath + "\\" + key + ".json"; + + CompletableFuture task = supplyAsync( + new RetrieveOperationResultFromDisk(persistentFileName, mapper, LOG), persistentExecutor); + + try { + /* + * this will do a blocking get, but it will be blocking only on the thread that executed this + * method which should be one of the persistentWorker threads from the executor. + */ + return task.get(); + } catch (InterruptedException | ExecutionException exc) { + // TODO Auto-generated catch block + LOG.error(AaiUiMsgs.DISK_NAMED_DATA_READ_IO_ERROR, "txn", exc.getMessage()); + } + + return null; + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.cache.EntityCache#put(java.lang.String, org.openecomp.sparky.dal.rest.OperationResult) + */ + @Override + public void put(String key, OperationResult data) { + + final String storagePath = generateOfflineStoragePathFromUri(data.getRequestLink()); + createDirs(storagePath); + final String persistentFileName = storagePath + "\\" + key + ".json"; + + Path persistentFilePath = Paths.get(persistentFileName); + + if (!Files.exists(persistentFilePath, LinkOption.NOFOLLOW_LINKS)) { + + supplyAsync(new PersistOperationResultToDisk(persistentFileName, data, mapper, LOG), + persistentExecutor).whenComplete((opResult, error) -> { + + if (error != null) { + LOG.error(AaiUiMsgs.DISK_DATA_WRITE_IO_ERROR, "entity", error.getMessage()); + } + + }); + } + + } + + /** + * The main method. + * + * @param args the arguments + * @throws URISyntaxException the URI syntax exception + */ + public static void main(String[] args) throws URISyntaxException { + + OperationResult or = new OperationResult(); + or.setResult("asdjashdkajsdhaksdj"); + or.setResultCode(200); + + String url1 = "https://aai-int1.dev.att.com:8443/aai/v8/search/nodes-query?" + + "search-node-type=tenant&filter=tenant-id:EXISTS"; + + or.setRequestLink(url1); + + PersistentEntityCache pec = new PersistentEntityCache("e:\\my_special_folder", 5); + String k1 = NodeUtils.generateUniqueShaDigest(url1); + pec.put(k1, or); + + String url2 = + "https://aai-int1.dev.att.com:8443/aai/v8/network/vnfcs/vnfc/trial-vnfc?nodes-only"; + or.setRequestLink(url2); + String k2 = NodeUtils.generateUniqueShaDigest(url2); + pec.put(k2, or); + + String url3 = "https://1.2.3.4:8443/aai/v8/network/vnfcs/vnfc/trial-vnfc?nodes-only"; + or.setRequestLink(url3); + String k3 = NodeUtils.generateUniqueShaDigest(url3); + pec.put(k3, or); + + pec.shutdown(); + + /* + * URI uri1 = new URI(url1); + * + * System.out.println("schemea = " + uri1.getScheme()); System.out.println("host = " + + * uri1.getHost()); + * + * String host = uri1.getHost(); String[] tokens = host.split("\\."); + * System.out.println(Arrays.asList(tokens)); ArrayList tokenList = new + * ArrayList(Arrays.asList(tokens)); //tokenList.remove(tokens.length-1); String + * hostAsPathElement = NodeUtils.concatArray(tokenList, "_"); + * + * System.out.println("hostAsPathElement = " + hostAsPathElement); + * + * + * System.out.println("port = " + uri1.getPort()); System.out.println("path = " + + * uri1.getPath()); System.out.println("query = " + uri1.getQuery()); System.out.println( + * "fragment = " + uri1.getFragment()); + */ + + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.cache.EntityCache#shutdown() + */ + @Override + public void shutdown() { + if (persistentExecutor != null) { + persistentExecutor.shutdown(); + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.cache.EntityCache#clear() + */ + @Override + public void clear() { + /* + * do nothing for this one, as it is not clear if we we really want to clear on the on-disk + * cache or not + */ + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchAdapter.java b/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchAdapter.java new file mode 100644 index 0000000..f2df3ab --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchAdapter.java @@ -0,0 +1,165 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.elasticsearch; + +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestDataProvider; +import org.openecomp.sparky.dal.rest.RestfulDataAccessor; + +/** + * The Class ElasticSearchAdapter. + * + * @author davea. + */ +public class ElasticSearchAdapter implements ElasticSearchDataProvider { + + private static final String BULK_IMPORT_INDEX_TEMPLATE = + "{\"index\":{\"_index\":\"%s\",\"_type\":\"%s\",\"_id\":\"%s\", \"_version\":\"%s\"}}\n"; + + private final RestDataProvider restDataProvider; + private final ElasticSearchConfig esConfig; + + /** + * Instantiates a new elastic search adapter. + * + * @param provider the provider + */ + public ElasticSearchAdapter(RestDataProvider provider, ElasticSearchConfig esConfig) { + this.restDataProvider = provider; + this.esConfig = esConfig; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doGet(java.lang.String, java.lang.String) + */ + @Override + public OperationResult doGet(String url, String acceptContentType) { + return restDataProvider.doGet(url, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doDelete(java.lang.String, java.lang.String) + */ + @Override + public OperationResult doDelete(String url, String acceptContentType) { + return restDataProvider.doDelete(url, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPost(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult doPost(String url, String jsonPayload, String acceptContentType) { + return restDataProvider.doPost(url, jsonPayload, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPut(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult doPut(String url, String jsonPayload, String acceptContentType) { + return restDataProvider.doPut(url, jsonPayload, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPatch(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult doPatch(String url, String jsonPayload, String acceptContentType) { + return restDataProvider.doPatch(url, jsonPayload, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doHead(java.lang.String, java.lang.String) + */ + @Override + public OperationResult doHead(String url, String acceptContentType) { + return restDataProvider.doHead(url, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#clearCache() + */ + @Override + public void clearCache() { + restDataProvider.clearCache(); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.elasticsearch.ElasticSearchDataProvider#doBulkOperation(java.lang.String, java.lang.String) + */ + @Override + public OperationResult doBulkOperation(String url, String payload) { + + return doRestfulOperation(HttpMethod.PUT, url, payload, + RestfulDataAccessor.APPLICATION_X_WWW_FORM_URL_ENCODED, + RestfulDataAccessor.APPLICATION_JSON); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.elasticsearch.ElasticSearchDataProvider#shutdown() + */ + @Override + public void shutdown() { + restDataProvider.shutdown(); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doRestfulOperation(org.openecomp.sparky.dal.rest.HttpMethod, java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult doRestfulOperation(HttpMethod method, String url, String payload, + String payloadType, String acceptContentType) { + return restDataProvider.doRestfulOperation(method, url, payload, payloadType, + acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.elasticsearch.ElasticSearchDataProvider#buildBulkImportOperationRequest(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public String buildBulkImportOperationRequest(String index, String type, String id, + String version, String payload) { + + StringBuilder requestPayload = new StringBuilder(128); + + requestPayload.append(String.format(BULK_IMPORT_INDEX_TEMPLATE, index, type, id, version)); + requestPayload.append(payload).append("\n"); + + return requestPayload.toString(); + + } + + @Override + public OperationResult retrieveEntityById(String entityId) throws Exception { + + String url = esConfig.getElasticFullUrl("/" +entityId); + return doGet( url, "application/json"); + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchDataProvider.java b/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchDataProvider.java new file mode 100644 index 0000000..97c4f4d --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchDataProvider.java @@ -0,0 +1,66 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.elasticsearch; + +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestDataProvider; + +/** + * The Interface ElasticSearchDataProvider. + */ +public interface ElasticSearchDataProvider extends RestDataProvider { + + /** + * Builds the bulk import operation request. + * + * @param index the index + * @param type the type + * @param id the id + * @param version the version + * @param payload the payload + * @return the string + */ + String buildBulkImportOperationRequest(String index, String type, String id, String version, + String payload); + + /** + * Do bulk operation. + * + * @param url the url + * @param payload the payload + * @return the operation result + */ + OperationResult doBulkOperation(String url, String payload); + + OperationResult retrieveEntityById(String entityId) throws Exception; + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#shutdown() + */ + @Override + void shutdown(); + +} diff --git a/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchEntityStatistics.java b/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchEntityStatistics.java new file mode 100644 index 0000000..6194027 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchEntityStatistics.java @@ -0,0 +1,274 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.elasticsearch; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class ElasticSearchEntityStatistics. + */ +public class ElasticSearchEntityStatistics { + + private static final String TOTAL = "Total"; + private static final String CREATED = "Created"; + private static final String MODIFIED = "Modified"; + private static final String OTHERSUCCESS = "OTHERSUCCESS"; + private static final String DELETED = "DELETED"; + private static final String ERROR = "ERROR"; + + private Map> entityStatistics; + private OxmModelLoader loader; + + /** + * Creates the entity op stats. + * + * @return the hash map + */ + private HashMap createEntityOpStats() { + + HashMap opStats = new HashMap(); + + opStats.put(TOTAL, new AtomicInteger()); + opStats.put(CREATED, new AtomicInteger()); + opStats.put(MODIFIED, new AtomicInteger()); + opStats.put(OTHERSUCCESS, new AtomicInteger()); + opStats.put(DELETED, new AtomicInteger()); + opStats.put(ERROR, new AtomicInteger()); + + return opStats; + + } + + /* + * private void createActiveInventoryEntityStatistics() { + * + * Map descriptors = loader.getSearchableEntityDescriptors(); + * + * if(descriptors == null) { return; } + * + * OxmEntityDescriptor d = null; for ( String key : descriptors.keySet() ) { d = + * descriptors.get(key); entityStatistics.put(d.getEntityName(), createEntityOpStats()); } + * + * } + */ + + /** + * Initializecreate active inventory entity statistics. + */ + private void initializecreateActiveInventoryEntityStatistics() { + Set keys = entityStatistics.keySet(); + + Set opStatKeySet = null; + Map opStats = null; + + for (String k : keys) { + + opStats = entityStatistics.get(k); + + opStatKeySet = opStats.keySet(); + + for (String opStatKey : opStatKeySet) { + opStats.get(opStatKey).set(0); + } + } + } + + /** + * Instantiates a new elastic search entity statistics. + * + * @param loader the loader + */ + public ElasticSearchEntityStatistics(OxmModelLoader loader) { + this.loader = loader; + entityStatistics = new HashMap>(); + // createActiveInventoryEntityStatistics(); + reset(); + } + + /** + * Initialize counters from oxm entity descriptors. + * + * @param descriptors the descriptors + */ + public void initializeCountersFromOxmEntityDescriptors( + Map descriptors) { + + if (descriptors == null) { + return; + } + + OxmEntityDescriptor descriptor = null; + for (String key : descriptors.keySet()) { + descriptor = descriptors.get(key); + entityStatistics.put(descriptor.getEntityName(), createEntityOpStats()); + } + } + + /** + * Reset. + */ + public void reset() { + initializecreateActiveInventoryEntityStatistics(); + } + + /** + * Gets the result code. + * + * @param txn the txn + * @return the result code + */ + private int getResultCode(NetworkTransaction txn) { + + + if (txn == null) { + return -1; + } + + OperationResult or = txn.getOperationResult(); + + if (or == null) { + return -1; + } + + return or.getResultCode(); + + } + + /** + * Update elastic search entity counters. + * + * @param txn the txn + */ + private void updateElasticSearchEntityCounters(NetworkTransaction txn) { + + if (txn == null) { + return; + } + + Map entityOpStats = entityStatistics.get(txn.getEntityType()); + + int resultCode = getResultCode(txn); + + if (txn.getOperationType() == HttpMethod.PUT) { + + entityOpStats.get(TOTAL).incrementAndGet(); + + if (resultCode == 201) { + entityOpStats.get(CREATED).incrementAndGet(); + } else if (resultCode == 200) { + entityOpStats.get(MODIFIED).incrementAndGet(); + } else if (202 <= resultCode && resultCode <= 299) { + entityOpStats.get(OTHERSUCCESS).incrementAndGet(); + } else { + entityOpStats.get(ERROR).incrementAndGet(); + } + + } else if (txn.getOperationType() == HttpMethod.DELETE) { + + entityOpStats.get(TOTAL).incrementAndGet(); + + if (200 <= resultCode && resultCode <= 299) { + entityOpStats.get(DELETED).incrementAndGet(); + } else { + entityOpStats.get(ERROR).incrementAndGet(); + } + } + + } + + /** + * Update counters. + * + * @param txn the txn + */ + public void updateCounters(NetworkTransaction txn) { + + updateElasticSearchEntityCounters(txn); + + } + + public String getStatisticsReport() { + + StringBuilder sb = new StringBuilder(128); + + /* + * sort entities, then sort nested op codes + */ + + TreeMap> elasticEntitySortedTreeMap = + new TreeMap>(new Comparator() { + + @Override + public int compare(String o1, String o2) { + return o1.toLowerCase().compareTo(o2.toLowerCase()); + } + }); + + elasticEntitySortedTreeMap.putAll(entityStatistics); + + for (String counterEntityKey : elasticEntitySortedTreeMap.keySet()) { + + HashMap entityCounters = + elasticEntitySortedTreeMap.get(counterEntityKey); + + AtomicInteger total = entityCounters.get(TOTAL); + AtomicInteger created = entityCounters.get(CREATED); + AtomicInteger modified = entityCounters.get(MODIFIED); + AtomicInteger otherSuccess = entityCounters.get(OTHERSUCCESS); + AtomicInteger deleted = entityCounters.get(DELETED); + AtomicInteger error = entityCounters.get(ERROR); + + int totalValue = (total == null) ? 0 : total.get(); + int createdValue = (created == null) ? 0 : created.get(); + int modifiedValue = (modified == null) ? 0 : modified.get(); + int otherSuccessValue = (otherSuccess == null) ? 0 : otherSuccess.get(); + int deletedValue = (deleted == null) ? 0 : deleted.get(); + int errorValue = (error == null) ? 0 : error.get(); + + sb.append("\n ") + .append(String.format( + "%-30s TOTAL: %-12d CREATED: %-12d MODIFIED:" + + " %-12d OTHER_2XX: %-12d DELETED: %-12d ERROR: %-12d", + counterEntityKey, totalValue, createdValue, modifiedValue, otherSuccessValue, + deletedValue, errorValue)); + } + return sb.toString(); + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/elasticsearch/HashQueryResponse.java b/src/main/java/org/openecomp/sparky/dal/elasticsearch/HashQueryResponse.java new file mode 100644 index 0000000..a376add --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/elasticsearch/HashQueryResponse.java @@ -0,0 +1,59 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.dal.elasticsearch; + +import org.json.JSONObject; +import org.openecomp.sparky.dal.rest.OperationResult; + +public class HashQueryResponse { + private String jsonPayload = null; + private OperationResult opResult = null; + + public HashQueryResponse() { + this(null, null); + } + + public HashQueryResponse(String jsonPayload, OperationResult opResult) { + this.jsonPayload = jsonPayload; + this.opResult = opResult; + } + + public String getJsonPayload() { + return jsonPayload; + } + public void setJsonPayload(String jsonPayload) { + this.jsonPayload = jsonPayload; + } + public OperationResult getOpResult() { + return opResult; + } + public void setOpResult(OperationResult opResult) { + this.opResult = opResult; + } + @Override + public String toString() { + return "HashQueryResponse [jsonPayload=" + jsonPayload + ", opResult=" + opResult + "]"; + } +} diff --git a/src/main/java/org/openecomp/sparky/dal/elasticsearch/SearchAdapter.java b/src/main/java/org/openecomp/sparky/dal/elasticsearch/SearchAdapter.java new file mode 100644 index 0000000..9479a8f --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/elasticsearch/SearchAdapter.java @@ -0,0 +1,122 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.elasticsearch; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MediaType; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.sas.config.SearchServiceConfig; +import org.openecomp.sparky.util.Encryptor; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; +import org.slf4j.MDC; + +import org.openecomp.restclient.client.RestClient; +import org.openecomp.restclient.enums.RestAuthenticationMode; +import org.openecomp.restclient.client.Headers; +import org.openecomp.cl.mdc.MdcContext; + +import org.openecomp.cl.mdc.MdcContext; + +/** + * The Class SearchAdapter. + */ +public class SearchAdapter { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(SearchAdapter.class); + + private RestClient client; + + private Map> commonHeaders; + private SearchServiceConfig sasConfig; + + /** + * Instantiates a new search adapter. + * @throws Exception + */ + public SearchAdapter() throws Exception { + sasConfig = SearchServiceConfig.getConfig(); + Encryptor encryptor = new Encryptor(); + client = new RestClient().authenticationMode(RestAuthenticationMode.SSL_CERT) + .validateServerHostname(false).validateServerCertChain(false) + .clientCertFile(TierSupportUiConstants.CONFIG_AUTH_LOCATION + sasConfig.getCertName()) + .clientCertPassword(encryptor.decryptValue(sasConfig.getKeystorePassword())) + .trustStore(TierSupportUiConstants.CONFIG_AUTH_LOCATION + sasConfig.getKeystore()); + + commonHeaders = new HashMap>(); + commonHeaders.put("Accept", Arrays.asList("application/json")); + commonHeaders.put(Headers.FROM_APP_ID, Arrays.asList("AAI-UI")); + } + + public SearchServiceConfig getSasConfig() { + return sasConfig; + } + + public void setSasConfig(SearchServiceConfig sasConfig) { + this.sasConfig = sasConfig; + } + + public OperationResult doPost(String url, String jsonPayload, String acceptContentType) { + org.openecomp.restclient.client.OperationResult or = client.post(url, jsonPayload, getTxnHeader(), + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + return new OperationResult(or.getResultCode(), or.getResult()); + } + + public OperationResult doGet(String url, String acceptContentType) { + org.openecomp.restclient.client.OperationResult or = + client.get(url, getTxnHeader(), MediaType.APPLICATION_JSON_TYPE); + return new OperationResult(or.getResultCode(), or.getResult()); + } + + public OperationResult doPut(String url, String payload, String acceptContentType) { + org.openecomp.restclient.client.OperationResult or = client.put(url, payload, getTxnHeader(), + MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + return new OperationResult(or.getResultCode(), or.getResult()); + } + + public OperationResult doDelete(String url, String acceptContentType) { + + org.openecomp.restclient.client.OperationResult or = + client.delete(url, getTxnHeader(), MediaType.APPLICATION_JSON_TYPE); + return new OperationResult(or.getResultCode(), or.getResult()); + } + + public Map> getTxnHeader() { + Map headers = new HashMap>(); + headers.putAll(this.commonHeaders); + headers.put("X-TransactionId", Arrays.asList(MDC.get(MdcContext.MDC_REQUEST_ID))); + headers.put("X-FromAppId", Arrays.asList(MDC.get(MdcContext.MDC_PARTNER_NAME))); + return headers; + } + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/elasticsearch/config/ElasticSearchConfig.java b/src/main/java/org/openecomp/sparky/dal/elasticsearch/config/ElasticSearchConfig.java new file mode 100644 index 0000000..3f2cf7a --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/elasticsearch/config/ElasticSearchConfig.java @@ -0,0 +1,543 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.elasticsearch.config; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.Properties; + +import org.openecomp.sparky.dal.exception.ElasticSearchOperationException; +import org.openecomp.sparky.synchronizer.config.TaskProcessorConfig; +import org.openecomp.sparky.util.ConfigHelper; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + + +/** + * The Class ElasticSearchConfig. + */ +public class ElasticSearchConfig { + + public static final String CONFIG_FILE = + TierSupportUiConstants.DYNAMIC_CONFIG_APP_LOCATION + "elasticsearch.properties"; + + private static ElasticSearchConfig instance; + + private String ipAddress; + + private String httpPort; + + private String javaApiPort; + + private String indexName; + + private String type; + + private String clusterName; + + private String mappingsFileName; + + private String settingsFileName; + + private int syncAdapterMaxConcurrentWorkers; + + private String auditIndexName; + + private String topographicalSearchIndex; + + private String entityCountHistoryIndex; + + private String autosuggestIndexname; + + private String entityCountHistoryMappingsFileName; + + private String autoSuggestSettingsFileName; + + private String autoSuggestMappingsFileName; + + private String dynamicMappingsFileName; + + private static final String IP_ADDRESS_DEFAULT = "localhost"; + + private static final String HTTP_PORT_DEFAULT = "9200"; + + private static final String JAVA_API_PORT_DEFAULT = "9300"; + + private static final String TYPE_DEFAULT = "aaiEntities"; + + private static final String CLUSTER_NAME_DEFAULT = "elasticsearch"; + + private static final String INDEX_NAME_DEFAULT = "entitySearchIndex"; + + private static final String AUDIT_INDEX_NAME_DEFAULT = "auditdataindex"; + + private static final String TOPOGRAPHICAL_INDEX_NAME_DEFAULT = "topographicalSearchIndex"; + + private static final String ENTITY_COUNT_HISTORY_INDEX_NAME_DEFAULT = "entityCountHistory"; + + private static final String ENTITY_AUTO_SUGGEST_INDEX_NAME_DEFAULT = + TierSupportUiConstants.ENTITY_AUTO_SUGGEST_INDEX_NAME_DEFAULT; + + private static final String ENTITY_AUTO_SUGGEST_SETTINGS_FILE_DEFAULT = + TierSupportUiConstants.ENTITY_AUTO_SUGGEST_SETTINGS_FILE_DEFAULT; + + private static final String ENTITY_AUTO_SUGGEST_MAPPINGS_FILE_DEFAULT = + TierSupportUiConstants.ENTITY_AUTO_SUGGEST_SETTINGS_FILE_DEFAULT; + + private static final String ENTITY_DYNAMIC_MAPPINGS_FILE_DEFAULT = + TierSupportUiConstants.ENTITY_DYNAMIC_MAPPINGS_FILE_DEFAULT; + + private static final String BULK_API = "_bulk"; + + private TaskProcessorConfig processorConfig; + + public TaskProcessorConfig getProcessorConfig() { + return processorConfig; + } + + public void setProcessorConfig(TaskProcessorConfig processorConfig) { + this.processorConfig = processorConfig; + } + + public static ElasticSearchConfig getConfig() throws Exception { + + if (instance == null) { + instance = new ElasticSearchConfig(); + instance.initializeProperties(); + } + + return instance; + } + + public static void setConfig(ElasticSearchConfig config) { + /* + * Explicitly allow setting the configuration singleton. This will be useful for automation. + */ + + ElasticSearchConfig.instance = config; + } + + /** + * Instantiates a new elastic search config. + */ + public ElasticSearchConfig() { + // test method + } + + public String getElasticFullUrl(String resourceUrl, String indexName, String indexType) + throws Exception { + final String host = getIpAddress(); + final String port = getHttpPort(); + return String.format("http://%s:%s/%s/%s%s", host, port, indexName, indexType, resourceUrl); + } + + public String getElasticFullUrl(String resourceUrl, String indexName) throws Exception { + final String host = getIpAddress(); + final String port = getHttpPort(); + return String.format("http://%s:%s/%s/%s%s", host, port, indexName, + ElasticSearchConfig.getConfig().getType(), resourceUrl); + } + + public String getElasticFullUrl(String resourceUrl) throws Exception { + final String host = getIpAddress(); + final String port = getHttpPort(); + final String indexName = getIndexName(); + return String.format("http://%s:%s/%s/%s%s", host, port, indexName, getType(), resourceUrl); + } + + /** + * Initialize properties. + */ + private void initializeProperties() { + Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); + + ipAddress = props.getProperty("elasticsearch.ipAddress", IP_ADDRESS_DEFAULT); + httpPort = props.getProperty("elasticsearch.httpPort", "" + HTTP_PORT_DEFAULT); + javaApiPort = props.getProperty("elasticsearch.javaApiPort", "" + JAVA_API_PORT_DEFAULT); + type = props.getProperty("elasticsearch.type", TYPE_DEFAULT); + clusterName = props.getProperty("elasticsearch.clusterName", CLUSTER_NAME_DEFAULT); + indexName = props.getProperty("elasticsearch.indexName", INDEX_NAME_DEFAULT); + mappingsFileName = props.getProperty("elasticsearch.mappingsFileName"); + settingsFileName = props.getProperty("elasticsearch.settingsFileName"); + auditIndexName = props.getProperty("elasticsearch.auditIndexName", AUDIT_INDEX_NAME_DEFAULT); + topographicalSearchIndex = + props.getProperty("elasticsearch.topographicalIndexName", TOPOGRAPHICAL_INDEX_NAME_DEFAULT); + entityCountHistoryIndex = props.getProperty("elasticsearch.entityCountHistoryIndexName", + ENTITY_COUNT_HISTORY_INDEX_NAME_DEFAULT); + entityCountHistoryMappingsFileName = + props.getProperty("elasticsearch.entityCountHistoryMappingsFileName"); + + autosuggestIndexname = props.getProperty("elasticsearch.autosuggestIndexname", + ENTITY_AUTO_SUGGEST_INDEX_NAME_DEFAULT); + autoSuggestSettingsFileName = props.getProperty("elasticsearch.autosuggestSettingsFileName", + ENTITY_AUTO_SUGGEST_SETTINGS_FILE_DEFAULT); + autoSuggestMappingsFileName = props.getProperty("elasticsearch.autosuggestMappingsFileName", + ENTITY_AUTO_SUGGEST_MAPPINGS_FILE_DEFAULT); + dynamicMappingsFileName = props.getProperty("elasticsearch.dynamicMappingsFileName", + ENTITY_DYNAMIC_MAPPINGS_FILE_DEFAULT); + + syncAdapterMaxConcurrentWorkers = + Integer.parseInt(props.getProperty("elasticsearch.syncAdapter.maxConcurrentWorkers", "5")); + + processorConfig = new TaskProcessorConfig(); + processorConfig.initializeFromProperties( + ConfigHelper.getConfigWithPrefix("elasticsearch.taskProcessor", props)); + + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public String getHttpPort() { + return httpPort; + } + + public void setHttpPort(String httpPort) { + this.httpPort = httpPort; + } + + public String getJavaApiPort() { + return javaApiPort; + } + + public void setJavaApiPort(String javaApiPort) { + this.javaApiPort = javaApiPort; + } + + public String getIndexName() { + return indexName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public String getMappingsFileName() { + return mappingsFileName; + } + + public void setMappingsFileName(String mappingsFileName) { + this.mappingsFileName = mappingsFileName; + } + + public String getSettingsFileName() { + return settingsFileName; + } + + public int getSyncAdapterMaxConcurrentWorkers() { + return syncAdapterMaxConcurrentWorkers; + } + + public void setSyncAdapterMaxConcurrentWorkers(int syncAdapterMaxConcurrentWorkers) { + this.syncAdapterMaxConcurrentWorkers = syncAdapterMaxConcurrentWorkers; + } + + public void setSettingsFileName(String settingsFileName) { + this.settingsFileName = settingsFileName; + } + + public String getAuditIndexName() { + return auditIndexName; + } + + public void setAuditIndexName(String auditIndexName) { + this.auditIndexName = auditIndexName; + } + + public String getTopographicalSearchIndex() { + return topographicalSearchIndex; + } + + public void setTopographicalSearchIndex(String topographicalSearchIndex) { + this.topographicalSearchIndex = topographicalSearchIndex; + } + + public String getEntityCountHistoryIndex() { + return entityCountHistoryIndex; + } + + public void setEntityCountHistoryIndex(String entityCountHistoryIndex) { + this.entityCountHistoryIndex = entityCountHistoryIndex; + } + + + public String getEntityCountHistoryMappingsFileName() { + return entityCountHistoryMappingsFileName; + } + + public void setEntityCountHistoryMappingsFileName(String entityCountHistoryMappingsFileName) { + this.entityCountHistoryMappingsFileName = entityCountHistoryMappingsFileName; + } + + public String getBulkUrl() { + String url = this.getIpAddress(); + String port = this.getHttpPort(); + return String.format("http://%s:%s/%s", url, port, BULK_API); + } + + public String getConfigAsString(String configItem, String configFileName) + throws ElasticSearchOperationException { + String indexConfig = null; + + try { + indexConfig = ConfigHelper.getFileContents(configFileName); + } catch (IOException exc) { + throw new ElasticSearchOperationException( + "Failed to read index " + configItem + " from file = " + configFileName + ".", exc); + } + + if (indexConfig == null) { + throw new ElasticSearchOperationException( + "Failed to load index " + configItem + " with filename = " + configFileName + "."); + } + return indexConfig; + } + + public String getElasticSearchSettings() throws ElasticSearchOperationException { + return getConfigAsString("settings", + TierSupportUiConstants.getConfigPath(this.getSettingsFileName())); + } + + public String getDynamicMappings() throws ElasticSearchOperationException{ + return getConfigAsString("mapping", + TierSupportUiConstants.getConfigPath(this.getDynamicMappingsFileName())); + } + public String getElasticSearchMappings() throws ElasticSearchOperationException { + return getConfigAsString("mapping", + TierSupportUiConstants.getConfigPath(this.getMappingsFileName())); + } + + public String getElasticSearchEntityCountHistoryMappings() + throws ElasticSearchOperationException { + return getConfigAsString("mapping", + TierSupportUiConstants.getConfigPath(this.getEntityCountHistoryMappingsFileName())); + } + + public String getAutosuggestIndexSettings() throws ElasticSearchOperationException { + return getConfigAsString("setting", + TierSupportUiConstants.getConfigPath(this.getAutoSuggestSettingsFileName())); + } + + public String getAutosuggestIndexMappings() throws ElasticSearchOperationException { + return getConfigAsString("mapping", + TierSupportUiConstants.getConfigPath(this.getAutoSuggestMappingsFileName())); + } + + public String getAutosuggestIndexname() { + return autosuggestIndexname; + } + + public void setAutosuggestIndexname(String autosuggestIndexname) { + this.autosuggestIndexname = autosuggestIndexname; + } + + public String getAutoSuggestSettingsFileName() { + return autoSuggestSettingsFileName; + } + + public void setAutoSuggestSettingsFileName(String autoSuggestSettingsFileName) { + this.autoSuggestSettingsFileName = autoSuggestSettingsFileName; + } + + public String getAutoSuggestMappingsFileName() { + return autoSuggestMappingsFileName; + } + + public void setAutoSuggestMappingsFileName(String autoSuggestMappingsFileName) { + this.autoSuggestMappingsFileName = autoSuggestMappingsFileName; + } + + public String getDynamicMappingsFileName() { + return dynamicMappingsFileName; + } + + public void setDynamicMappingsFileName(String dynamicMappingsFileName) { + this.dynamicMappingsFileName = dynamicMappingsFileName; + } + + /** + * Builds the elastic search table config. + * + * @return the string + * @throws ElasticSearchOperationException the elastic search operation exception + */ + public String buildElasticSearchTableConfig() throws ElasticSearchOperationException { + + JsonNode esSettingsNode; + JsonNode esMappingsNodes; + ObjectMapper mapper = new ObjectMapper(); + + try { + esSettingsNode = mapper.readTree(getElasticSearchSettings()); + esMappingsNodes = mapper.readTree(getElasticSearchMappings()); + } catch (IOException e1) { + throw new ElasticSearchOperationException("Caught an exception building initial ES index"); + } + + ObjectNode esConfig = (ObjectNode) mapper.createObjectNode().set("settings", esSettingsNode); + ObjectNode mappings = (ObjectNode) mapper.createObjectNode().set(getType(), esMappingsNodes); + + esConfig.set("mappings", mappings); + + try { + return mapper.writeValueAsString(esConfig); + } catch (JsonProcessingException exc) { + throw new ElasticSearchOperationException("Error getting object node as string", exc); + } + + } + + /** + * Builds the elastic search entity count history table config. + * + * @return the string + * @throws ElasticSearchOperationException the elastic search operation exception + */ + public String buildElasticSearchEntityCountHistoryTableConfig() + throws ElasticSearchOperationException { + + JsonNode esSettingsNode; + JsonNode esMappingsNodes; + ObjectMapper mapper = new ObjectMapper(); + + try { + esSettingsNode = mapper.readTree(getElasticSearchSettings()); + esMappingsNodes = mapper.readTree(getElasticSearchEntityCountHistoryMappings()); + } catch (IOException e1) { + throw new ElasticSearchOperationException("Caught an exception building initial ES index"); + } + + ObjectNode esConfig = (ObjectNode) mapper.createObjectNode().set("settings", esSettingsNode); + ObjectNode mappings = (ObjectNode) mapper.createObjectNode().set(getType(), esMappingsNodes); + + esConfig.set("mappings", mappings); + + try { + return mapper.writeValueAsString(esConfig); + } catch (JsonProcessingException exc) { + throw new ElasticSearchOperationException("Error getting object node as string", exc); + } + + } + + public String buildAggregationTableConfig() throws ElasticSearchOperationException { + + JsonNode esMappingsNodes; + ObjectMapper mapper = new ObjectMapper(); + + try { + esMappingsNodes = mapper.readTree(this.getDynamicMappings()); + } catch (IOException e1) { + throw new ElasticSearchOperationException( + "Caught an exception building Aggreagation ES index"); + } + + ObjectNode mappings = (ObjectNode) mapper.createObjectNode().set(getType(), esMappingsNodes); + + ObjectNode indexConfig = (ObjectNode) mapper.createObjectNode().set("mappings", mappings); + + try { + return mapper.writeValueAsString(indexConfig); + } catch (JsonProcessingException exc) { + throw new ElasticSearchOperationException("Error getting object node as string", exc); + } + + } + + public String buildAutosuggestionTableConfig() throws ElasticSearchOperationException { + + JsonNode esSettingsNode; + JsonNode esMappingsNodes; + ObjectMapper mapper = new ObjectMapper(); + + try { + esSettingsNode = mapper.readTree(this.getAutosuggestIndexSettings()); + esMappingsNodes = mapper.readTree(this.getAutosuggestIndexMappings()); + } catch (IOException e1) { + throw new ElasticSearchOperationException( + "Caught an exception building Autosuggestion ES index"); + } + + ObjectNode indexConfig = (ObjectNode) mapper.createObjectNode().set("settings", esSettingsNode); + ObjectNode mappings = (ObjectNode) mapper.createObjectNode().set(getType(), esMappingsNodes); + + indexConfig.set("mappings", mappings); + + try { + return mapper.writeValueAsString(indexConfig); + } catch (JsonProcessingException exc) { + throw new ElasticSearchOperationException("Error getting object node as string", exc); + } + + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ElasticSearchConfig [ipAddress=" + ipAddress + ", httpPort=" + httpPort + + ", javaApiPort=" + javaApiPort + ", indexName=" + indexName + ", type=" + type + + ", clusterName=" + clusterName + ", mappingsFileName=" + mappingsFileName + + ", settingsFileName=" + settingsFileName + ", syncAdapterMaxConcurrentWorkers=" + + syncAdapterMaxConcurrentWorkers + ", auditIndexName=" + auditIndexName + + ", topographicalSearchIndex=" + topographicalSearchIndex + ", entityCountHistoryIndex=" + + entityCountHistoryIndex + ", autosuggestIndexname=" + autosuggestIndexname + + ", entityCountHistoryMappingsFileName=" + entityCountHistoryMappingsFileName + + ", autoSuggestSettingsFileName=" + autoSuggestSettingsFileName + + ", autoSuggestMappingsFileName=" + autoSuggestMappingsFileName + + ", dynamicMappingsFileName=" + dynamicMappingsFileName + ", processorConfig=" + + processorConfig + "]"; + } +} diff --git a/src/main/java/org/openecomp/sparky/dal/exception/ElasticSearchOperationException.java b/src/main/java/org/openecomp/sparky/dal/exception/ElasticSearchOperationException.java new file mode 100644 index 0000000..cb477d0 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/exception/ElasticSearchOperationException.java @@ -0,0 +1,54 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.exception; + +/** + * The Class ElasticSearchOperationException. + */ +public class ElasticSearchOperationException extends Exception { + + private static final long serialVersionUID = -7689309913743200670L; + + /** + * Instantiates a new elastic search operation exception. + * + * @param message the message + * @param exc the exc + */ + public ElasticSearchOperationException(String message, Exception exc) { + super(message, exc); + } + + /** + * Instantiates a new elastic search operation exception. + * + * @param message the message + */ + public ElasticSearchOperationException(String message) { + super(message); + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/rest/HttpMethod.java b/src/main/java/org/openecomp/sparky/dal/rest/HttpMethod.java new file mode 100644 index 0000000..6a7c3db --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/rest/HttpMethod.java @@ -0,0 +1,34 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.rest; + + +/** + * The Enum HttpMethod. + */ +public enum HttpMethod { + GET, PUT, POST, DELETE, PATCH, HEAD +} diff --git a/src/main/java/org/openecomp/sparky/dal/rest/OperationResult.java b/src/main/java/org/openecomp/sparky/dal/rest/OperationResult.java new file mode 100644 index 0000000..fcceb2b --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/rest/OperationResult.java @@ -0,0 +1,198 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.rest; + +/** + * The Class OperationResult. + */ +public class OperationResult { + + private String result; + + private String objectId; + private String requestLink; + private String requestPayload; + + private int resultCode; + + private boolean resolvedLinkFromCache; + + private boolean resolvedLinkFromServer; + + private boolean resolvedLinkFailure; + + private int numRequestRetries; + + private long responseTimeInMs; + + /** + * Reset. + */ + public void reset() { + this.objectId = null; + this.result = null; + this.requestLink = null; + this.requestPayload = null; + this.resultCode = -1; + this.resolvedLinkFailure = false; + this.resolvedLinkFromServer = false; + this.resolvedLinkFromCache = false; + this.responseTimeInMs = 0; + this.numRequestRetries = 0; + } + + public String getObjectId() { + return objectId; + } + + public void setObjectId(String objectId) { + this.objectId = objectId; + } + + public boolean isResolvedLinkFromCache() { + return resolvedLinkFromCache; + } + + /** + * Was successful. + * + * @return true, if successful + */ + public boolean wasSuccessful() { + return (resultCode > 199 && resultCode < 300); + } + + public String getRequestLink() { + return requestLink; + } + + public void setRequestLink(String requestLink) { + this.requestLink = requestLink; + } + + public String getRequestPayload() { + return requestPayload; + } + + public void setRequestPayload(String requestPayload) { + this.requestPayload = requestPayload; + } + + public void setResolvedLinkFromCache(boolean resolvedLinkFromCache) { + this.resolvedLinkFromCache = resolvedLinkFromCache; + } + + public boolean isResolvedLinkFromServer() { + return resolvedLinkFromServer; + } + + public void setResolvedLinkFromServer(boolean resolvedLinkFromServer) { + this.resolvedLinkFromServer = resolvedLinkFromServer; + } + + public boolean isResolvedLinkFailure() { + return resolvedLinkFailure; + } + + public void setResolvedLinkFailure(boolean resolvedLinkFailure) { + this.resolvedLinkFailure = resolvedLinkFailure; + } + + public String getResult() { + return result; + } + + public int getResultCode() { + return resultCode; + } + + public void setResultCode(int resultCode) { + this.resultCode = resultCode; + } + + public void setResult(String result) { + this.result = result; + } + + /** + * Sets the result. + * + * @param resultCode the result code + * @param result the result + */ + public void setResult(int resultCode, String result) { + this.resultCode = resultCode; + this.result = result; + } + + /** + * Instantiates a new operation result. + */ + public OperationResult() { + super(); + } + + /** + * Instantiates a new operation result. + * + * @param resultCode the result code + * @param result the result + */ + public OperationResult(int resultCode, String result) { + super(); + this.resultCode = resultCode; + this.result = result; + } + + public long getResponseTimeInMs() { + return responseTimeInMs; + } + + public void setResponseTimeInMs(long responseTimeInMs) { + this.responseTimeInMs = responseTimeInMs; + } + + public int getNumRequestRetries() { + return numRequestRetries; + } + + public void setNumRequestRetries(int numRequestRetries) { + this.numRequestRetries = numRequestRetries; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "OperationResult [result=" + result + ", resultCode=" + resultCode + + ", resolvedLinkFromCache=" + resolvedLinkFromCache + ", resolvedLinkFromServer=" + + resolvedLinkFromServer + ", resolvedLinkFailure=" + resolvedLinkFailure + + ", numRequestRetries=" + numRequestRetries + ", responseTimeInMs=" + responseTimeInMs + + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/rest/RestClientBuilder.java b/src/main/java/org/openecomp/sparky/dal/rest/RestClientBuilder.java new file mode 100644 index 0000000..267061a --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/rest/RestClientBuilder.java @@ -0,0 +1,148 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.rest; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import com.sun.jersey.client.urlconnection.HTTPSProperties; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; + +import org.openecomp.sparky.security.SecurityContextFactory; +import org.openecomp.sparky.security.SecurityContextFactoryImpl; + +/** + * This is a generic REST Client builder with flexible security validation. Sometimes it's nice to + * be able to disable server chain cert validation and hostname validation to work-around lab + * issues, but at the same time be able to provide complete validation with client cert + hostname + + * server cert chain validation. + * I used the ModelLoader REST client as a base and merged in the TSUI client I wrote which also + * validates the server hostname and server certificate chain. + * + * @author DAVEA + * + */ +public class RestClientBuilder { + + /* + * TODO: implement fluent interface? + */ + + private boolean useHttps; + private boolean validateServerHostname; + private int connectTimeoutInMs; + private int readTimeoutInMs; + protected SecurityContextFactory sslContextFactory; + + /** + * Instantiates a new rest client builder. + */ + public RestClientBuilder() { + validateServerHostname = false; + connectTimeoutInMs = 60000; + readTimeoutInMs = 60000; + useHttps = true; + sslContextFactory = new SecurityContextFactoryImpl(); + } + + public SecurityContextFactory getSslContextFactory() { + return sslContextFactory; + } + + public void setSslContextFactory(SecurityContextFactory sslContextFactory) { + this.sslContextFactory = sslContextFactory; + } + + public boolean isUseHttps() { + return useHttps; + } + + public void setUseHttps(boolean useHttps) { + this.useHttps = useHttps; + } + + public int getConnectTimeoutInMs() { + return connectTimeoutInMs; + } + + public void setConnectTimeoutInMs(int connectTimeoutInMs) { + this.connectTimeoutInMs = connectTimeoutInMs; + } + + public int getReadTimeoutInMs() { + return readTimeoutInMs; + } + + public void setReadTimeoutInMs(int readTimeoutInMs) { + this.readTimeoutInMs = readTimeoutInMs; + } + + public boolean isValidateServerHostname() { + return validateServerHostname; + } + + public void setValidateServerHostname(boolean validateServerHostname) { + this.validateServerHostname = validateServerHostname; + } + + public Client getClient() throws Exception { + + Client client = null; + ClientConfig clientConfig = new DefaultClientConfig(); + + if (useHttps) { + SSLContext sslContext = sslContextFactory.getSecureContext(); + + if (validateServerHostname) { + + clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, + new HTTPSProperties(null, sslContext)); + + } else { + clientConfig.getProperties().put(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES, + new HTTPSProperties(new HostnameVerifier() { + @Override + public boolean verify(String string, SSLSession sslSession) { + return true; + } + }, sslContext)); + + } + } + + client = Client.create(clientConfig); + + client.setConnectTimeout(connectTimeoutInMs); + client.setReadTimeout(readTimeoutInMs); + + return client; + + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/rest/RestDataProvider.java b/src/main/java/org/openecomp/sparky/dal/rest/RestDataProvider.java new file mode 100644 index 0000000..15dad28 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/rest/RestDataProvider.java @@ -0,0 +1,112 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.rest; + +/** + * The Interface RestDataProvider. + */ +public interface RestDataProvider { + + /** + * Do get. + * + * @param url the url + * @param acceptContentType the accept content type + * @return the operation result + */ + OperationResult doGet(String url, String acceptContentType); + + /** + * Do delete. + * + * @param url the url + * @param acceptContentType the accept content type + * @return the operation result + */ + OperationResult doDelete(String url, String acceptContentType); + + /** + * Do post. + * + * @param url the url + * @param jsonPayload the json payload + * @param acceptContentType the accept content type + * @return the operation result + */ + OperationResult doPost(String url, String jsonPayload, String acceptContentType); + + /** + * Do put. + * + * @param url the url + * @param jsonPayload the json payload + * @param acceptContentType the accept content type + * @return the operation result + */ + OperationResult doPut(String url, String jsonPayload, String acceptContentType); + + /** + * Do patch. + * + * @param url the url + * @param jsonPayload the json payload + * @param acceptContentType the accept content type + * @return the operation result + */ + OperationResult doPatch(String url, String jsonPayload, String acceptContentType); + + /** + * Do head. + * + * @param url the url + * @param acceptContentType the accept content type + * @return the operation result + */ + OperationResult doHead(String url, String acceptContentType); + + /** + * Do restful operation. + * + * @param method the method + * @param url the url + * @param payload the payload + * @param payloadType the payload type + * @param acceptContentType the accept content type + * @return the operation result + */ + OperationResult doRestfulOperation(HttpMethod method, String url, String payload, + String payloadType, String acceptContentType); + + /** + * Shutdown. + */ + void shutdown(); + + /** + * Clear cache. + */ + void clearCache(); +} diff --git a/src/main/java/org/openecomp/sparky/dal/rest/RestOperationalStatistics.java b/src/main/java/org/openecomp/sparky/dal/rest/RestOperationalStatistics.java new file mode 100644 index 0000000..7b0ca48 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/rest/RestOperationalStatistics.java @@ -0,0 +1,256 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.rest; + +import org.openecomp.sparky.analytics.AbstractStatistics; +import org.openecomp.sparky.dal.NetworkTransaction; + +/** + * The Class RestOperationalStatistics. + */ +public class RestOperationalStatistics extends AbstractStatistics { + + private static final String GET_1XX = "GET_1XX"; + private static final String GET_2XX = "GET_2XX"; + private static final String GET_3XX = "GET_3XX"; + private static final String GET_4XX = "GET_4XX"; + private static final String GET_5XX = "GET_5XX"; + private static final String GET_6XX = "GET_6XX"; + + private static final String PUT_1XX = "PUT_1XX"; + private static final String PUT_2XX = "PUT_2XX"; + private static final String PUT_3XX = "PUT_3XX"; + private static final String PUT_4XX = "PUT_4XX"; + private static final String PUT_5XX = "PUT_5XX"; + private static final String PUT_6XX = "PUT_6XX"; + + private static final String POST_1XX = "POST_1XX"; + private static final String POST_2XX = "POST_2XX"; + private static final String POST_3XX = "POST_3XX"; + private static final String POST_4XX = "POST_4XX"; + private static final String POST_5XX = "POST_5XX"; + private static final String POST_6XX = "POST_6XX"; + + private static final String DELETE_1XX = "DELETE_1XX"; + private static final String DELETE_2XX = "DELETE_2XX"; + private static final String DELETE_3XX = "DELETE_3XX"; + private static final String DELETE_4XX = "DELETE_4XX"; + private static final String DELETE_5XX = "DELETE_5XX"; + private static final String DELETE_6XX = "DELETE_6XX"; + + /** + * Creates the counters. + */ + private void createCounters() { + + addCounter(GET_1XX); + addCounter(GET_2XX); + addCounter(GET_3XX); + addCounter(GET_4XX); + addCounter(GET_5XX); + addCounter(GET_6XX); + + addCounter(PUT_1XX); + addCounter(PUT_2XX); + addCounter(PUT_3XX); + addCounter(PUT_4XX); + addCounter(PUT_5XX); + addCounter(PUT_6XX); + + addCounter(POST_1XX); + addCounter(POST_2XX); + addCounter(POST_3XX); + addCounter(POST_4XX); + addCounter(POST_5XX); + addCounter(POST_6XX); + + addCounter(DELETE_1XX); + addCounter(DELETE_2XX); + addCounter(DELETE_3XX); + addCounter(DELETE_4XX); + addCounter(DELETE_5XX); + addCounter(DELETE_6XX); + + + } + + /** + * Gets the result code. + * + * @param txn the txn + * @return the result code + */ + private int getResultCode(NetworkTransaction txn) { + + if (txn == null) { + return -1; + } + + if (txn.getOperationResult() == null) { + return -1; + } + + return txn.getOperationResult().getResultCode(); + + } + + /** + * Update counters. + * + * @param txn the txn + */ + public void updateCounters(NetworkTransaction txn) { + + if (txn == null) { + return; + } + + int rc = getResultCode(txn); + + switch (txn.getOperationType()) { + + case GET: { + + if (100 <= rc && rc <= 199) { + pegCounter(GET_1XX); + } else if (200 <= rc && rc <= 299) { + pegCounter(GET_2XX); + } else if (300 <= rc && rc <= 399) { + pegCounter(GET_3XX); + } else if (400 <= rc && rc <= 499) { + pegCounter(GET_4XX); + } else if (500 <= rc && rc <= 599) { + pegCounter(GET_5XX); + } else if (600 <= rc && rc <= 699) { + pegCounter(GET_6XX); + } + + break; + } + + case PUT: { + + if (100 <= rc && rc <= 199) { + pegCounter(PUT_1XX); + } else if (200 <= rc && rc <= 299) { + pegCounter(PUT_2XX); + } else if (300 <= rc && rc <= 399) { + pegCounter(PUT_3XX); + } else if (400 <= rc && rc <= 499) { + pegCounter(PUT_4XX); + } else if (500 <= rc && rc <= 599) { + pegCounter(PUT_5XX); + } else if (600 <= rc && rc <= 699) { + pegCounter(PUT_6XX); + } + + break; + } + + case POST: { + + if (100 <= rc && rc <= 199) { + pegCounter(POST_1XX); + } else if (200 <= rc && rc <= 299) { + pegCounter(POST_2XX); + } else if (300 <= rc && rc <= 399) { + pegCounter(POST_3XX); + } else if (400 <= rc && rc <= 499) { + pegCounter(POST_4XX); + } else if (500 <= rc && rc <= 599) { + pegCounter(POST_5XX); + } else if (600 <= rc && rc <= 699) { + pegCounter(POST_6XX); + } + + break; + } + + case DELETE: { + + if (100 <= rc && rc <= 199) { + pegCounter(DELETE_1XX); + } else if (200 <= rc && rc <= 299) { + pegCounter(DELETE_2XX); + } else if (300 <= rc && rc <= 399) { + pegCounter(DELETE_3XX); + } else if (400 <= rc && rc <= 499) { + pegCounter(DELETE_4XX); + } else if (500 <= rc && rc <= 599) { + pegCounter(DELETE_5XX); + } else if (600 <= rc && rc <= 699) { + pegCounter(DELETE_6XX); + } + + break; + } + + default: { + // not expecting anything else yet + } + + } + + } + + /** + * Instantiates a new rest operational statistics. + */ + public RestOperationalStatistics() { + createCounters(); + } + + public String getStatisticsReport() { + + StringBuilder sb = new StringBuilder(128); + + sb.append("\n ") + .append(String.format( + "%-12s 1XX: %-12d 2XX: %-12d 3XX: %-12d 4XX: %-12d 5XX: %-12d 6XX: %-12d ", + HttpMethod.DELETE, getCounterValue(DELETE_1XX), getCounterValue(DELETE_2XX), + getCounterValue(DELETE_3XX), getCounterValue(DELETE_4XX), getCounterValue(DELETE_5XX), + getCounterValue(DELETE_6XX))); + + sb.append("\n ").append(String.format( + "%-12s 1XX: %-12d 2XX: %-12d 3XX: %-12d 4XX: %-12d 5XX: %-12d 6XX: %-12d ", HttpMethod.PUT, + getCounterValue(PUT_1XX), getCounterValue(PUT_2XX), getCounterValue(PUT_3XX), + getCounterValue(PUT_4XX), getCounterValue(PUT_5XX), getCounterValue(PUT_6XX))); + + sb.append("\n ").append(String.format( + "%-12s 1XX: %-12d 2XX: %-12d 3XX: %-12d 4XX: %-12d 5XX: %-12d 6XX: %-12d ", HttpMethod.POST, + getCounterValue(POST_1XX), getCounterValue(POST_2XX), getCounterValue(POST_3XX), + getCounterValue(POST_4XX), getCounterValue(POST_5XX), getCounterValue(POST_6XX))); + + sb.append("\n ").append(String.format( + "%-12s 1XX: %-12d 2XX: %-12d 3XX: %-12d 4XX: %-12d 5XX: %-12d 6XX: %-12d ", HttpMethod.GET, + getCounterValue(GET_1XX), getCounterValue(GET_2XX), getCounterValue(GET_3XX), + getCounterValue(GET_4XX), getCounterValue(GET_5XX), getCounterValue(GET_6XX))); + + return sb.toString(); + } + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/rest/RestfulDataAccessor.java b/src/main/java/org/openecomp/sparky/dal/rest/RestfulDataAccessor.java new file mode 100644 index 0000000..1c2fb07 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/rest/RestfulDataAccessor.java @@ -0,0 +1,357 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.rest; + +import java.security.SecureRandom; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.cache.EntityCache; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.util.NodeUtils; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; + +/** + * The Class RestfulDataAccessor. + */ +public class RestfulDataAccessor implements RestDataProvider { + + protected SecureRandom txnIdGenerator; + + protected RestClientBuilder clientBuilder; + + protected EntityCache entityCache; + private boolean cacheEnabled; + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(RestfulDataAccessor.class); + + private boolean resourceNotFoundErrorsSurpressed; + + public static final String APPLICATION_JSON = "application/json"; + public static final String APPLICATION_MERGE_PATCH_JSON = "application/merge-patch+json"; + public static final String APPLICATION_X_WWW_FORM_URL_ENCODED = + "application/x-www-form-urlencoded"; + + + /** + * Instantiates a new restful data accessor. + * + * @param clientBuilder the client builder + */ + public RestfulDataAccessor(RestClientBuilder clientBuilder) { + this.clientBuilder = clientBuilder; + txnIdGenerator = new SecureRandom(); + resourceNotFoundErrorsSurpressed = false; + cacheEnabled = false; + entityCache = null; + } + + protected boolean isCacheEnabled() { + return cacheEnabled; + } + + public void setCacheEnabled(boolean cacheEnabled) { + this.cacheEnabled = cacheEnabled; + } + + protected EntityCache getEntityCache() { + return entityCache; + } + + public void setEntityCache(EntityCache entityCache) { + this.entityCache = entityCache; + } + + /** + * Cache result. + * + * @param result the result + */ + private void cacheResult(OperationResult result) { + if (cacheEnabled && entityCache != null) { + final String id = + NodeUtils.generateUniqueShaDigest(result.getRequestLink(), result.getRequestPayload()); + entityCache.put(id, result); + } + } + + /** + * Populate operation result. + * + * @param response the response + * @param opResult the op result + */ + protected void populateOperationResult(ClientResponse response, OperationResult opResult) { + + if (response == null) { + opResult.setResult(500, "Client response was null"); + return; + } + + int statusCode = response.getStatus(); + String payload = response.getEntity(String.class); + + opResult.setResult(statusCode, payload); + + } + + /** + * Gets the cached data. + * + * @param link the link + * @param payload the payload + * @return the cached data + */ + private OperationResult getCachedData(String link, String payload) { + if (cacheEnabled && entityCache != null) { + final String id = NodeUtils.generateUniqueShaDigest(link, payload); + return entityCache.get(id, link); + } + return null; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doRestfulOperation(org.openecomp.sparky.dal.rest.HttpMethod, java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult doRestfulOperation(HttpMethod method, String url, String payload, + String payloadType, String acceptContentType) { + + ClientResponse clientResponse = null; + + long startTimeInMs = System.currentTimeMillis(); + Client client = null; + Builder builder = null; + + OperationResult operationResult = null; + + /* + * Attempt to get cached data for the requested URL. We don't currently cache the other + * operations. + */ + + operationResult = getCachedData(url, payload); + + if (operationResult != null) { + + /* + * cache-hit, return what we found + */ + + // System.out.println("operationResult = " + operationResult.getResultCode()); + // System.out.println("opresult = " + operationResult.getResult()); + return operationResult; + } + + /* + * else cache miss / cache disabled (default operation) + */ + + operationResult = new OperationResult(); + operationResult.setRequestLink(url); + + try { + + client = clientBuilder.getClient(); + + switch (method) { + case GET: { + builder = setClientDefaults(client, url, null, acceptContentType); + clientResponse = builder.get(ClientResponse.class); + break; + } + + case PUT: { + builder = setClientDefaults(client, url, payloadType, acceptContentType); + clientResponse = builder.put(ClientResponse.class, payload); + break; + } + + case POST: { + builder = setClientDefaults(client, url, payloadType, acceptContentType); + clientResponse = builder.post(ClientResponse.class, payload); + break; + } + + case DELETE: { + builder = setClientDefaults(client, url, null, acceptContentType); + clientResponse = builder.delete(ClientResponse.class); + break; + } + + case PATCH: { + builder = setClientDefaults(client, url, payloadType, acceptContentType); + builder = builder.header("X-HTTP-Method-Override", "PATCH"); + clientResponse = builder.post(ClientResponse.class, payload); + break; + } + + case HEAD: { + builder = setClientDefaults(client, url, null, acceptContentType); + clientResponse = builder.head(); + break; + } + + + default: { + operationResult.setResult(500, "Unhandled HTTP Method operation = " + method); + return operationResult; + } + + } + + } catch (Exception ex) { + LOG.error(AaiUiMsgs.RESTFULL_OP_ERROR_VERBOSE, url, ex.getLocalizedMessage()); + operationResult.setResult(500, + String.format("Error retrieving link = '%s' from restful endpoint due to error = '%s'", + url, ex.getLocalizedMessage())); + return operationResult; + } + + populateOperationResult(clientResponse, operationResult); + + if (operationResult.getResultCode() != 404 + || (operationResult.getResultCode() == 404 && !isResourceNotFoundErrorsSurpressed())) { + LOG.info(AaiUiMsgs.RESTFULL_OP_COMPLETE, method.toString(), + String.valueOf(System.currentTimeMillis() - startTimeInMs), url, + String.valueOf(operationResult.getResultCode())); + } + + cacheResult(operationResult); + + return operationResult; + + } + + public boolean isResourceNotFoundErrorsSurpressed() { + return resourceNotFoundErrorsSurpressed; + } + + public void setResourceNotFoundErrorsSurpressed(boolean resourceNotFoundErrorsSurpressed) { + this.resourceNotFoundErrorsSurpressed = resourceNotFoundErrorsSurpressed; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doGet(java.lang.String, java.lang.String) + */ + @Override + public OperationResult doGet(String url, String acceptContentType) { + return doRestfulOperation(HttpMethod.GET, url, null, null, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doDelete(java.lang.String, java.lang.String) + */ + @Override + public OperationResult doDelete(String url, String acceptContentType) { + return doRestfulOperation(HttpMethod.DELETE, url, null, null, acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPost(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult doPost(String url, String jsonPayload, String acceptContentType) { + return doRestfulOperation(HttpMethod.POST, url, jsonPayload, APPLICATION_JSON, + acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPut(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult doPut(String url, String jsonPayload, String acceptContentType) { + return doRestfulOperation(HttpMethod.PUT, url, jsonPayload, APPLICATION_JSON, + acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPatch(java.lang.String, java.lang.String, java.lang.String) + */ + @Override + public OperationResult doPatch(String url, String jsonPayload, String acceptContentType) { + return doRestfulOperation(HttpMethod.PATCH, url, jsonPayload, APPLICATION_MERGE_PATCH_JSON, + acceptContentType); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doHead(java.lang.String, java.lang.String) + */ + @Override + public OperationResult doHead(String url, String acceptContentType) { + return doRestfulOperation(HttpMethod.HEAD, url, null, null, acceptContentType); + } + + /** + * Sets the client defaults. + * + * @param client the client + * @param url the url + * @param payloadContentType the payload content type + * @param acceptContentType the accept content type + * @return the builder + */ + protected Builder setClientDefaults(Client client, String url, String payloadContentType, + String acceptContentType) { + WebResource resource = client.resource(url); + Builder builder = null; + builder = resource.accept(acceptContentType); + + if (payloadContentType != null) { + builder = builder.header("Content-Type", payloadContentType); + } + + return builder; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#shutdown() + */ + @Override + public void shutdown() { + + if (entityCache != null) { + entityCache.shutdown(); + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#clearCache() + */ + @Override + public void clearCache() { + if (cacheEnabled) { + entityCache.clear(); + } + + } + +} diff --git a/src/main/java/org/openecomp/sparky/dal/sas/config/SearchServiceConfig.java b/src/main/java/org/openecomp/sparky/dal/sas/config/SearchServiceConfig.java new file mode 100644 index 0000000..1ff2001 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/sas/config/SearchServiceConfig.java @@ -0,0 +1,224 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.sas.config; + +import java.util.Properties; + +import org.openecomp.sparky.util.ConfigHelper; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +/** + * The Class ElasticSearchConfig. + */ +public class SearchServiceConfig { + + public static final String CONFIG_FILE = + TierSupportUiConstants.DYNAMIC_CONFIG_APP_LOCATION + "search-service.properties"; + + private static SearchServiceConfig instance; + + private String ipAddress; + + private String httpPort; + + private String indexName; + + private String auditIndexName; + + private String topographicalSearchIndex; + + private String entityCountHistoryIndex; + + private String version; + + private String type; + + private String certName; + + private String keystorePassword; + + private String keystore; + + private static final String IP_ADDRESS_DEFAULT = "localhost"; + + private static final String HTTP_PORT_DEFAULT = "9509"; + + private static final String INDEX_NAME_DEFAULT = "entitySearchIndex-localhost"; + + private static final String AUDIT_INDEX_NAME_DEFAULT = "di-violations"; + + private static final String TOPOGRAPHICAL_INDEX_NAME_DEFAULT = + "topographicalsearchindex-localhost"; + + private static final String ENTITY_COUNT_HISTORY_INDEX_NAME_DEFAULT = + "entitycounthistoryindex-localhost"; + + private static final String VERSION_DEFAULT = "v1"; + + public static SearchServiceConfig getConfig() throws Exception { + + if (instance == null) { + instance = new SearchServiceConfig(); + instance.initializeProperties(); + } + + return instance; + } + + public static void setConfig(SearchServiceConfig config) { + SearchServiceConfig.instance = config; + } + + /** + * Instantiates a new search service config. + */ + public SearchServiceConfig() { + // test method + } + + /** + * Initialize properties. + */ + private void initializeProperties() { + Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); + + Properties sasProps = ConfigHelper.getConfigWithPrefix("search-service", props); + + ipAddress = sasProps.getProperty("ipAddress", IP_ADDRESS_DEFAULT); + httpPort = sasProps.getProperty("httpPort", "" + HTTP_PORT_DEFAULT); + version = sasProps.getProperty("version", "" + VERSION_DEFAULT); + indexName = sasProps.getProperty("indexName", INDEX_NAME_DEFAULT); + auditIndexName = sasProps.getProperty("auditIndexName", AUDIT_INDEX_NAME_DEFAULT); + topographicalSearchIndex = sasProps.getProperty("topographicalIndexName", + TOPOGRAPHICAL_INDEX_NAME_DEFAULT); + entityCountHistoryIndex = sasProps.getProperty("entityCountHistoryIndexName", + ENTITY_COUNT_HISTORY_INDEX_NAME_DEFAULT); + certName = + sasProps.getProperty("ssl.cert-name", "aai-client-cert.p12"); + keystorePassword = sasProps.getProperty("ssl.keystore-password", + "OBF:1i9a1u2a1unz1lr61wn51wn11lss1unz1u301i6o"); + keystore = sasProps.getProperty("ssl.keystore", "tomcat_keystore"); + } + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public String getHttpPort() { + return httpPort; + } + + public void setHttpPort(String httpPort) { + this.httpPort = httpPort; + } + + public String getIndexName() { + return indexName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getAuditIndexName() { + return auditIndexName; + } + + public void setAuditIndexName(String auditIndexName) { + this.auditIndexName = auditIndexName; + } + + public String getTopographicalSearchIndex() { + return topographicalSearchIndex; + } + + public void setTopographicalSearchIndex(String topographicalSearchIndex) { + this.topographicalSearchIndex = topographicalSearchIndex; + } + + public String getEntityCountHistoryIndex() { + return entityCountHistoryIndex; + } + + public void setEntityCountHistoryIndex(String entityCountHistoryIndex) { + this.entityCountHistoryIndex = entityCountHistoryIndex; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + public String getCertName() { + return certName; + } + + public void setCertName(String certName) { + this.certName = certName; + } + + public String getKeystorePassword() { + return keystorePassword; + } + + public void setKeystorePassword(String keystorePassword) { + this.keystorePassword = keystorePassword; + } + + public String getKeystore() { + return keystore; + } + + public void setKeystore(String keystore) { + this.keystore = keystore; + } + + @Override + public String toString() { + return "SearchServiceConfig [ipAddress=" + ipAddress + ", httpPort=" + httpPort + ", indexName=" + + indexName + ", auditIndexName=" + auditIndexName + ", topographicalSearchIndex=" + + topographicalSearchIndex + ", entityCountHistoryIndex=" + entityCountHistoryIndex + + ", version=" + version + ", type=" + type + "]"; + } + + +} diff --git a/src/main/java/org/openecomp/sparky/dal/servlet/ResettableStreamHttpServletRequest.java b/src/main/java/org/openecomp/sparky/dal/servlet/ResettableStreamHttpServletRequest.java new file mode 100644 index 0000000..d7b1e6b --- /dev/null +++ b/src/main/java/org/openecomp/sparky/dal/servlet/ResettableStreamHttpServletRequest.java @@ -0,0 +1,129 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.dal.servlet; + +import com.google.common.primitives.Bytes; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/** + * The Class ResettableStreamHttpServletRequest. + */ +public class ResettableStreamHttpServletRequest extends HttpServletRequestWrapper { + + private byte[] requestBody = new byte[0]; + private boolean bufferFilled = false; + + /** + * Constructs a request object wrapping the given request. + * + * @param request The request to wrap + * @throws IllegalArgumentException if the request is null + */ + public ResettableStreamHttpServletRequest(HttpServletRequest request) { + super(request); + } + + /** + * Get request body. + * + * @return Bytes with the request body contents. + * @throws IOException In case stream reqding fails. + */ + public byte[] getRequestBody() throws IOException { + if (bufferFilled) { + return Arrays.copyOf(requestBody, requestBody.length); + } + + InputStream inputStream = super.getInputStream(); + + byte[] buffer = new byte[102400]; + + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + requestBody = Bytes.concat(this.requestBody, Arrays.copyOfRange(buffer, 0, bytesRead)); + } + + bufferFilled = true; + + return requestBody; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return new CustomServletInputStream(getRequestBody()); + } + + /** + * The Class CustomServletInputStream. + */ + private static class CustomServletInputStream extends ServletInputStream { + + private ByteArrayInputStream buffer; + + /** + * Instantiates a new custom servlet input stream. + * + * @param contents the contents + */ + public CustomServletInputStream(byte[] contents) { + this.buffer = new ByteArrayInputStream(contents); + } + + /* (non-Javadoc) + * @see java.io.InputStream#read() + */ + @Override + public int read() throws IOException { + return buffer.read(); + } + + @Override + public boolean isFinished() { + return buffer.available() == 0; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener arg0) { + throw new RuntimeException("Not implemented"); + } + + } + +} diff --git a/src/main/java/org/openecomp/sparky/inventory/EntityHistoryQueryBuilder.java b/src/main/java/org/openecomp/sparky/inventory/EntityHistoryQueryBuilder.java new file mode 100644 index 0000000..534af4a --- /dev/null +++ b/src/main/java/org/openecomp/sparky/inventory/EntityHistoryQueryBuilder.java @@ -0,0 +1,144 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.inventory; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +/** + * The Class EntityHistoryQueryBuilder. + */ +public class EntityHistoryQueryBuilder { + + private static final String TABLE = "table"; + private static final String GRAPH = "graph"; + + /** + * Gets the query. + * + * @param type the type + * @return the query + */ + public static JsonObject getQuery(String type) { + if (type.equalsIgnoreCase(TABLE)) { + return createTableQuery(); + } else if (type.equalsIgnoreCase(GRAPH)) { + return createGraphQuery(); + } else { + return null; + } + } + + /** + * Creates the graph query. + * + * @return the json object + */ + public static JsonObject createGraphQuery() { + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder(); + + jsonBuilder.add("aggs", + Json.createObjectBuilder().add("group_by_entityType", + Json.createObjectBuilder() + .add("terms", Json.createObjectBuilder().add("field", "entityType").add("size", 0)) + .add("aggs", Json.createObjectBuilder().add("group_by_date", + Json.createObjectBuilder().add("date_histogram", createDateHistogram()) + .add("aggs", Json.createObjectBuilder().add("sort_by_date", + Json.createObjectBuilder().add("top_hits", createTopHitsBlob()))))))); + jsonBuilder.add("size", 0); + + return jsonBuilder.build(); + } + + /** + * Creates the table query. + * + * @return the json object + */ + public static JsonObject createTableQuery() { + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder(); + + jsonBuilder.add("aggs", + Json.createObjectBuilder().add("group_by_entityType", + Json.createObjectBuilder() + .add("terms", Json.createObjectBuilder().add("field", "entityType").add("size", 0)) + .add("aggs", Json.createObjectBuilder().add("sort_by_date", + Json.createObjectBuilder().add("top_hits", createTopHitsBlob()))))); + jsonBuilder.add("size", 0); + + return jsonBuilder.build(); + } + + /** + * Creates the date histogram. + * + * @return the json object + */ + private static JsonObject createDateHistogram() { + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder(); + + jsonBuilder.add("field", "timestamp"); + jsonBuilder.add("min_doc_count", 1); + jsonBuilder.add("interval", "day"); + jsonBuilder.add("format", "epoch_millis"); + + return jsonBuilder.build(); + } + + /** + * Creates the top hits blob. + * + * @return the json object + */ + private static JsonObject createTopHitsBlob() { + JsonObjectBuilder builder = Json.createObjectBuilder(); + builder.add("size", 1); + builder.add("sort", getSortCriteria()); + return builder.build(); + } + + public static JsonArray getSortCriteria() { + JsonArrayBuilder jsonBuilder = Json.createArrayBuilder(); + jsonBuilder.add(Json.createObjectBuilder().add("timestamp", + Json.createObjectBuilder().add("order", "desc"))); + + return jsonBuilder.build(); + } + + /** + * The main method. + * + * @param args the arguments + */ + public static void main(String[] args) { + System.out.println("TABLE-QUERY: " + createTableQuery().toString()); + System.out.println("GRAPH_QUERY: " + createGraphQuery().toString()); + } + +} diff --git a/src/main/java/org/openecomp/sparky/inventory/entity/GeoIndexDocument.java b/src/main/java/org/openecomp/sparky/inventory/entity/GeoIndexDocument.java new file mode 100644 index 0000000..7ea1e44 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/inventory/entity/GeoIndexDocument.java @@ -0,0 +1,322 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.inventory.entity; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +import javax.json.Json; +import javax.json.JsonObject; + +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.synchronizer.entity.IndexDocument; +import org.openecomp.sparky.util.NodeUtils; + +/** + * The Class GeoIndexDocument. + */ +public class GeoIndexDocument implements Serializable, IndexDocument { + + private static final long serialVersionUID = -5188479658230319058L; + + protected String entityType; + protected String entityPrimaryKeyValue; + protected String entityPrimaryKeyName; + protected String latitude; + protected String longitude; + protected String selfLink; + protected OxmModelLoader loader; + protected ObjectMapper mapper = new ObjectMapper(); + // generated, SHA-256 digest + protected String id; + + /** + * Instantiates a new geo index document. + * + * @param loader the loader + */ + public GeoIndexDocument(OxmModelLoader loader) { + this(); + this.loader = loader; + } + + /** + * Convert bytes to hex string. + * + * @param bytesToConvert the bytes to convert + * @return the string + */ + private static String convertBytesToHexString(byte[] bytesToConvert) { + StringBuffer hexString = new StringBuffer(); + for (int i = 0; i < bytesToConvert.length; i++) { + hexString.append(Integer.toHexString(0xFF & bytesToConvert[i])); + } + return hexString.toString(); + } + + + + public boolean isValidGeoDocument() { + + boolean isValid = true; + + isValid &= (this.getEntityType() != null); + isValid &= (this.getLatitude() != null); + isValid &= (this.getLongitude() != null); + isValid &= (this.getId() != null); + isValid &= (this.getSelfLink() != null); + + isValid &= NodeUtils.isNumeric(this.getLatitude()); + isValid &= NodeUtils.isNumeric(this.getLongitude()); + + return isValid; + } + + /** + * Concat array. + * + * @param list the list + * @param delimiter the delimiter + * @return the string + */ + private static String concatArray(List list, char delimiter) { + + if (list == null || list.size() == 0) { + return ""; + } + + StringBuilder result = new StringBuilder(64); + + int listSize = list.size(); + boolean firstValue = true; + + for (String item : list) { + + if (firstValue) { + result.append(item); + firstValue = false; + } else { + result.append(delimiter).append(item); + } + + } + + return result.toString(); + + } + + /* + * We'll try and create a unique identity key that we can use for differencing the previously + * imported record sets as we won't have granular control of what is created/removed and when. The + * best we can hope for is identification of resources by generated Id until the Identity-Service + * UUID is tagged against all resources, then we can use that instead. + */ + + /** + * Generate unique sha digest. + * + * @param entityType the entity type + * @param fieldName the field name + * @param fieldValue the field value + * @return the string + * @throws NoSuchAlgorithmException the no such algorithm exception + */ + public static String generateUniqueShaDigest(String entityType, String fieldName, + String fieldValue) throws NoSuchAlgorithmException { + + /* + * Basically SHA-256 will result in an identity with a guaranteed uniqueness compared to just a + * java hashcode value. + */ + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(String.format("%s.%s.%s", entityType, fieldName, fieldValue).getBytes()); + return convertBytesToHexString(digest.digest()); + } + + /** + * Instantiates a new geo index document. + */ + public GeoIndexDocument() {} + + /* + * (non-Javadoc) + * + * @see com.att.queryrouter.dao.DocumentStoreDataEntity#getAsJson() + */ + @Override + public String getIndexDocumentJson() { + + JsonObject obj = null; + + if (latitude != null && longitude != null) { + obj = + Json.createObjectBuilder().add("entityType", entityType) + .add("pkey", entityPrimaryKeyValue) + .add("location", + Json.createObjectBuilder().add("lat", latitude).add("lon", longitude)) + .add("selfLink", selfLink).build(); + + } else { + obj = Json.createObjectBuilder().add("entityType", entityType) + .add("pkey", entityPrimaryKeyValue).add("selfLink", selfLink).build(); + } + + return obj.toString(); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.entity.IndexDocument#deriveFields() + */ + @Override + public void deriveFields() { + + /* + * We'll try and create a unique identity key that we can use for differencing the previously + * imported record sets as we won't have granular control of what is created/removed and when. + * The best we can hope for is identification of resources by generated Id until the + * Identity-Service UUID is tagged against all resources, then we can use that instead. + */ + + OxmEntityDescriptor descriptor = loader.getEntityDescriptor(entityType); + String entityPrimaryKeyName = NodeUtils.concatArray( + descriptor.getPrimaryKeyAttributeName(), "/"); + + this.id = + NodeUtils.generateUniqueShaDigest(entityType, entityPrimaryKeyName, entityPrimaryKeyValue); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "TopographicalEntity [" + ("entityType=" + entityType + ", ") + + ("entityPrimaryKeyValue=" + entityPrimaryKeyValue + ", ") + + ("latitude=" + latitude + ", ") + ("longitude=" + longitude + ", ") + ("ID=" + id + ", ") + + ("selfLink=" + selfLink) + "]"; + } + + @Override + public String getId() { + return this.id; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public String getEntityPrimaryKeyValue() { + return entityPrimaryKeyValue; + } + + public void setEntityPrimaryKeyValue(String entityPrimaryKeyValue) { + this.entityPrimaryKeyValue = entityPrimaryKeyValue; + } + + public String getEntityPrimaryKeyName() { + return entityPrimaryKeyName; + } + + public void setEntityPrimaryKeyName(String entityPrimaryKeyName) { + this.entityPrimaryKeyName = entityPrimaryKeyName; + } + + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } + + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + public String getSelfLink() { + return selfLink; + } + + public void setSelfLink(String selfLink) { + this.selfLink = selfLink; + } + + public static long getSerialversionuid() { + return serialVersionUID; + } + + public void setId(String id) { + this.id = id; + } + + @Override + public ObjectNode getBulkImportEntity() { + // TODO Auto-generated method stub + return buildImportDataObject(this.entityType, this.entityPrimaryKeyValue, this.selfLink, + this.latitude, this.longitude); + } + + /** + * Builds the import data object. + * + * @param entityType the entity type + * @param entityPrimaryKeyValue the entity primary key value + * @param selfLink the self link + * @param latitude the latitude + * @param longitude the longitude + * @return the object node + */ + @SuppressWarnings("deprecation") + protected ObjectNode buildImportDataObject(String entityType, String entityPrimaryKeyValue, + String selfLink, String latitude, String longitude) { + ObjectNode childNode = mapper.createObjectNode(); + childNode.put("lat", latitude); + childNode.put("lon", longitude); + ObjectNode parentNode = mapper.createObjectNode(); + + parentNode.put("entityType", entityType); + parentNode.put("pkey", entityPrimaryKeyValue); + parentNode.put("selfLink", selfLink); + parentNode.put("location", childNode); + + return parentNode; + } + +} diff --git a/src/main/java/org/openecomp/sparky/inventory/entity/TopographicalEntity.java b/src/main/java/org/openecomp/sparky/inventory/entity/TopographicalEntity.java new file mode 100644 index 0000000..da52728 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/inventory/entity/TopographicalEntity.java @@ -0,0 +1,221 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.inventory.entity; + +import java.io.IOException; +import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +import javax.json.Json; +import javax.json.JsonObject; + +/** + * The Class TopographicalEntity. + */ +public class TopographicalEntity implements Serializable { + + private static final long serialVersionUID = -5188479658230319058L; + + protected String entityType; + protected String entityPrimaryKeyValue; + protected String entityPrimaryKeyName; + protected String latitude; + protected String longitude; + protected String selfLink; + + // generated, SHA-256 digest + protected String id; + + /** + * Convert bytes to hex string. + * + * @param bytesToConvert the bytes to convert + * @return the string + */ + private static String convertBytesToHexString(byte[] bytesToConvert) { + StringBuffer hexString = new StringBuffer(); + for (int i = 0; i < bytesToConvert.length; i++) { + hexString.append(Integer.toHexString(0xFF & bytesToConvert[i])); + } + return hexString.toString(); + } + + /** + * Concat array. + * + * @param list the list + * @param delimiter the delimiter + * @return the string + */ + private static String concatArray(List list, char delimiter) { + + if (list == null || list.size() == 0) { + return ""; + } + + StringBuilder result = new StringBuilder(64); + + int listSize = list.size(); + boolean firstValue = true; + + for (String item : list) { + + if (firstValue) { + result.append(item); + firstValue = false; + } else { + result.append(delimiter).append(item); + } + + } + + return result.toString(); + + } + + /* + * We'll try and create a unique identity key that we can use for differencing the previously + * imported record sets as we won't have granular control of what is created/removed and when. The + * best we can hope for is identification of resources by generated Id until the Identity-Service + * UUID is tagged against all resources, then we can use that instead. + */ + + /** + * Generate unique sha digest. + * + * @param entityType the entity type + * @param fieldName the field name + * @param fieldValue the field value + * @return the string + * @throws NoSuchAlgorithmException the no such algorithm exception + */ + public static String generateUniqueShaDigest(String entityType, String fieldName, + String fieldValue) throws NoSuchAlgorithmException { + + /* + * Basically SHA-256 will result in an identity with a guaranteed uniqueness compared to just a + * java hashcode value. + */ + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + digest.update(String.format("%s.%s.%s", entityType, fieldName, fieldValue).getBytes()); + return convertBytesToHexString(digest.digest()); + } + + /** + * Instantiates a new topographical entity. + */ + public TopographicalEntity() {} + + /* + * (non-Javadoc) + * + * @see com.att.queryrouter.dao.DocumentStoreDataEntity#getAsJson() + */ + public String getAsJson() throws IOException { + + JsonObject obj = + Json.createObjectBuilder().add("entityType", entityType).add("pkey", entityPrimaryKeyValue) + .add("location", Json.createObjectBuilder().add("lat", latitude).add("lon", longitude)) + .add("selfLink", selfLink).build(); + + return obj.toString(); + } + + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "TopographicalEntity [" + ("entityType=" + entityType + ", ") + + ("entityPrimaryKeyValue=" + entityPrimaryKeyValue + ", ") + + ("latitude=" + latitude + ", ") + ("longitude=" + longitude + ", ") + ("ID=" + id + ", ") + + ("selfLink=" + selfLink) + "]"; + } + + public String getId() { + return this.id; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public String getEntityPrimaryKeyValue() { + return entityPrimaryKeyValue; + } + + public void setEntityPrimaryKeyValue(String entityPrimaryKeyValue) { + this.entityPrimaryKeyValue = entityPrimaryKeyValue; + } + + public String getEntityPrimaryKeyName() { + return entityPrimaryKeyName; + } + + public void setEntityPrimaryKeyName(String entityPrimaryKeyName) { + this.entityPrimaryKeyName = entityPrimaryKeyName; + } + + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } + + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + public String getSelfLink() { + return selfLink; + } + + public void setSelfLink(String selfLink) { + this.selfLink = selfLink; + } + + public static long getSerialversionuid() { + return serialVersionUID; + } + + public void setId(String id) { + this.id = id; + } + +} diff --git a/src/main/java/org/openecomp/sparky/inventory/servlet/EntityCountHistoryServlet.java b/src/main/java/org/openecomp/sparky/inventory/servlet/EntityCountHistoryServlet.java new file mode 100644 index 0000000..c3d96cf --- /dev/null +++ b/src/main/java/org/openecomp/sparky/inventory/servlet/EntityCountHistoryServlet.java @@ -0,0 +1,376 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.inventory.servlet; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openecomp.sparky.dal.elasticsearch.SearchAdapter; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestClientBuilder; +import org.openecomp.sparky.inventory.EntityHistoryQueryBuilder; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.util.ServletUtils; +import org.openecomp.sparky.viewandinspect.config.VisualizationConfig; + +/** + * The Class EntityCountHistoryServlet. + */ +public class EntityCountHistoryServlet extends HttpServlet { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(EntityCountHistoryServlet.class); + + private static final long serialVersionUID = 1L; + + private SearchAdapter search = null; + private ElasticSearchConfig elasticConfig = null; + private VisualizationConfig visualConfig = null; + private ObjectMapper mapper; + + private static final String SEARCH_STRING = "_search"; + private static final String TABLE = "table"; + private static final String GRAPH = "graph"; + + private List vnfEntityTypesToSummarize; + private boolean summarizevnf = false; + + /** + * Instantiates a new entity count history servlet. + * + * @throws ServletException the servlet exception + */ + public EntityCountHistoryServlet() throws ServletException { + init(); + } + + /* (non-Javadoc) + * @see javax.servlet.GenericServlet#init() + */ + @Override + public void init() throws ServletException { + super.init(); + try { + if (elasticConfig == null) { + elasticConfig = ElasticSearchConfig.getConfig(); + } + if (visualConfig == null) { + visualConfig = VisualizationConfig.getConfig(); + vnfEntityTypesToSummarize = + Arrays.asList(visualConfig.getVnfEntityTypes().toLowerCase().split("[\\s,]+")); + summarizevnf = visualConfig.getEntityTypesToSummarize().toLowerCase().contains("vnf"); + } + if (search == null) { + search = new SearchAdapter(); + } + this.mapper = new ObjectMapper(); + this.mapper.configure(SerializationFeature.INDENT_OUTPUT, true); + } catch (Exception exc) { + new ServletException( + "Caught an exception while getting an instance of servlet configuration.", exc); + } + } + + public void setSearch(SearchAdapter search) { + this.search = search; + } + + public void setElasticConfig(ElasticSearchConfig elasticConfig) { + this.elasticConfig = elasticConfig; + } + + /* (non-Javadoc) + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String txnID = request.getHeader("X-TransactionId"); + if (txnID == null){ + txnID = NodeUtils.getRandomTxnId(); + } + + String partnerName = request.getHeader("X-FromAppId"); + if ( partnerName == null) + partnerName = "Browser"; + + MdcContext.initialize(txnID, "AAI-UI", "", partnerName, + request.getRemoteAddr()); + + @SuppressWarnings("unused") + OperationResult operationResult = null; + if (request.getParameter("type") != null + && (request.getParameter("type").equalsIgnoreCase(TABLE) + || request.getParameter("type").equalsIgnoreCase(GRAPH))) { + try { + operationResult = getResults(response, request.getParameter("type")); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_SERVLET_PROCESSSING, exc); + } + } else { + ServletUtils.setServletResponse(LOG, true, 501, response, + ServletUtils.generateJsonErrorResponse("Unsupported request")); + } + } + + /* (non-Javadoc) + * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException { + + } + + /** + * Gets the results. + * + * @param response the response + * @param type the type + * @return the results + * @throws Exception the exception + */ + private OperationResult getResults(HttpServletResponse response, String type) throws Exception { + OperationResult operationResult = new OperationResult(); + + String requestString = + String.format("/%s/%s?pretty", elasticConfig.getEntityCountHistoryIndex(), SEARCH_STRING); + + String reqPayload = EntityHistoryQueryBuilder.getQuery(type).toString(); + + try { + final String fullUrlStr = ServletUtils.getFullUrl(elasticConfig, requestString); + OperationResult opResult = + ServletUtils.executePostQuery(LOG, search, response, fullUrlStr, reqPayload); + + JSONObject finalOutput = null; + if (type.equalsIgnoreCase(TABLE)) { + finalOutput = formatTableOutput(opResult.getResult()); + } else if (type.equalsIgnoreCase(GRAPH)) { + finalOutput = formatLineGraphOutput(opResult.getResult()); + } + + if (finalOutput != null) { + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(finalOutput); + out.close(); + } + + } catch (JsonProcessingException exc) { + ServletUtils.handleSearchServletErrors(LOG, "Unable to map JSONpayload", exc, response); + } + + return operationResult; + } + + /** + * Format table output. + * + * @param results the results + * @return the JSON object + * @throws JsonProcessingException the json processing exception + */ + private JSONObject formatTableOutput(String results) throws JsonProcessingException { + JsonNode resultNode = null; + + JSONObject finalResult = new JSONObject(); + JSONArray entitiesArr = new JSONArray(); + + Map entityCountInTable = initializeEntityMap(); + + long vnfCount = 0; + + try { + resultNode = mapper.readTree(results); + + final JsonNode bucketsNode = getBucketsNode(resultNode); + if (bucketsNode.isArray()) { + + for (final JsonNode entityNode : bucketsNode) { + String entityType = entityNode.get("key").asText(); + boolean isAVnf = vnfEntityTypesToSummarize.contains(entityType); + long countValue = 0; + + if (isAVnf || entityCountInTable.get(entityType) != null) { + final JsonNode hitsBucketNode = entityNode.get("sort_by_date").get("hits").get("hits"); + if (hitsBucketNode.isArray()) { + // the first bucket will be the latest + final JsonNode hitNode = hitsBucketNode.get(0); + + countValue = hitNode.get("_source").get("count").asLong(); + + /* + * Special case: Add all the VNF types together to get aggregate count + */ + if (summarizevnf && isAVnf) { + vnfCount += countValue; + countValue = vnfCount; + entityType = "vnf"; + } + + entityCountInTable.replace(entityType, countValue); + } + } + + } + } + for (Entry entry : entityCountInTable.entrySet()) { + JSONObject entityType = new JSONObject(); + entityType.put("key", entry.getKey()); + entityType.put("doc_count", entry.getValue()); + entitiesArr.put(entityType); + } + + finalResult.put("result", entitiesArr); + + } catch (Exception exc) { + LOG.warn(AaiUiMsgs.ERROR_BUILDING_RESPONSE_FOR_TABLE_QUERY, exc.getLocalizedMessage()); + } + + return finalResult; + } + + + /** + * Format line graph output. + * + * @param results the results + * @return the JSON object + * @throws JsonProcessingException the json processing exception + */ + private JSONObject formatLineGraphOutput(String results) throws JsonProcessingException { + Map countByDateMap = new HashMap(); + + JsonNode resultNode = null; + + JSONObject finalResult = new JSONObject(); + JSONArray finalResultArr = new JSONArray(); + + try { + resultNode = mapper.readTree(results); + + final JsonNode bucketsNode = getBucketsNode(resultNode); + + if (bucketsNode.isArray()) { + + for (final JsonNode entityNode : bucketsNode) { + final JsonNode dateBucketNode = entityNode.get("group_by_date").get("buckets"); + if (dateBucketNode.isArray()) { + for (final JsonNode dateBucket : dateBucketNode) { + Long date = dateBucket.get("key").asLong(); + final JsonNode countBucketNode = + dateBucket.get("sort_by_date").get("hits").get("hits"); + + if (countBucketNode.isArray()) { + final JsonNode latestEntityNode = countBucketNode.get(0); + + long currentCount = latestEntityNode.get("_source").get("count").asLong(); + if (countByDateMap.containsKey(date)) { + // add to the value if map already contains this date + currentCount += countByDateMap.get(date); + } + + countByDateMap.put(date, currentCount); + } + } + + } + } + } + /* + * Sort the map by epoch timestamp + */ + Map sortedMap = new TreeMap(countByDateMap); + for (Entry entry : sortedMap.entrySet()) { + JSONObject dateEntry = new JSONObject(); + dateEntry.put("date", entry.getKey()); + dateEntry.put("count", entry.getValue()); + finalResultArr.put(dateEntry); + } + + } catch (Exception exc) { + LOG.warn(AaiUiMsgs.ERROR_BUILDING_SEARCH_RESPONSE, exc.getLocalizedMessage()); + } + + return finalResult.put("result", finalResultArr); + } + + /** + * Gets the buckets node. + * + * @param node the node + * @return the buckets node + * @throws Exception the exception + */ + private JsonNode getBucketsNode(JsonNode node) throws Exception { + if (node.get("aggregations").get("group_by_entityType").get("buckets") != null) { + return node.get("aggregations").get("group_by_entityType").get("buckets"); + } else { + throw new Exception("Failed to map JSON response"); + } + } + + /** + * Initialize entity map. + * + * @return the map + */ + private Map initializeEntityMap() { + Map entityMap = new HashMap(); + String[] entityTypes = visualConfig.getEntityTypesToSummarize().split(","); + for (String entity : entityTypes) { + entityMap.put(entity, (long) 0); + } + + return entityMap; + } + +} diff --git a/src/main/java/org/openecomp/sparky/inventory/servlet/GeoVisualizationServlet.java b/src/main/java/org/openecomp/sparky/inventory/servlet/GeoVisualizationServlet.java new file mode 100644 index 0000000..cf6e0f2 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/inventory/servlet/GeoVisualizationServlet.java @@ -0,0 +1,223 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.inventory.servlet; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.security.SecureRandom; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.json.JSONArray; +import org.json.JSONObject; +import org.openecomp.sparky.dal.elasticsearch.SearchAdapter; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestClientBuilder; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.util.ServletUtils; + +/** + * The Class GeoVisualizationServlet. + */ +public class GeoVisualizationServlet extends HttpServlet { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(GeoVisualizationServlet.class); + + private static final long serialVersionUID = 1L; + + private SearchAdapter search = null; + private ElasticSearchConfig elasticConfig = null; + private ObjectMapper mapper; + + private static final String SEARCH_STRING = "_search"; + + private static final String SEARCH_PARAMETER = + "?filter_path=hits.hits._source&_source=location&size=5000&q=entityType:"; + + /** + * Instantiates a new geo visualization servlet. + * + * @throws ServletException the servlet exception + */ + public GeoVisualizationServlet() throws ServletException { + init(); + } + + /* (non-Javadoc) + * @see javax.servlet.GenericServlet#init() + */ + @Override + public void init() throws ServletException { + super.init(); + try { + if (elasticConfig == null) { + elasticConfig = ElasticSearchConfig.getConfig(); + } + if (search == null) { + search = new SearchAdapter(); + } + this.mapper = new ObjectMapper(); + } catch (Exception exc) { + new ServletException( + "Caught an exception while getting an instance of servlet configuration.", exc); + } + } + + public void setSearch(SearchAdapter search) { + this.search = search; + } + + public void setElasticConfig(ElasticSearchConfig elasticConfig) { + this.elasticConfig = elasticConfig; + } + + /* (non-Javadoc) + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String txnID = request.getHeader("X-TransactionId"); + if (txnID == null){ + txnID = NodeUtils.getRandomTxnId(); + } + + String partnerName = request.getHeader("X-FromAppId"); + if ( partnerName == null) + partnerName = "Browser"; + + MdcContext.initialize(txnID, "AAI-UI", "", partnerName, + request.getRemoteAddr()); + + OperationResult operationResult = null; + try { + operationResult = getGeoVisualizationResults(response, request.getParameter("entity")); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_PROCESSING_REQUEST, exc); + } + } + + /* (non-Javadoc) + * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException { + + } + + /** + * Gets the geo visualization results. + * + * @param response the response + * @param entityType the entity type + * @return the geo visualization results + * @throws Exception the exception + */ + protected OperationResult getGeoVisualizationResults(HttpServletResponse response, + String entityType) throws Exception { + OperationResult operationResult = new OperationResult(); + + String parameters = SEARCH_PARAMETER + entityType; + String requestString = String.format("/%s/%s/%s", elasticConfig.getTopographicalSearchIndex(), + SEARCH_STRING, parameters); + + try { + final String fullUrlStr = ServletUtils.getFullUrl(elasticConfig, requestString); + OperationResult opResult = ServletUtils.executeGetQuery(LOG, search, response, fullUrlStr); + + JSONObject finalOutputJson = formatOutput(opResult.getResult()); + + if (finalOutputJson != null) { + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(finalOutputJson); + out.close(); + } + + } catch (JsonProcessingException exc) { + ServletUtils.handleSearchServletErrors(LOG, "Unable to map JSONpayload", exc, response); + } + + return operationResult; + } + + /** + * Format output. + * + * @param results the results + * @return the JSON object + */ + private JSONObject formatOutput(String results) { + JsonNode resultNode = null; + JSONObject finalResult = new JSONObject(); + JSONArray entitiesArr = new JSONArray(); + + try { + resultNode = mapper.readTree(results); + + final JsonNode hitsNode = resultNode.get("hits").get("hits"); + if (hitsNode.isArray()) { + + for (final JsonNode arrayNode : hitsNode) { + JsonNode sourceNode = arrayNode.get("_source"); + if (sourceNode.get("location") != null) { + JsonNode locationNode = sourceNode.get("location"); + if (NodeUtils.isNumeric(locationNode.get("lon").asText()) + && NodeUtils.isNumeric(locationNode.get("lat").asText())) { + JSONObject location = new JSONObject(); + location.put("longitude", locationNode.get("lon").asText()); + location.put("latitude", locationNode.get("lat").asText()); + + entitiesArr.put(location); + } + + } + } + } + finalResult.put("plotPoints", entitiesArr); + + } catch (IOException exc) { + LOG.warn(AaiUiMsgs.ERROR_BUILDING_SEARCH_RESPONSE, exc.getLocalizedMessage()); + } + + return finalResult; + } +} diff --git a/src/main/java/org/openecomp/sparky/logging/AaiUiMsgs.java b/src/main/java/org/openecomp/sparky/logging/AaiUiMsgs.java new file mode 100644 index 0000000..a06559c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/logging/AaiUiMsgs.java @@ -0,0 +1,422 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.logging; + +import com.att.eelf.i18n.EELFResourceManager; + +import org.openecomp.cl.eelf.LogMessageEnum; + +/** + * The Enum AaiUiMsgs. + */ +public enum AaiUiMsgs implements LogMessageEnum { + /** Arguments: {0} = Exception/error. */ + FAILURE_TO_PROCESS_REQUEST, + /** Arguments: {0} = Message and or error body. */ + FAILED_TO_DETERMINE, + /** Arguments: {0} = Exception/error. */ + UNKNOWN_SERVER_ERROR, + /** Arguments: {0} = Message and or error body. */ + FAILED_TO_ANALYZE, + /** Arguments: {0} = Exception/error. */ + FAILED_TO_GET_NODES_QUERY_RESULT, + /** Arguments: {0} = Expected link count, {1} = Actual link count. */ + UNEXPECTED_NUMBER_OF_LINKS, + /** Arguments: {0} = Reason. */ + DANGLING_NODE_WARNING, + /** Arguments: {0} = Node count, {1} = Link count. */ + VISUALIZATION_GRAPH_OUTPUT, + /** Arguments: {0} = JsonNode. */ + ITEM_TYPE_NULL, + /** Arguments: {0} = Filter property. */ + UNEXPECTED_TOKEN_COUNT, + /** Arguments: {0} = Error/exception message. */ + ADD_SEARCH_TARGET_ATTRIBUTES_FAILED, + /** No argument */ + MAX_EVALUATION_ATTEMPTS_EXCEEDED, + /** Arguments: {0} = Error/exception message. */ + VISUALIZATION_OUTPUT_ERROR, + /** Arguments: {0} = Total resolve time, {1} = Total links retrieved, {2} = Op time. */ + ALL_TRANSACTIONS_RESOLVED, + /** Arguments: {0} = Error/exception message. */ + PROCESSING_LOOP_INTERUPTED, + /** Arguments: {0} = Node ID. */ + IGNORING_SKELETON_NODE, + /** Arguments: {0} = Node count. */ + OUTSTANDING_WORK_PENDING_NODES, + /** Arguments: {0} = Reason. */ + FAILED_TO_ADD_SKELETON_NODE, + /** Arguments: {0} = Reason. */ + FAILED_TO_PROCESS_SKELETON_NODE, + INVALID_RESOLVE_STATE_DURING_INIT, + /** Arguments: {0} = Reason. */ + FAILED_TO_PROCESS_INITIAL_STATE, + /** Arguments: {0} = Relationship. */ + SKIPPING_RELATIONSHIP, + /** Arguments: {0} = Failure reason. */ + FAILED_TO_DETERMINE_NODE_ID, + /** Arguments: {0} = Error/exception message. */ + EXTRACTION_ERROR, + /** Arguments: {0} = Error/exception message. */ + SELF_LINK_NODE_PARSE_ERROR, + /** Arguments: {0} = Node ID. */ + ROOT_NODE_DISCOVERED, + /** Arguments: {0} = Error/exception message. */ + SELF_LINK_PROCESS_NEIGHBORS_ERROR, + /** Arguments: {0} = Error/exception message. */ + SELF_LINK_JSON_PARSE_ERROR, + /** Arguments: {0} = Error/exception message. */ + SELF_LINK_PROCESSING_ERROR, + /** Arguments: {0} = Entity type. */ + UNHANDLED_OBJ_TYPE_FOR_ENTITY_TYPE, + /** Arguments: {0} = Attribute group. */ + ATTRIBUTE_GROUP_FAILURE, + /** Arguments: {0} = Situational description, {1} = Exception message. */ + EXCEPTION_CAUGHT, + /** Arguments: {0} = Operation name, {1} = Operation time. */ + OPERATION_TIME, + /** Arguments: {0} = Error message. */ + SEARCH_SERVLET_ERROR, + /** Arguments: {0} = Exception message. */ + SEARCH_RESPONSE_BUILDING_EXCEPTION, + /** Arguments: {0} = Error message, {1} = Error message. */ + SEARCH_TAG_ANNOTATION_ERROR, + /** Arguments: {0} = App type. */ + QUERY_FAILED_UNHANDLED_APP_TYPE, + /** Arguments: {0} = Entity type. */ + ENTITY_NOT_FOUND_IN_OXM, + /** Arguments: {0} = JSON conversion type, {1} = Error thrown. */ + JSON_CONVERSION_ERROR, + /** Arguments: {0} = Node ID */ + NO_RELATIONSHIP_DISCOVERED, + /** No argument */ + SELF_LINK_NULL_EMPTY_RESPONSE, + /** Arguments: {0} = Error message. */ + SELF_LINK_RELATIONSHIP_LIST_ERROR, + /** Arguments: {0} = AIN id, {1} = old depth, {2} = new depth. */ + ACTIVE_INV_NODE_CHANGE_DEPTH, + /** Arguments: {0} = Node ID, {1} = Current state, {2} = New state {3} = Triggering action */ + ACTIVE_INV_NODE_CHANGE_STATE, + /** Arguments: {0} = Current state, {1} = New state {2} = Triggering action */ + ACTIVE_INV_NODE_CHANGE_STATE_NO_NODE_ID, + /** Arguments: {0} = Count Key {1} = Aggregation Key. */ + AGGREGATION_KEY_ERROR, + /** Arguments: {0} Configuration */ + CONFIGURATION_ERROR, + /** Arguments: {0} = Source. */ + ERROR_PARSING_JSON_PAYLOAD_NONVERBOSE, + /** Arguments: {0} = Payload. */ + ERROR_PARSING_JSON_PAYLOAD_VERBOSE, + /** Arguments: {0} = Key {1} = JSON Blob. */ + ERROR_FETCHING_JSON_VALUE, + /** Arguments: {0} = Error. */ + ERROR_PARSING_PARAMS, + /** No argument */ + INVALID_REQUEST_PARAMS, + /** Arguments: {0} = Key. */ + ERROR_SORTING_VIOLATION_DATA, + /** Arguments: {0} = exception */ + ERROR_SERVLET_PROCESSSING, + /** Arguments: {0} = exception */ + ERROR_BUILDING_RESPONSE_FOR_TABLE_QUERY, + /** Arguments: {0} = exception */ + ERROR_BUILDING_SEARCH_RESPONSE, + /** No argument */ + ERROR_CSP_CONFIG_FILE, + /** Arguments: {0} = exception */ + ERROR_SHUTDOWN_EXECUTORS, + /** No argument */ + ERROR_LOADING_OXM, + /** Arguments: {0} = exception */ + ERROR_GETTING_DATA_FROM_AAI, + /** No argument */ + WAIT_FOR_ALL_SELFLINKS_TO_BE_COLLECTED, + /** Arguments: {0} = Entity Type */ + MISSING_ENTITY_DESCRIPTOR, + /** Arguments: {0} = Error */ + SELF_LINK_GET, + /** Arguments: {0} = Error */ + ES_FAILED_TO_CONSTRUCT_QUERY, + /** Arguments: {0} = Error */ + ES_RETRIEVAL_FAILED, + /** Arguments: {0} = Error */ + ES_LINK_UPSERT, + /** Arguments: {0} = Element */ + ES_SIMPLE_PUT, + /** Arguments: {0} = Value {1} = Element {2} = Error */ + ES_ABORT_CROSS_ENTITY_REF_SYNC, + /** Arguments: {0} Return Code */ + ES_OPERATION_RETURN_CODE, + /** Arguments: {0} = Error */ + ES_CROSS_ENTITY_REF_PUT, + /** No argument */ + ES_CROSS_REF_SYNC_VERSION_CONFLICT, + /** Arguments: {0} Result Code {1} = Error */ + ES_CROSS_REF_SYNC_FAILURE, + /** Arguments: {0} = Error */ + ES_FAILED_TO_CONSTRUCT_URI, + /** No argument */ + ES_RETRIEVAL_FAILED_RESYNC, + /** Arguments: {0} = Entity */ + ES_CROSS_ENTITY_RESYNC_LIMIT, + /** Arguments: {0} Entity Name */ + ES_PKEYVALUE_NULL, + /** Arguments: {0} = Error */ + ES_STORE_FAILURE, + /** Arguments: {0} Index Name {1} = Error */ + ES_PRE_SYNC_FAILURE, + /** Arguments: {0} Index Name */ + ES_SYNC_CLEAN_UP, + /** Arguments: {0} Index Name {1} Size before clean up {2} = Size after clean up */ + ES_SYNC_CLEAN_UP_SIZE, + /** Arguments: {0} Index Name {1} Index Type {2} = Size before delete */ + ES_SYNC_SELECTIVE_DELETE, + /** Arguments: {0} Index Name {1} Number of records */ + ES_BULK_DELETE, + /** Arguments: {0} Index name {1} = Error */ + ES_BULK_DELETE_ERROR, + /** Arguments: {0} Type of retrieval {1} Completion Time */ + COLLECT_TIME_WITH_ERROR, + /** Arguments: {0} Type of retrieval {1} Completion Time */ + COLLECT_TIME_WITH_SUCCESS, + /** Arguments: {0} Type of retrieval {1} Number of records */ + COLLECT_TOTAL, + /** Arguments: {0} Number of required fetches */ + SYNC_NUMBER_REQ_FETCHES, + /** Arguments: {0} Number of total fetches {1} Number of available records*/ + SYNC_NUMBER_TOTAL_FETCHES, + /** Arguments: {0} Completion Time */ + COLLECT_TOTAL_TIME, + /** Arguments: {0} = Error */ + ES_SCROLL_CONTEXT_ERROR, + /** No argument */ + ES_BULK_DELETE_SKIP, + /** Arguments: {0} = Number of docs */ + ES_BULK_DELETE_START, + /** No argument */ + SELF_LINK_CROSS_REF_SYNC, + /** Arguments: {0} = message */ + ERROR_GENERIC, + /** Arguments: {0} = error */ + JSON_PROCESSING_ERROR, + /** Arguments: {0} = exception */ + ERROR_PROCESSING_REQUEST, + /** Arguments: {0} = Self Link */ + SELF_LINK_GET_NO_RESPONSE, + /** Arguments: {0} = error */ + HISTORICAL_COLLECT_ERROR, + /** Arguments: {0} = Time */ + HISTORICAL_ENTITY_COUNT_SUMMARIZER_STARTING, + /** No argument */ + HISTORICAL_ENTITY_COUNT_SUMMARIZER_NOT_STARTED, + /** Arguments: {0} = Controller {1} = Time */ + HISTORICAL_SYNC_DURATION, + /** No argument */ + HISTORICAL_SYNC_PENDING, + /** Arguments: {0} = Time */ + HISTORICAL_SYNC_TO_BEGIN, + /** Arguments: {0} = message */ + DEBUG_GENERIC, + /** Arguments: {0} = message */ + INFO_GENERIC, + /** Arguments: {0} = message */ + WARN_GENERIC, + /** Arguments: {0} = context {1} = Exception*/ + INTERRUPTED, + /** Arguments: {0} = Entity Type {1} Entity */ + GEO_SYNC_IGNORING_ENTITY, + /** Arguments: {0} = type */ + OXM_FAILED_RETRIEVAL, + /** Arguments: {0} = Directory. */ + OXM_FILE_NOT_FOUND, + /** No argument */ + OXM_READ_ERROR_NONVERBOSE, + /** Arguments: {0} = OXM File name */ + OXM_READ_ERROR_VERBOSE, + /** No argument */ + OXM_PARSE_ERROR_NONVERBOSE, + /** Arguments: {0} = OXM File name {1} = Exception*/ + OXM_PARSE_ERROR_VERBOSE, + /** No argument */ + OXM_LOAD_SUCCESS, + /** Arguments: {0} = Entity {1} = Found property-value*/ + OXM_PROP_DEF_ERR_CROSS_ENTITY_REF, + /** Arguments: {0} = Sequence Number */ + ETAG_RETRY_SEQ, + /** Arguments: {0} = Reason */ + ETAG_WAIT_INTERRUPTION, + /** Arguments: {0} = URL {1} = Sequence Number */ + QUERY_AAI_RETRY_SEQ, + /** Arguments: {0} = URL {1} = Sequence Number */ + QUERY_AAI_RETRY_DONE_SEQ, + /** Arguments: {0} = Reason */ + QUERY_AAI_WAIT_INTERRUPTION, + /** Arguments: {0} = URL {1} = Sequence Number */ + QUERY_AAI_RETRY_FAILURE_WITH_SEQ, + /** Arguments: {0} = URL */ + QUERY_AAI_RETRY_MAXED_OUT, + /** Arguments: {0} = Reason */ + PEGGING_ERROR, + /** Arguments: {0} = Key */ + DATA_CACHE_SUCCESS, + /** Arguments: {0} = URL {1} = Sequence Number */ + EXECUTOR_SERV_EXCEPTION, + /** Arguments: {0} = Exception */ + DISK_CACHE_READ_IO_ERROR, + /** Arguments: {0} = Exception */ + DISK_CREATE_DIR_IO_ERROR, + /** Arguments: {0} = Exception */ + DISK_DATA_WRITE_IO_ERROR, + /** Arguments: {0} = Data Item {1} = Exception */ + DISK_NAMED_DATA_WRITE_IO_ERROR, + /** Arguments: {0} = Data Item {1} = Exception */ + DISK_NAMED_DATA_READ_IO_ERROR, + /** No argument */ + OFFLINE_STORAGE_PATH_ERROR, + /** Arguments: {0} = URL {1} = Error */ + RESTFULL_OP_ERROR_VERBOSE, + /** Arguments: {0} = Method {1} = Time {2} = URL {3} = Result Code */ + RESTFULL_OP_COMPLETE, + /** No argument */ + INITIALIZE_OXM_MODEL_LOADER, + /** Arguments: {0} = Exception */ + AAI_RETRIEVAL_FAILED_GENERIC, + /** Arguments: {0} = Self Link */ + AAI_RETRIEVAL_FAILED_FOR_SELF_LINK, + /** Arguments: {0} = Cookie */ + COOKIE_FOUND, + /** No argument */ + COOKIE_NOT_FOUND, + /** Arguments: {0} = Message */ + INVALID_REQUEST, + /** Arguments: {0} = User ID */ + USER_AUTHORIZATION_FILE_UNAVAILABLE, + /** Arguments: {0} = URL {1} = Cause */ + INVALID_URL_VERBOSE, + /** Arguments: {0} = Row ID */ + DI_DATA_NOT_FOUND_NONVERBOSE, + /** Arguments: {0} = Row ID {1} Attempt count */ + DI_DATA_NOT_FOUND_VERBOSE, + /** Arguments: {0} = Time in ms {1} Status */ + DI_MS_TIME_FOR_DATA_FETCH, + /** Arguments: {0} = Number of Entity Links */ + ENTITY_SYNC_FAILED_SELFLINK_AMBIGUITY, + /** Arguments: {0} = Message */ + ERROR_EXTRACTING_FROM_RESPONSE, + /** No argument */ + ERROR_LOADING_OXM_SEARCHABLE_ENTITIES, + /** Arguments: {0} = Message */ + ES_SEARCHABLE_ENTITY_SYNC_ERROR, + /** Arguments: {0} = Message */ + FAILED_TO_REGISTER_DUE_TO_NULL, + /** Arguments: {0} = File Path */ + FAILED_TO_RESTORE_TXN_FILE_MISSING, + /** Arguments: {0} = Index Name */ + INDEX_ALREADY_EXISTS, + /** Arguments: {0} = Index Name */ + INDEX_EXISTS, + /** Arguments: {0} = Index Name {1} = Operation Result */ + INDEX_INTEGRITY_CHECK_FAILED, + /** Arguments: {0} = Index Name */ + INDEX_NOT_EXIST, + /** Arguments: {0} = Index Name */ + INDEX_RECREATED, + /** Arguments: {0} = Time */ + SEARCH_ENGINE_SYNC_STARTED, + /** Arguments: {0} = Time */ + SKIP_PERIODIC_SYNC_AS_SYNC_DIDNT_FINISH, + /** Arguments: {0} = Message */ + SYNC_DURATION, + /** Arguments: {0} = Entity Type */ + ENTITY_SYNC_FAILED_DESCRIPTOR_NOT_FOUND, + /** Arguments: {0} = AAI Query Result */ + ENTITY_SYNC_FAILED_DURING_AAI_RESPONSE_CONVERSION, + /** Arguments: {0} = Message */ + ENTITY_SYNC_FAILED_QUERY_ERROR, + /** Arguments: {0} = Self Link Query */ + SELF_LINK_DETERMINATION_FAILED_GENERIC, + /** Arguments: {0} = Number of Entity Links */ + SELF_LINK_DETERMINATION_FAILED_UNEXPECTED_LINKS, + /** Arguments: {1} = Query {2} = Operation Result Code {3} = Operation Result */ + SELF_LINK_RETRIEVAL_FAILED, + /** Arguments: {0} = Controller {1} = Synchronizer Current Internal State {2} = New State {3} = Caused By Action */ + SYNC_INTERNAL_STATE_CHANGED, + /** Arguments: {0} = Message */ + SYNC_INVALID_CONFIG_PARAM, + /** Arguments: {0} = Synchronizer Current Internal State */ + SYNC_NOT_VALID_STATE_DURING_REQUEST, + /** No argument */ + SYNC_SKIPPED_SYNCCONTROLLER_NOT_INITIALIZED, + /** No argument */ + SYNC_START_TIME, + /** Arguments: {0} = Controller {1} = Time */ + SYNC_TO_BEGIN, + /** Arguments: {0} = File Path */ + WILL_RETRIEVE_TXN, + /** Arguments: {0} = Configuration file name {1} = Exception */ + CONFIG_NOT_FOUND_VERBOSE, + /** Arguments: {0} = File name */ + FILE_NOT_FOUND, + /** Arguments: {0} = File name */ + FILE_READ_IN_PROGRESS, + ERROR_LOADING_OXM_SUGGESTIBLE_ENTITIES, + /** Arguments: {0} = Error message */ + ES_SUGGESTION_SEARCH_ENTITY_SYNC_ERROR, + /** Arguments: {0} = Error message */ + ES_AGGREGATION_SUGGESTION_ENTITY_SYNC_ERROR, + /** Arguments: {0} = Error message. */ + ENTITY_SYNC_SEARCH_TAG_ANNOTATION_FAILED, + /** Arguments: {0} = Error message */ + SEARCH_ADAPTER_ERROR, + /** Arguments: {0} = Decoding exception message */ + UNSUPPORTED_URL_ENCODING, + /** Arguments: {0} = Invalid URL */ + INVALID_REDIRECT_URL, + /** Arguments: {0} = Valid login URL */ + VALID_REDIRECT_URL, + /** Arguments: {0} = Query Parameter Self-Link Extraction Error */ + QUERY_PARAM_EXTRACTION_ERROR, + /** Arguments: {0} = Info message */ + LOGIN_FILTER_INFO, + /** Arguments: {0} = Debug message */ + LOGIN_FILTER_DEBUG, + /** Arguments: {0} = URL to extract parameter from */ + ERROR_REMOVING_URL_PARAM, + /** Arguments: {0} = Hash value */ + ERROR_INVALID_HASH, + ERROR_HASH_NOT_FOUND, + ERROR_READING_HTTP_REQ_PARAMS, + /** Arguments: {0} = Exception */ + ERROR_D3_GRAPH_VISUALIZATION, + /** Arguments: {0} = Exception */ + ERROR_AAI_QUERY_WITH_RETRY; + + /** + * Static initializer to ensure the resource bundles for this class are loaded... + */ + static { + EELFResourceManager.loadMessageBundle("logging/AAIUIMsgs"); + } +} diff --git a/src/main/java/org/openecomp/sparky/logging/util/LoggingUtils.java b/src/main/java/org/openecomp/sparky/logging/util/LoggingUtils.java new file mode 100644 index 0000000..13f2337 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/logging/util/LoggingUtils.java @@ -0,0 +1,44 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.logging.util; + +/** + * The Class LoggingUtils. + */ +public class LoggingUtils { + + /** + * Sets the duration. + * + * @param startTime the start time + * @param stopTime the stop time + * @return the string + */ + public static String setDuration(long startTime, long stopTime) { + return String.valueOf(stopTime - startTime); + } + +} diff --git a/src/main/java/org/openecomp/sparky/search/EntityTypeSummary.java b/src/main/java/org/openecomp/sparky/search/EntityTypeSummary.java new file mode 100644 index 0000000..aa79f3d --- /dev/null +++ b/src/main/java/org/openecomp/sparky/search/EntityTypeSummary.java @@ -0,0 +1,53 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.search; + +import java.util.ArrayList; +import java.util.List; + +public class EntityTypeSummary { + private int totalChartHits; + private List buckets = new ArrayList<>(); + + public int getTotalChartHits() { + return totalChartHits; + } + + public List getBuckets() { + return buckets; + } + + public void setTotalChartHits(int totalChartHits) { + this.totalChartHits = totalChartHits; + } + + public void setBuckets(List buckets) { + this.buckets = buckets; + } + + public void addBucket(EntityTypeSummaryBucket bucket) { + this.buckets.add(bucket); + } +} diff --git a/src/main/java/org/openecomp/sparky/search/EntityTypeSummaryBucket.java b/src/main/java/org/openecomp/sparky/search/EntityTypeSummaryBucket.java new file mode 100644 index 0000000..540b300 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/search/EntityTypeSummaryBucket.java @@ -0,0 +1,46 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.search; + +public class EntityTypeSummaryBucket { + private int count; + private String key; + + public int getCount() { + return count; + } + + public String getKey() { + return key; + } + + public void setCount(int count) { + this.count = count; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/src/main/java/org/openecomp/sparky/search/SearchEntityProperties.java b/src/main/java/org/openecomp/sparky/search/SearchEntityProperties.java new file mode 100644 index 0000000..bcf46f9 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/search/SearchEntityProperties.java @@ -0,0 +1,49 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.search; + +import java.util.HashMap; +import java.util.Map; + +public class SearchEntityProperties { + private String type; + private Map fields = new HashMap<>(); + + public String getType() { + return type; + } + + public Map getFields() { + return fields; + } + + public void setType(String type) { + this.type = type; + } + + public void setFields(Map field) { + this.fields = field; + } +} diff --git a/src/main/java/org/openecomp/sparky/search/Suggestion.java b/src/main/java/org/openecomp/sparky/search/Suggestion.java new file mode 100644 index 0000000..79eb240 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/search/Suggestion.java @@ -0,0 +1,59 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.search; + +public class Suggestion { + private String entityType; + private String searchTags; + private SearchEntityProperties properties; + + public Suggestion(SearchEntityProperties properties) { + this.properties = properties; + } + + public String getEntityType() { + return entityType; + } + + public String getSearchTags() { + return searchTags; + } + + public SearchEntityProperties getProperties() { + return properties; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public void setSearchTags(String searchTags) { + this.searchTags = searchTags; + } + + public void setProperties(SearchEntityProperties properties) { + this.properties = properties; + } +} diff --git a/src/main/java/org/openecomp/sparky/search/SuggestionList.java b/src/main/java/org/openecomp/sparky/search/SuggestionList.java new file mode 100644 index 0000000..cd56099 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/search/SuggestionList.java @@ -0,0 +1,72 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.search; + +import java.util.LinkedList; +import java.util.List; + +public class SuggestionList { + // TODO: verify which data type these fields should be + private Long processingTimeInMs; + private Long totalFound; + private Long numReturned; + private List suggestions = new LinkedList<>(); + + public void addSuggestion(Suggestion suggestion) { + suggestions.add(suggestion); + } + + public List getSuggestions() { + return suggestions; + } + + public void setSuggestions(List suggestions) { + this.suggestions = suggestions; + } + + public Long getProcessingTimeInMs() { + return processingTimeInMs; + } + + public Long getTotalFound() { + return totalFound; + } + + public Long getNumReturned() { + return numReturned; + } + + public void setProcessingTimeInMs(Long processingTimeInMs) { + this.processingTimeInMs = processingTimeInMs; + } + + public void setTotalFound(Long totalFound) { + this.totalFound = totalFound; + } + + public void setNumReturned(Long numReturned) { + this.numReturned = numReturned; + } +} \ No newline at end of file diff --git a/src/main/java/org/openecomp/sparky/search/VnfSearchQueryBuilder.java b/src/main/java/org/openecomp/sparky/search/VnfSearchQueryBuilder.java new file mode 100644 index 0000000..55d003e --- /dev/null +++ b/src/main/java/org/openecomp/sparky/search/VnfSearchQueryBuilder.java @@ -0,0 +1,200 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.search; + +import java.util.Date; +import java.util.Map; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +/** + * Build a JSON payload to send to elastic search to get vnf search data. + */ + +public class VnfSearchQueryBuilder { + + /** + * Creates the suggestions query. + * + * @param maxResults maximum number of suggestions to fetch + * @param queryStr query string + * @return the json object + */ + + /* + * { "vnfs" : { "text" : "VNFs", "completion" : { "field" : "entity_suggest", "size": 1 } } } + */ + public static JsonObject createSuggestionsQuery(String maxResults, String queryStr) { + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder(); + + JsonObjectBuilder completionBlob = Json.createObjectBuilder(); + completionBlob.add("field", "entity_suggest"); + completionBlob.add("size", maxResults); + + JsonObjectBuilder jsonAllBuilder = Json.createObjectBuilder(); + jsonAllBuilder.add("text", queryStr); + jsonAllBuilder.add("completion", completionBlob); + + jsonBuilder.add("vnfs", jsonAllBuilder.build()); + return jsonBuilder.build(); + } + + public static JsonObject getTermBlob(String key, String value) { + JsonObjectBuilder termBlobBuilder = Json.createObjectBuilder(); + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder().add(key, value); + return termBlobBuilder.add("term", jsonBuilder.build()).build(); + } + + public static void getSummaryAggsBlob(JsonObjectBuilder aggsBlobBuilder, String aggsKey, + int resultSize) { + JsonObjectBuilder fieldBuilder = + Json.createObjectBuilder().add("field", aggsKey).add("size", resultSize); + JsonObject aggsFieldBlob = fieldBuilder.build(); + JsonObjectBuilder defaultBlobBuilder = Json.createObjectBuilder().add("terms", aggsFieldBlob); + JsonObject defaultBlob = defaultBlobBuilder.build(); + aggsBlobBuilder.add("default", defaultBlob); + } + + public static void buildSingleTermCountQuery(JsonObjectBuilder jsonBuilder, String key, + String value) { + jsonBuilder.add("query", getTermBlob(key, value)); + } + + public static void buildSingleTermSummaryQuery(JsonObjectBuilder jsonBuilder, String key, + String value, String groupByKey) { + JsonObjectBuilder queryBlobBuilder = Json.createObjectBuilder(); + JsonObjectBuilder aggsBlobBuilder = Json.createObjectBuilder(); + + queryBlobBuilder.add("constant_score", + Json.createObjectBuilder().add("filter", getTermBlob(key, value))); + + getSummaryAggsBlob(aggsBlobBuilder, groupByKey, 0); + + jsonBuilder.add("query", queryBlobBuilder.build()); + jsonBuilder.add("aggs", aggsBlobBuilder.build()); + } + + public static void buildMultiTermSummaryQuery(JsonObjectBuilder jsonBuilder, + Map attributes, String groupByKey) { + JsonObjectBuilder queryBlobBuilder = Json.createObjectBuilder(); + JsonObjectBuilder aggsBlobBuilder = Json.createObjectBuilder(); + JsonArrayBuilder mustBlobBuilder = Json.createArrayBuilder(); + for (String key : attributes.keySet()) { + mustBlobBuilder.add(getTermBlob(key, attributes.get(key))); + } + JsonArray mustBlob = mustBlobBuilder.build(); + + queryBlobBuilder.add("constant_score", Json.createObjectBuilder().add("filter", + Json.createObjectBuilder().add("bool", Json.createObjectBuilder().add("must", mustBlob)))); + + getSummaryAggsBlob(aggsBlobBuilder, groupByKey, 0); + + jsonBuilder.add("query", queryBlobBuilder.build()); + jsonBuilder.add("aggs", aggsBlobBuilder.build()); + } + + public static void buildZeroTermSummaryQuery(JsonObjectBuilder jsonBuilder, String groupByKey) { + JsonObjectBuilder aggsBlobBuilder = Json.createObjectBuilder(); + + getSummaryAggsBlob(aggsBlobBuilder, groupByKey, 0); + + jsonBuilder.add("aggs", aggsBlobBuilder.build()); + } + + public static void buildMultiTermCountQuery(JsonObjectBuilder jsonBuilder, + Map attributes) { + JsonArrayBuilder mustBlobBuilder = Json.createArrayBuilder(); + for (String key : attributes.keySet()) { + mustBlobBuilder.add(getTermBlob(key, attributes.get(key))); + } + jsonBuilder.add("query", Json.createObjectBuilder().add("bool", + Json.createObjectBuilder().add("must", mustBlobBuilder))); + } + + + + public static JsonObject createSummaryByEntityTypeQuery(Map attributes, + String groupByKey) { + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder(); + jsonBuilder.add("size", "0"); // avoid source data + if (attributes.size() == 0) { + buildZeroTermSummaryQuery(jsonBuilder, groupByKey); + } else if (attributes.size() == 1) { + Map.Entry entry = attributes.entrySet().iterator().next(); + buildSingleTermSummaryQuery(jsonBuilder, entry.getKey(), entry.getValue(), groupByKey); + } else { + buildMultiTermSummaryQuery(jsonBuilder, attributes, groupByKey); + } + return jsonBuilder.build(); + } + + public static JsonObject createEntityCountsQuery(Map attributes) { + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder(); + if (attributes.size() == 1) { + Map.Entry entry = attributes.entrySet().iterator().next(); + buildSingleTermCountQuery(jsonBuilder, entry.getKey(), entry.getValue()); + } else { + buildMultiTermCountQuery(jsonBuilder, attributes); + } + return jsonBuilder.build(); + } + + public static JsonArray getSortCriteria(String sortFieldName, String sortOrder) { + JsonArrayBuilder jsonBuilder = Json.createArrayBuilder(); + jsonBuilder.add(Json.createObjectBuilder().add(sortFieldName, + Json.createObjectBuilder().add("order", sortOrder))); + + return jsonBuilder.build(); + } + + + /** + * The main method. + * + * @param args the arguments + */ + public static void main(String[] args) { + Date start = new Date(System.currentTimeMillis() - Integer.MAX_VALUE); + Date end = new Date(); + String timezone = "-05:00"; + // JsonObject arr = createDateHistogramQuery(start, end, timezone); + + // System.out.println(arr.toString()); + + + // JsonObject table = createTableQuery(start, end, timezone, 0, 25); + // JsonObject aggre = createAuditQuery(start, end, timezone, "entityType", null, null); + + // System.out.println(arr.toString()); + // System.out.println(table.toString()); + // System.out.println(aggre.toString()); + + + } +} diff --git a/src/main/java/org/openecomp/sparky/search/VnfSearchService.java b/src/main/java/org/openecomp/sparky/search/VnfSearchService.java new file mode 100644 index 0000000..1cef43c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/search/VnfSearchService.java @@ -0,0 +1,348 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.search; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.MediaType; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.elasticsearch.HashQueryResponse; +import org.openecomp.sparky.dal.elasticsearch.SearchAdapter; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.suggestivesearch.SuggestionEntity; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; +import org.openecomp.sparky.viewandinspect.entity.QuerySearchEntity; + + +/** + * From the given HTTP request, create vnf-search query for document store, and process document + * store response. + */ + +public class VnfSearchService { + + private static final String APP_JSON = MediaType.APPLICATION_JSON; + + private static ElasticSearchConfig esConfig = null; + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(VnfSearchService.class); + + private static SearchAdapter search = null; + private static final String ES_SUGGEST_API = TierSupportUiConstants.ES_SUGGEST_API; + private static final String ES_COUNT_API = TierSupportUiConstants.ES_COUNT_API; + private static final String ES_SEARCH_API = TierSupportUiConstants.ES_SEARCH_API; + + private static final String ENTITY_TYPE = "generic-vnf"; + + /** + * Get Full URL for search using elastic search configuration. + * + * @param api the api + * @return the full url + */ + private static String getFullUrl(String indexName, String api) { + + final String host = esConfig.getIpAddress(); + final String port = esConfig.getHttpPort(); + return String.format("http://%s:%s/%s/%s", host, port, indexName, api); + } + + /** + * Process operation result. + * + * @param api the api + * @param response the response + * @param opResult the op result + * @throws IOException Signals that an I/O exception has occurred. + */ + private static void buildVnfQuerySearchResponse(String apiKey, HttpServletResponse response, + OperationResult opResult) throws IOException { + int resonseCode = opResult.getResultCode(); + String result = opResult.getResult(); + + if (resonseCode > 300) { + setServletResponse(true, resonseCode, response, result); + return; + } + + if (result != null) { + JSONObject finalOutputToFe = new JSONObject(); + JSONObject responseJson = new JSONObject(result); + + if (apiKey.equalsIgnoreCase(ES_SUGGEST_API)) { // process suggestion results + try { + String suggestionsKey = "vnfs"; + int total = 0; + JSONArray suggestionsArray = new JSONArray(); + JSONArray suggestions = responseJson.getJSONArray(suggestionsKey); + if (suggestions.length() > 0) { + suggestionsArray = suggestions.getJSONObject(0).getJSONArray("options"); + for (int i = 0; i < suggestionsArray.length(); i++) { + suggestionsArray.getJSONObject(i).remove("score"); // FE doesn't like this noise: 'score' + } + + total = suggestionsArray.length(); + } + finalOutputToFe.put("totalFound", total); + finalOutputToFe.put("suggestions", suggestionsArray); + } catch (Exception e) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Error parsing response from suggestions index. Response: " + result); + } + } else if (apiKey.equalsIgnoreCase(ES_COUNT_API)) { + try { + String shardsKey = "_shards"; + responseJson.remove(shardsKey); + finalOutputToFe = responseJson; + } catch (Exception e) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Error fetching total count response from aggregation index. Response: " + result); + } + } else if (apiKey.equalsIgnoreCase(ES_SEARCH_API)) { + try { + JSONArray bucketsArray = (responseJson.getJSONObject("aggregations") + .getJSONObject("default").getJSONArray("buckets")); + int count = 0; + for (int i=0; i< bucketsArray.length(); i++) { + count += bucketsArray.getJSONObject(i).getInt("doc_count"); + } + JSONObject content = new JSONObject(); + content.put("totalChartHits", count); + content.put("buckets", bucketsArray); + finalOutputToFe.put("groupby_aggregation", content); + } catch (Exception e) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Error fetching group-by query response from aggregation index. Response: " + result); + } + } + + setServletResponse(false, resonseCode, response, finalOutputToFe.toString()); + } + } + + /** + * Sets the servlet response. + * + * @param isError the is error + * @param responseCode the response code + * @param response the response + * @param postPayload the post payload + * @throws IOException Signals that an I/O exception has occurred. + */ + public static void setServletResponse(boolean isError, int responseCode, + HttpServletResponse response, String postPayload) throws IOException { + + if (isError) { + LOG.error(AaiUiMsgs.ERROR_PARSING_JSON_PAYLOAD_VERBOSE, postPayload); + } + + response.setStatus(responseCode); + + if (postPayload != null) { + response.setContentType(APP_JSON); + PrintWriter out = response.getWriter(); + out.println(postPayload); + out.close(); + } + } + + /** + * Instantiates a new vnf search service. + */ + public VnfSearchService() { + try { + if (esConfig == null) { + esConfig = ElasticSearchConfig.getConfig(); + } + + if (search == null) { + search = new SearchAdapter(); + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.CONFIGURATION_ERROR, "Search"); + } + } + + + /** + * Gets the suggestions results. + * + * @param response the response + * @param maxResults maximum number of suggestions + * @param queryStr query string + * @return the suggestions results + * @throws IOException Signals that an I/O exception has occurred. + */ + public List getSuggestionsResults(QuerySearchEntity querySearchEntity, int resultCountLimit) throws IOException { + List returnList = new ArrayList(); + + /* Create suggestions query */ + JsonObject vnfSearch = VnfSearchQueryBuilder.createSuggestionsQuery(String.valueOf(resultCountLimit), querySearchEntity.getQueryStr()); + + /* Parse suggestions response */ + OperationResult opResult = search.doPost(getFullUrl(esConfig.getAutosuggestIndexname(), ES_SUGGEST_API), vnfSearch.toString(), APP_JSON); + + String result = opResult.getResult(); + + if (!opResult.wasSuccessful()) { + LOG.error(AaiUiMsgs.ERROR_PARSING_JSON_PAYLOAD_VERBOSE, result); + return returnList; + } + + JSONObject responseJson = new JSONObject(result); + String suggestionsKey = "vnfs"; + JSONArray suggestionsArray = new JSONArray(); + JSONArray suggestions = responseJson.getJSONArray(suggestionsKey); + if (suggestions.length() > 0) { + suggestionsArray = suggestions.getJSONObject(0).getJSONArray("options"); + for (int i = 0; i < suggestionsArray.length(); i++) { + JSONObject querySuggestion = suggestionsArray.getJSONObject(i); + if (querySuggestion != null) { + SuggestionEntity responseSuggestion = new SuggestionEntity(); + responseSuggestion.setText(querySuggestion.getString("text")); + responseSuggestion.setRoute("vnfSearch"); // TODO -> Read route from suggestive-search.properties instead of hard coding + responseSuggestion.setHashId(NodeUtils.generateUniqueShaDigest(querySuggestion.getString("text"))); + returnList.add(responseSuggestion); + } + } + } + return returnList; + } + + + /** + * This method sets server response if lookup in ES has 0 count + * TODO: Change the response code to appropriate when FE-BE contract is finalized + * @param response + */ + public void setZeroCountResponse(HttpServletResponse response) throws IOException { + JSONObject payload = new JSONObject(); + payload.put("count", 0); + setServletResponse(false, 200, response, payload.toString() ); + } + + /** + * This method sets server response if lookup in ES for an aggregation has 0 results + * TODO: Change the response code to appropriate when FE-BE contract is finalized + * @param response + */ + public void setEmptyAggResponse(HttpServletResponse response) throws IOException { + JSONObject aggPayload = new JSONObject(); + aggPayload.put("totalChartHits", 0); + aggPayload.put("buckets", new JSONArray()); + JSONObject payload = new JSONObject(); + payload.append("groupby_aggregation", aggPayload); + setServletResponse(false, 200, response, payload.toString() ); + } + + public HashQueryResponse getJSONPayloadFromHash(String hashId) { + + HashQueryResponse hashQueryResponse = new HashQueryResponse(); + JsonObjectBuilder hashSearch = Json.createObjectBuilder(); + VnfSearchQueryBuilder.buildSingleTermCountQuery(hashSearch, "_id", hashId); + String hashSearchQuery = hashSearch.build().toString(); + OperationResult opResult = search.doPost( + getFullUrl(esConfig.getAutosuggestIndexname(), ES_SEARCH_API), + hashSearchQuery, APP_JSON); + hashQueryResponse.setOpResult(opResult); + + if(opResult != null && opResult.wasSuccessful()){ + String result = opResult.getResult(); + if (result != null) { + JSONObject responseJson = new JSONObject(result); + JSONArray hits = responseJson.getJSONObject("hits").getJSONArray("hits"); + if(hits != null && hits.length() > 0){ + hashQueryResponse.setJsonPayload (hits.getJSONObject(0).getJSONObject("_source") + .getJSONObject("entity_suggest").toString()); + } + } + } + return hashQueryResponse; + } + + public void getEntityCountResults(HttpServletResponse response, Map attributes) + throws IOException { + // Create entity counts query + JsonObject vnfSearch = VnfSearchQueryBuilder.createEntityCountsQuery(attributes); + + // Parse response for entity counts query + OperationResult opResult = search.doPost( + getFullUrl(TierSupportUiConstants.getAggregationIndexName(ENTITY_TYPE), ES_COUNT_API), + vnfSearch.toString(), APP_JSON); + buildVnfQuerySearchResponse(ES_COUNT_API, response, opResult); + } + + public void getSummaryByEntityType(HttpServletResponse response, Map attributes, + String groupByKey) throws IOException { + // Create query for summary by entity type + JsonObject vnfSearch = + VnfSearchQueryBuilder.createSummaryByEntityTypeQuery(attributes, groupByKey); + + // Parse response for summary by entity type query + OperationResult opResult = search.doPost( + getFullUrl(TierSupportUiConstants.getAggregationIndexName(ENTITY_TYPE), ES_SEARCH_API), + vnfSearch.toString(), APP_JSON); + buildVnfQuerySearchResponse(ES_SEARCH_API, response, opResult); + } + + public SearchAdapter getSearch() { + return search; + } + + public void setSearch(SearchAdapter search) { + VnfSearchService.search = search; + } + + public static ElasticSearchConfig getEsConfig() { + return esConfig; + } + + public static void setEsConfig(ElasticSearchConfig esConfig) { + VnfSearchService.esConfig = esConfig; + } + + public static void main(String agrs[]) { + VnfSearchService vnfs = new VnfSearchService(); + Date start = new Date(); + Date end = start; + } + +} diff --git a/src/main/java/org/openecomp/sparky/search/config/SuggestionConfig.java b/src/main/java/org/openecomp/sparky/search/config/SuggestionConfig.java new file mode 100644 index 0000000..c9dbc6e --- /dev/null +++ b/src/main/java/org/openecomp/sparky/search/config/SuggestionConfig.java @@ -0,0 +1,143 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.search.config; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import org.openecomp.sparky.util.ConfigHelper; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +public class SuggestionConfig { + public static final String CONFIG_FILE = + TierSupportUiConstants.DYNAMIC_CONFIG_APP_LOCATION + "suggestive-search.properties"; + + private static SuggestionConfig config; + private static final String INDEX_SEARCH_MAPPER_DEFAULT = "elasticsearch.autosuggestIndexname:SearchServiceWrapper,elasticsearch.indexName:VnfSearchService"; + + private Map searchIndexToSearchService; + + private static final String CALLED_PAIRING_KEY_DEFAULT = "volume-group-id,volume-group-name,physical-location-id,data-center-code,complex-name,tenant-id,tenant-name,vserver-id,vserver-name,vserver-name2,hostname,pserver-name2,pserver-id,global-customer-id,subscriber-name,service-instance-id,service-instance-name,link-name,vpn-id,vpn-name,vpe-id,vnf-id,vnf-name,vnf-name2,vnfc-name,network-id,network-name,network-policy-id,vf-module-id,vf-module-name,vnf-id2,pnf-name,circuit-id"; + private static final String CALLED_PAIRING_VALUE_DEFAULT = "called"; + private static final String AT_PAIRING_KEY_DEFAULT = "street1,street2,postal-code,ipv4-oam-address,network-policy-fqdn"; + private static final String AT_PAIRING_VALUE_DEFAULT = "at"; + private static final String DEFAULT_PAIRING_DEFAULT_VALUE = "with"; + private String conjunctionForAt; + Map pairingList; + private Collection stopWords; + private String defaultPairingValue; + + + private SuggestionConfig() {} + + /** + * Returns initialized instance as per singleton pattern. + * + * @return initialized SuggestionConfig instance + */ + public static SuggestionConfig getConfig() { + if (config == null) { + config = new SuggestionConfig(); + config.initializeConfigProperties(); + } + return config; + } + + public void initializeConfigProperties() { + + Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); + Properties suggestionProps = ConfigHelper.getConfigWithPrefix("suggestion", props); + + String indexSearchMapper = suggestionProps.getProperty("routing", INDEX_SEARCH_MAPPER_DEFAULT); + String[] indexesToSearchClassesArray = indexSearchMapper.split(","); + searchIndexToSearchService = new HashMap(); + for (String pair : indexesToSearchClassesArray) { + String[] subPair = pair.split(":"); + searchIndexToSearchService.put(subPair[0], subPair[1]); + } + + defaultPairingValue=suggestionProps.getProperty("pairing.default.value", DEFAULT_PAIRING_DEFAULT_VALUE); + String calledValue = suggestionProps.getProperty("pairing.called.value", CALLED_PAIRING_VALUE_DEFAULT); + String[] calledPairingArray = suggestionProps.getProperty("pairing.called.key", CALLED_PAIRING_KEY_DEFAULT).split(","); + pairingList = new HashMap(); + for(String calledField: calledPairingArray){ + pairingList.put(calledField, calledValue); + } + + this.conjunctionForAt = suggestionProps.getProperty("pairing.at.value", AT_PAIRING_VALUE_DEFAULT); + String[] atPairingArray = suggestionProps.getProperty("pairing.at.key", AT_PAIRING_KEY_DEFAULT).split(","); + for(String atField: atPairingArray){ + pairingList.put(atField, conjunctionForAt); + } + + stopWords = Arrays.asList(suggestionProps.getProperty("stopwords", "").split(",")); + + } + + public void setSearchIndexToSearchService(Map searchIndexToSearchService) { + this.searchIndexToSearchService = searchIndexToSearchService; + } + + public Map getSearchIndexToSearchService() { + return searchIndexToSearchService; + } + + public Collection getStopWords() { + return stopWords; + } + + public void setStopWords(Collection stopWords) { + this.stopWords = stopWords; + } + + public Map getPairingList() { + return pairingList; + } + + public void setPairingList(Map pairingList) { + this.pairingList = pairingList; + } + + public String getDefaultPairingValue() { + return defaultPairingValue; + } + + public void setDefaultPairingValue(String defaultPairingValue) { + this.defaultPairingValue = defaultPairingValue; + } + + public String getConjunctionForAt() { + return conjunctionForAt; + } + + public void setConjunctionForAt(String conjunctionForAt) { + this.conjunctionForAt = conjunctionForAt; + } + + +} diff --git a/src/main/java/org/openecomp/sparky/security/EcompSso.java b/src/main/java/org/openecomp/sparky/security/EcompSso.java new file mode 100644 index 0000000..a008066 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/EcompSso.java @@ -0,0 +1,160 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.security; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.portalsdk.core.onboarding.util.PortalApiProperties; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.security.portal.config.PortalAuthenticationConfig; +import org.openecomp.portalsdk.core.onboarding.util.CipherUtil; + + +/** + * Provides authentication services for onboarded ECOMP applications. + */ +public class EcompSso { + + public static final String EP_SERVICE = "EPService"; + public static final String CSP_COOKIE_NAME = "csp_cookie_name"; + public static final String CSP_GATE_KEEPER_PROD_KEY = "csp_gate_keeper_prod_key"; + public static final String ONAP_ENABLED = "ONAP_ENABLED"; + private static final Logger LOG = LoggerFactory.getInstance().getLogger(EcompSso.class); + + /** + * Searches the request for a cookie with the specified name. + * + * @param request + * @param cookieName + * @return Cookie, or null if not found. + */ + public static Cookie getCookie(HttpServletRequest request, String cookieName) { + Cookie[] cookies = request.getCookies(); + if (cookies != null) + for (Cookie cookie : cookies) { + if (cookie.getName().equals(cookieName)) { + return cookie; + } + } + + return null; + } + + /** + * Answers whether the ECOMP Portal service cookie is present in the specified request. + * + * @param request + * @return true if the cookie is found, else false. + */ + private static boolean isEPServiceCookiePresent(HttpServletRequest request) { + Cookie ep = getCookie(request, EP_SERVICE); + return (ep != null); + } + + /** + * Validates whether the ECOMP Portal sign-on process has completed, which relies the AT&T Global + * Log On single-sign on process. Checks for the ECOMP cookie (see {@link #EP_SERVICE}). If found, + * then searches for a CSP cookie; if not found, for a WebJunction header. + * + * @param request + * @return ATT UID if the ECOMP cookie is present and the sign-on process established an ATT UID; + * else null. + */ + public static String validateEcompSso(HttpServletRequest request) { + boolean isOnapEnabled = PortalAuthenticationConfig.getInstance().getIsOnapEnabled(); + if (isOnapEnabled) { + if (isEPServiceCookiePresent(request)) { + /* This is a "temporary" fix until proper separation + * between closed source and open source code is reached */ + return ONAP_ENABLED; + } + return null; + } else { + return getLoginIdFromCookie(request); + } + } + + /** + * Searches the specified request for the CSP cookie, decodes it and gets the ATT UID. + * + * @param request + * @return ATTUID if the cookie is present in the request and can be decoded successfully (expired + * cookies do not decode); else null. + */ + private static String getLoginIdFromCookie(HttpServletRequest request) { + String attuid = null; + try { + String[] cspFields = getCspData(request); + if (cspFields != null && cspFields.length > 5) + attuid = cspFields[5]; + } catch (Throwable t) { + LOG.info(AaiUiMsgs.LOGIN_FILTER_INFO, + "getLoginIdFromCookie failed " + t.getLocalizedMessage()); + } + return attuid; + } + + /** + * Searches the specified request for the CSP cookie, decodes it and parses it to a String array. + * + * @param request + * @return Array of String as parsed from the cookie; null if the cookie is not present; empty + * array if the cookie could not be decoded. + */ + private static String[] getCspData(HttpServletRequest request) { + final String cookieName = PortalApiProperties.getProperty(CSP_COOKIE_NAME); + if (cookieName == null) { + LOG.debug(AaiUiMsgs.LOGIN_FILTER_DEBUG, + "getCspData: Failed to get property " + CSP_COOKIE_NAME); + return null; + } + Cookie csp = getCookie(request, cookieName); + if (csp == null) { + LOG.debug(AaiUiMsgs.LOGIN_FILTER_DEBUG, "getCspData failed to get cookie " + cookieName); + return null; + } + final String cspCookieEncrypted = csp.getValue(); + + String gateKeeperProdKey = PortalApiProperties.getProperty(CSP_GATE_KEEPER_PROD_KEY); + if (gateKeeperProdKey == null) { + LOG.debug(AaiUiMsgs.LOGIN_FILTER_DEBUG, + "getCspData: failed to get property " + CSP_GATE_KEEPER_PROD_KEY); + } + + String cspCookieDecrypted = ""; + try { + cspCookieDecrypted = CipherUtil.decrypt(cspCookieEncrypted,""); + } catch (Exception e) { + LOG.info(AaiUiMsgs.LOGIN_FILTER_INFO, + "decrypting cookie failed " + e.getLocalizedMessage()); + } + + String[] cspData = cspCookieDecrypted.split("\\|"); + return cspData; + } +} diff --git a/src/main/java/org/openecomp/sparky/security/SecurityContextFactory.java b/src/main/java/org/openecomp/sparky/security/SecurityContextFactory.java new file mode 100644 index 0000000..3144dee --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/SecurityContextFactory.java @@ -0,0 +1,79 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.security; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import javax.net.ssl.SSLContext; + +/** + * A factory for creating SecurityContext objects. + */ +public interface SecurityContextFactory { + + public String getSslAlgorithm(); + + public void setSslAlgorithm(String sslAlgorithm); + + public String getKeyManagerAlgortihm(); + + public void setKeyManagerAlgortihm(String keyManagerAlgortihm); + + public String getKeyStoreType(); + + public void setKeyStoreType(String keyStoreType); + + public boolean isServerCertificationChainValidationEnabled(); + + public void setServerCertificationChainValidationEnabled( + boolean serverCertificationChainValidationEnabled); + + public String getTrustStoreFileName(); + + public void setTrustStoreFileName(String filename); + + public String getClientCertPassword(); + + public void setClientCertPassword(String password); + + public void setClientCertFileInputStream(FileInputStream fis); + + public void setClientCertFileName(String filename) throws IOException; + + public FileInputStream getClientCertFileInputStream(); + + public SSLContext getSecureContext() + throws KeyManagementException, NoSuchAlgorithmException, FileNotFoundException, + KeyStoreException, CertificateException, IOException, UnrecoverableKeyException; + +} diff --git a/src/main/java/org/openecomp/sparky/security/SecurityContextFactoryImpl.java b/src/main/java/org/openecomp/sparky/security/SecurityContextFactoryImpl.java new file mode 100644 index 0000000..1fb03a7 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/SecurityContextFactoryImpl.java @@ -0,0 +1,206 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.security; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +/** + * The Class SecurityContextFactoryImpl. + */ +public class SecurityContextFactoryImpl implements SecurityContextFactory { + + protected String sslAlgorithm; + protected String keyManagerAlgortihm; + protected String keyStoreType; + protected boolean serverCertificationChainValidationEnabled; + protected String trustStoreFileName; + protected String clientCertPassword; + protected FileInputStream clientCertFileInputStream; + protected String clientCertFileName; + protected byte[] clientCertBytes; + + /** + * Instantiates a new security context factory impl. + */ + public SecurityContextFactoryImpl() { + this.sslAlgorithm = "TLS"; + this.keyManagerAlgortihm = "SunX509"; + this.keyStoreType = "PKCS12"; + this.serverCertificationChainValidationEnabled = false; + this.clientCertFileInputStream = null; + this.clientCertFileName = null; + } + + @Override + public String getSslAlgorithm() { + return sslAlgorithm; + } + + @Override + public void setSslAlgorithm(String sslAlgorithm) { + this.sslAlgorithm = sslAlgorithm; + } + + @Override + public String getKeyManagerAlgortihm() { + return keyManagerAlgortihm; + } + + @Override + public void setKeyManagerAlgortihm(String keyManagerAlgortihm) { + this.keyManagerAlgortihm = keyManagerAlgortihm; + } + + @Override + public String getKeyStoreType() { + return keyStoreType; + } + + @Override + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + @Override + public boolean isServerCertificationChainValidationEnabled() { + return serverCertificationChainValidationEnabled; + } + + @Override + public void setServerCertificationChainValidationEnabled( + boolean serverCertificationChainValidationEnabled) { + this.serverCertificationChainValidationEnabled = serverCertificationChainValidationEnabled; + } + + @Override + public void setClientCertFileName(String filename) throws IOException { + this.clientCertFileName = filename; + + if (filename == null) { + this.clientCertBytes = null; + } else { + this.clientCertBytes = Files.readAllBytes(new File(filename).toPath()); + } + } + + @Override + public void setClientCertFileInputStream(FileInputStream fis) { + this.clientCertFileInputStream = fis; + } + + @Override + public FileInputStream getClientCertFileInputStream() { + return this.clientCertFileInputStream; + } + + @Override + public SSLContext getSecureContext() throws KeyManagementException, NoSuchAlgorithmException, + KeyStoreException, CertificateException, IOException, UnrecoverableKeyException { + + TrustManager[] trustAllCerts = null; + + if (serverCertificationChainValidationEnabled) { + + System.setProperty("javax.net.ssl.trustStore", trustStoreFileName); + + } else { + + // Create a trust manager that does not validate certificate chains + trustAllCerts = new TrustManager[] {new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkClientTrusted(X509Certificate[] certs, String authType) {} + + @Override + public void checkServerTrusted(X509Certificate[] certs, String authType) {} + } }; + } + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerAlgortihm); + + KeyStore ks = KeyStore.getInstance(keyStoreType); + + char[] pwd = null; + if (clientCertPassword != null) { + pwd = clientCertPassword.toCharArray(); + } + + if (clientCertBytes != null) { + ks.load(new ByteArrayInputStream(clientCertBytes), pwd); + } else { + ks.load(null, pwd); + } + + kmf.init(ks, pwd); + + SSLContext ctx = SSLContext.getInstance(sslAlgorithm); + ctx.init(kmf.getKeyManagers(), trustAllCerts, null); + + return ctx; + + } + + @Override + public String getTrustStoreFileName() { + return this.trustStoreFileName; + } + + @Override + public void setTrustStoreFileName(String filename) { + this.trustStoreFileName = filename; + } + + @Override + public String getClientCertPassword() { + return this.clientCertPassword; + } + + @Override + public void setClientCertPassword(String password) { + this.clientCertPassword = password; + } + +} diff --git a/src/main/java/org/openecomp/sparky/security/filter/CspCookieFilter.java b/src/main/java/org/openecomp/sparky/security/filter/CspCookieFilter.java new file mode 100644 index 0000000..7140e96 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/filter/CspCookieFilter.java @@ -0,0 +1,271 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.security.filter; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +import org.openecomp.cl.mdc.MdcContext; + +// import esGateKeeper.esGateKeeper; + +/** + * Redirects to the AT&T global login page if the user is not authenticated.
    + * Filter properties need to be configured in: csp-cookie-filter.properties + */ +public class CspCookieFilter implements Filter { + + /** Redirect URL for the login page. */ + private String globalLoginUrl; + + /** Application identifier. */ + private String applicationId; + + /** Gatekeeper environment setting (development or production). */ + private String gateKeeperEnvironment; + + private static final String FILTER_PARAMETER_CONFIG = "config"; + private static final String PROPERTY_GLOBAL_LOGIN_URL = "global.login.url"; + private static final String PROPERTY_APPLICATION_ID = "application.id"; + private static final String PROPERTY_GATEKEEPER_ENVIRONMENT = "gatekeeper.environment"; + // valid open redirect domains + private List redirectDomains = new ArrayList<>(); + private static final String PROPERTY_REDIRECT_DOMAINS = "redirect-domain"; + + /** Needed by esGateKeeper, does not accept any other value. */ + private static final String GATEKEEPER_ACCOUNT_NAME = "CSP"; + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(CspCookieFilter.class); + + + /* (non-Javadoc) + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "CspCookieFilter", "", "Init", ""); + + try { + setConfigurationProperties(filterConfig); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.ERROR_CSP_CONFIG_FILE); + throw new ServletException(exc); + } + } + + + /* (non-Javadoc) + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + + Cookie[] cookies = request.getCookies(); + if ((cookies == null) || (cookies.length == 0)) { + doLogin(request, response); + return; + } + + /* + * String attEsSec = getSecurityCookie(cookies); + * + * if (attESSec == null || attESSec.length() == 0) { doLogin(request, response); return; } + * + * String attESSecUnEncrypted = esGateKeeper.esGateKeeper(attESSec, GATEKEEPER_ACCOUNT_NAME, + * gateKeeperEnvironment); if (attESSecUnEncrypted == null) { doLogin(request, response); } else + * { + */ + // LOG.info("User has valid cookie"); + chain.doFilter(request, response); + // } + } + + + /* (non-Javadoc) + * @see javax.servlet.Filter#destroy() + */ + @Override + public void destroy() {} + + /** + * Sets all required properties needed by this filter. + * + * @param filterConfig the filter configuration defined in the application web.xml + * @throws IOException if the properties failed to load. + */ + private void setConfigurationProperties(FilterConfig filterConfig) throws IOException { + InputStream inputStream = new FileInputStream(TierSupportUiConstants.STATIC_CONFIG_APP_LOCATION + + filterConfig.getInitParameter(FILTER_PARAMETER_CONFIG)); + Properties cspProperties = new Properties(); + cspProperties.load(inputStream); + globalLoginUrl = cspProperties.getProperty(PROPERTY_GLOBAL_LOGIN_URL); + applicationId = cspProperties.getProperty(PROPERTY_APPLICATION_ID); + gateKeeperEnvironment = cspProperties.getProperty(PROPERTY_GATEKEEPER_ENVIRONMENT); + redirectDomains = Arrays.asList(cspProperties.getProperty(PROPERTY_REDIRECT_DOMAINS).split(",")); + } + + /** + * Returns the attESSec cookie if found in the client. + * + * @param cookies the cookies available in the client + * @return the attESSec authentication cookie generated by the login page. + */ + private String getSecurityCookie(Cookie[] cookies) { + String attEsSec = null; + for (int i = 0; i < cookies.length; i++) { + Cookie thisCookie = cookies[i]; + String cookieName = thisCookie.getName(); + + if ("attESSec".equals(cookieName)) { + attEsSec = thisCookie.getValue(); + break; + } + } + return attEsSec; + } + + /** + * Redirects to the AT&T global login page. If this is an AJAX request it returns an unauthorized + * HTTP error in the response. + * + * @param request the filter request object + * @param response the filter response object + * @throws IOException if there is an error setting the error response + */ + private void doLogin(HttpServletRequest request, HttpServletResponse response) + throws IOException { + if (isAjaxRequest(request)) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, + "User is not authorized. Please login to application"); + } else { + // Fix for Safari 7.0.2 onwards to avoid login page cache + response.addHeader("Cache-Control", "no-cache, no-store"); + String redirectURL = createRedirectUrl(request); + if (this.isValidRedirectURL(redirectURL)){ + response.sendRedirect(redirectURL); + LOG.debug(AaiUiMsgs.VALID_REDIRECT_URL, redirectURL); + } else{ + response.sendError(400, "Bad redirect URL: " + redirectURL); + LOG.error(AaiUiMsgs.INVALID_REDIRECT_URL, redirectURL); + } + } + } + + /** + * Checks if a redirect url is valid + * @param url URL to validate + * @return true if URL is a valid redirect URL, false otherwise + */ + private boolean isValidRedirectURL (String url){ + String redirectTo = url.substring(url.indexOf("?retURL=")+ "?retURL=".length()); + try { + redirectTo = URLDecoder.decode(redirectTo, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + LOG.error(AaiUiMsgs.UNSUPPORTED_URL_ENCODING, e.getLocalizedMessage()); + return false; + } + for (String domain: this.redirectDomains){ + if (redirectTo.endsWith(domain)) + return true; + } + return false; + } + + + /** + * Returns true if the request is an AJAX request. + * + * @param request the filter request object + * @return true if the request is an AJAX request. + */ + private boolean isAjaxRequest(HttpServletRequest request) { + String headerValue = request.getHeader("X-Requested-With"); + if ("XMLHttpRequest".equals(headerValue)) { + return true; + } + return false; + } + + /** + * Returns the redirection URL to the AT&T Global login page. + * + * @param request the request + * @return the string + * @throws UnsupportedEncodingException the unsupported encoding exception + */ + private String createRedirectUrl(HttpServletRequest request) throws UnsupportedEncodingException { + String returnUrl = getReturnUrl(request); + + return globalLoginUrl + "?retURL=" + returnUrl + "&sysName=" + applicationId; + } + + /** + * Gets the URL encoded return URL. + * + * @param request the HTTP request + * @return an encoded URL to return to following login + * @throws UnsupportedEncodingException the unsupported encoding exception + */ + private String getReturnUrl(HttpServletRequest request) throws UnsupportedEncodingException { + StringBuffer retUrl = request.getRequestURL(); + String urlParams = request.getQueryString(); + if (urlParams != null) { + retUrl.append("?" + urlParams); + } + return URLEncoder.encode(retUrl.toString(), StandardCharsets.UTF_8.toString()); + } +} diff --git a/src/main/java/org/openecomp/sparky/security/filter/LoginFilter.java b/src/main/java/org/openecomp/sparky/security/filter/LoginFilter.java new file mode 100644 index 0000000..3ab8990 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/filter/LoginFilter.java @@ -0,0 +1,230 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.security.filter; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.ws.rs.core.HttpHeaders; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.portalsdk.core.onboarding.listener.PortalTimeoutHandler; +import org.openecomp.portalsdk.core.onboarding.util.PortalApiConstants; +import org.openecomp.portalsdk.core.onboarding.util.PortalApiProperties; +import org.openecomp.portalsdk.core.onboarding.util.SSOUtil; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.security.EcompSso; +import org.openecomp.sparky.security.portal.config.PortalAuthenticationConfig; + +/** + * This filter checks every request for proper ECOMP Portal single sign on initialization. The + * possible paths and actions: + *
      + *
    1. User starts at an app page via a bookmark. No ECOMP portal cookie is set. Redirect there to + * get one; then continue as below. + *
    2. User starts at ECOMP Portal and goes to app. Alternately, the user's session times out and + * the user hits refresh. The ECOMP Portal cookie is set, but there is no valid session. Create one + * and publish info. + *
    3. User has valid ECOMP Portal cookie and session. Reset the max idle in that session. + *
    + *

    + * Notes: + *

      + *
    • Portal Session should be up prior to App Session
    • + *
    • If App Session Expires or if EPService cookie is unavailable, we need to redirect to Portal. + *
    • Method {@link #initiateSessionMgtHandler(HttpServletRequest)} should be called for Session + * management when the initial session is created + *
    • While redirecting, the cookie "redirectUrl" should also be set so that Portal knows where to + * forward the request to once the Portal Session is created and EPService cookie is set. + *
    • Method {@link #resetSessionMaxIdleTimeOut(HttpServletRequest)} should be called for every + * request to reset the MaxInactiveInterval to the right value. + *
    + *

    + * This filter incorporates most features of the SDK application's SessionTimeoutInterceptor and + * SingleSignOnController classes + */ +public class LoginFilter implements Filter { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(LoginFilter.class); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Validate that app has provided useful portal properties + if (PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL) == null) { + throw new ServletException("Failed to find URL in portal.properties"); + } + + PortalAuthenticationConfig appProperties; + try { + appProperties = PortalAuthenticationConfig.getInstance(); + } catch (Exception ex) { + throw new ServletException("Failed to get properties", ex); + } + + String restUser = appProperties.getUsername(); + String restPassword = appProperties.getPassword(); + if (restUser == null || restPassword == null) { + throw new ServletException("Failed to find user and/or password from properties"); + } + } + + @Override + public void destroy() { + // No resources to release + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, + * javax.servlet.FilterChain) + */ + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws ServletException, IOException { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + + // Choose authentication appropriate for the request. + final String restApiURI = request.getContextPath() + PortalApiConstants.API_PREFIX; + if (request.getRequestURI().startsWith(restApiURI)) { + // REST servlet checks credentials + LOG.debug(AaiUiMsgs.LOGIN_FILTER_DEBUG, "doFilter: delegating auth to REST servlet for request " + request.getRequestURI()); + chain.doFilter(request, response); + } else { + // All other requests require ECOMP Portal authentication + if (EcompSso.validateEcompSso(request) == null) { + String redirectURL, logMessage; + + // Redirect to Portal UI + redirectURL = PortalApiProperties.getProperty(PortalApiConstants.ECOMP_REDIRECT_URL); + logMessage = "Unauthorized login attempt."; + + LOG.debug(AaiUiMsgs.LOGIN_FILTER_DEBUG, + logMessage + + " | Remote IP: " + request.getRemoteAddr() + + " | User agent: " + request.getHeader(HttpHeaders.USER_AGENT) + + " | Request URL: " + request.getRequestURL() + + " | Redirecting to: " + redirectURL); + + response.sendRedirect(redirectURL); + } else { + HttpSession session = request.getSession(false); + if (session == null) { + // New session + session = request.getSession(true); + LOG.debug(AaiUiMsgs.LOGIN_FILTER_DEBUG, "doFilter: created new session " + session.getId()); + initiateSessionMgtHandler(request); + } else { + // Existing session + LOG.debug(AaiUiMsgs.LOGIN_FILTER_DEBUG, "doFilter: resetting idle in existing session " + session.getId()); + resetSessionMaxIdleTimeOut(request); + } + // Pass request back down the filter chain + chain.doFilter(request, response); + } + } + } + + /** + * Publishes information about the session. + * + * @param request + */ + private void initiateSessionMgtHandler(HttpServletRequest request) { + String portalJSessionId = getPortalJSessionId(request); + String jSessionId = getJessionId(request); + storeMaxInactiveTime(request); + PortalTimeoutHandler.sessionCreated(portalJSessionId, jSessionId, request.getSession(false)); + } + + /** + * Gets the ECOMP Portal service cookie value. + * + * @param request + * @return Cookie value, or null if not found. + */ + private String getPortalJSessionId(HttpServletRequest request) { + Cookie ep = EcompSso.getCookie(request, EcompSso.EP_SERVICE); + return ep == null ? null : ep.getValue(); + } + + /** + * Gets the container session ID. + * + * @param request + * @return Session ID, or null if no session. + */ + private String getJessionId(HttpServletRequest request) { + HttpSession session = request.getSession(); + return session == null ? null : session.getId(); + } + + /** + * Sets the global session's max idle time to the session's max inactive interval. + * + * @param request + */ + private void storeMaxInactiveTime(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (session != null + && session.getAttribute(PortalApiConstants.GLOBAL_SESSION_MAX_IDLE_TIME) == null) { + session.setAttribute(PortalApiConstants.GLOBAL_SESSION_MAX_IDLE_TIME, + session.getMaxInactiveInterval()); + } + } + + /** + * Sets the session's max inactive interval. + * + * @param request + */ + private void resetSessionMaxIdleTimeOut(HttpServletRequest request) { + try { + HttpSession session = request.getSession(false); + if (session != null) { + final Object maxIdleAttribute = session + .getAttribute(PortalApiConstants.GLOBAL_SESSION_MAX_IDLE_TIME); + if (maxIdleAttribute != null) { + session.setMaxInactiveInterval(Integer.parseInt(maxIdleAttribute.toString())); + } + } + } catch (Exception e) { + LOG.info(AaiUiMsgs.LOGIN_FILTER_INFO, "resetSessionMaxIdleTimeOut: failed to set session max inactive interval - " + e.getLocalizedMessage()); + } + } + +} diff --git a/src/main/java/org/openecomp/sparky/security/portal/PortalRestAPIServiceImpl.java b/src/main/java/org/openecomp/sparky/security/portal/PortalRestAPIServiceImpl.java new file mode 100644 index 0000000..ce43ea2 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/portal/PortalRestAPIServiceImpl.java @@ -0,0 +1,229 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.security.portal; + +import java.io.File; +import java.io.IOException; +import java.text.MessageFormat; +import java.util.LinkedHashSet; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.openecomp.portalsdk.core.onboarding.crossapi.IPortalRestAPIService; +import org.openecomp.portalsdk.core.onboarding.exception.PortalAPIException; +import org.openecomp.portalsdk.core.restful.domain.EcompRole; +import org.openecomp.portalsdk.core.restful.domain.EcompUser; +import org.openecomp.sparky.security.EcompSso; +import org.openecomp.sparky.security.portal.config.PortalAuthenticationConfig; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Responds to ECOMP Portal's REST queries for user and role information and management. + */ +public class PortalRestAPIServiceImpl implements IPortalRestAPIService { + + private static final Logger LOG = LoggerFactory.getLogger(PortalRestAPIServiceImpl.class); + private static final String ERROR_MESSAGE = "Failed to {0} user [loginId:{1}]"; + + private UserManager userManager; + + /** + * Initialise user manager. + */ + public PortalRestAPIServiceImpl() { + userManager = new UserManager(new File(TierSupportUiConstants.USERS_FILE_LOCATION)); + } + + ///////////////////////////////////////////////////////////////////////////// + // User interface + ///////////////////////////////////////////////////////////////////////////// + + /* + * (non-Javadoc) + * + * @see + * com.att.fusion.core.onboarding.crossapi.IPortalRestAPIService#pushUser(com.att.fusion.core. + * restful.domain.EcompUser) + */ + @Override + public void pushUser(EcompUser user) throws PortalAPIException { + LOG.debug("Push user [loginId:" + user.getLoginId() + "]"); + + if (userManager.getUser(user.getLoginId()).isPresent()) { + String message = getMessage(ERROR_MESSAGE, "push", user.getLoginId()) + + ", user is already stored"; + LOG.error(message); + throw new PortalAPIException(message); + } + + try { + userManager.pushUser(user); + } catch (IOException e) { + String message = getMessage(ERROR_MESSAGE, "push", user.getLoginId()); + LOG.error(message, e); + throw new PortalAPIException(message, e); + } + } + + /* + * (non-Javadoc) + * + * @see com.att.fusion.core.onboarding.crossapi.IPortalRestAPIService#editUser(java.lang.String, + * com.att.fusion.core.restful.domain.EcompUser) + */ + @Override + public void editUser(String loginId, EcompUser user) throws PortalAPIException { + LOG.debug("Edit user [loginId:" + loginId + "]"); + + userManager.getUser(loginId).orElseThrow(() -> { + String message = getMessage(ERROR_MESSAGE, "edit", loginId) + ", unknown user"; + LOG.error(message); + return new PortalAPIException(message); + }); + + try { + userManager.editUser(loginId, user); + } catch (IOException e) { + String message = getMessage(ERROR_MESSAGE, "edit", loginId); + LOG.error(message, e); + throw new PortalAPIException(message, e); + } + } + + /* + * (non-Javadoc) + * + * @see com.att.fusion.core.onboarding.crossapi.IPortalRestAPIService#getUser(java.lang.String) + */ + @Override + public EcompUser getUser(String loginId) throws PortalAPIException { + LOG.debug("Get user [loginId:" + loginId + "]"); + return userManager.getUser(loginId).orElseThrow(() -> { + String message = getMessage(ERROR_MESSAGE, "get", loginId) + ", unknown user"; + LOG.error(message); + return new PortalAPIException(message); + }); + } + + /* + * (non-Javadoc) + * + * @see com.att.fusion.core.onboarding.crossapi.IPortalRestAPIService#getUsers() + */ + @Override + public List getUsers() throws PortalAPIException { + LOG.debug("Get users"); + return userManager.getUsers(); + } + + @Override + public String getUserId(HttpServletRequest request) throws PortalAPIException { + return EcompSso.validateEcompSso(request); + } + + ///////////////////////////////////////////////////////////////////////////// + // Role interface + ///////////////////////////////////////////////////////////////////////////// + + /* + * (non-Javadoc) + * + * @see com.att.fusion.core.onboarding.crossapi.IPortalRestAPIService#getAvailableRoles() + */ + @Override + public List getAvailableRoles() throws PortalAPIException { + LOG.debug("Get available roles"); + return UserManager.getRoles(); + } + + /* + * (non-Javadoc) + * + * @see + * com.att.fusion.core.onboarding.crossapi.IPortalRestAPIService#getUserRoles(java.lang.String) + */ + @Override + public List getUserRoles(String loginId) throws PortalAPIException { + LOG.debug("Get user roles"); + return userManager.getUserRoles(loginId); + } + + /* + * (non-Javadoc) + * + * @see + * com.att.fusion.core.onboarding.crossapi.IPortalRestAPIService#pushUserRole(java.lang.String, + * java.util.List) + */ + @Override + public void pushUserRole(String loginId, List roles) throws PortalAPIException { + LOG.debug("Push user role [loginId:" + loginId + "]"); + try { + EcompUser user = getUser(loginId); + if (roles != null) { + user.setRoles(new LinkedHashSet(roles)); + } else { + user.setRoles(new LinkedHashSet()); + } + editUser(loginId, user); + } catch (PortalAPIException e) { + String message = getMessage(ERROR_MESSAGE, "push role", loginId); + LOG.error(message); + throw new PortalAPIException(message, e); + } + } + + ///////////////////////////////////////////////////////////////////////////// + // Security interface + ///////////////////////////////////////////////////////////////////////////// + + /* + * (non-Javadoc) + * + * @see + * com.att.fusion.core.onboarding.crossapi.IPortalRestAPIService#isAppAuthenticated(javax.servlet. + * http.HttpServletRequest) + */ + @Override + public boolean isAppAuthenticated(HttpServletRequest request) throws PortalAPIException { + LOG.debug("Authentication request"); + PortalAuthenticationConfig config = PortalAuthenticationConfig.getInstance(); + String restUsername = request.getHeader(PortalAuthenticationConfig.PROP_USERNAME); + String restPassword = request.getHeader(PortalAuthenticationConfig.PROP_PASSWORD); + return restUsername != null && restPassword != null && restUsername.equals(config.getUsername()) + && restPassword.equals(config.getPassword()); + } + + private String getMessage(String message, Object... args) { + MessageFormat formatter = new MessageFormat(""); + formatter.applyPattern(message); + return formatter.format(args); + } + +} \ No newline at end of file diff --git a/src/main/java/org/openecomp/sparky/security/portal/UserManager.java b/src/main/java/org/openecomp/sparky/security/portal/UserManager.java new file mode 100644 index 0000000..bbc4ee3 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/portal/UserManager.java @@ -0,0 +1,171 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.security.portal; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import org.openecomp.portalsdk.core.restful.domain.EcompRole; +import org.openecomp.portalsdk.core.restful.domain.EcompUser; +import org.openecomp.sparky.security.portal.config.RolesConfig; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +/** + * Basic file based user storage. + */ +public class UserManager { + + private File usersFile; + + private static final ReadWriteLock LOCK = new ReentrantReadWriteLock(true); + private static final Lock READ_LOCK = LOCK.readLock(); + private static final Lock WRITE_LOCK = LOCK.writeLock(); + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + /** + * + * @param usersFile a file to store the users + */ + public UserManager(File usersFile) { + this.usersFile = usersFile; + } + + /** + * Returns all users stored. + * + * @return a list of users. + */ + public List getUsers() { + Type collectionType = new TypeToken>() { + }.getType(); + + Optional users = read(usersFile); + if (users.isPresent()) { + return GSON.fromJson(users.get(), collectionType); + } + + return new ArrayList<>(); + } + + /** + * Returns a stored user. + * + * @param loginId the identifier of the user + * @return an optional user. + */ + public Optional getUser(String loginId) { + if (!getUsers().isEmpty()) { + return getUsers().stream().filter(u -> loginId.equals(u.getLoginId())).findFirst(); + } + return Optional.empty(); + } + + /** + * Stores a user if not already stored. + * + * @param user the user to be stored + * @throws IOException + */ + public void pushUser(EcompUser user) throws IOException { + WRITE_LOCK.lock(); + try { + if (!getUser(user.getLoginId()).isPresent()) { + addUser(getUsers(), user); + } + } finally { + WRITE_LOCK.unlock(); + } + } + + /** + * Replaces an existing user. + * + * @param loginId the id of the user + * @param user the new user details + * @throws IOException + */ + public void editUser(String loginId, EcompUser user) throws IOException { + WRITE_LOCK.lock(); + try { + if (getUser(loginId).isPresent()) { + List users = getUsers().stream().filter(u -> !u.getLoginId().equals(loginId)) + .collect(Collectors.toList()); + addUser(users, user); + } + } finally { + WRITE_LOCK.unlock(); + } + } + + /** + * Gets the roles assigned to a user. + * + * @param loginId the id of the user + * @return the assigned roles + */ + public List getUserRoles(String loginId) { + List roles = new ArrayList<>(); + roles.addAll(getUser(loginId).orElseGet(EcompUser::new).getRoles()); + return roles; + } + + public static List getRoles() { + return RolesConfig.getInstance().getRoles(); + } + + private void addUser(List users, EcompUser user) throws IOException { + users.add(user); + write(users); + } + + private void write(List users) throws IOException { + Files.write(usersFile.toPath(), GSON.toJson(users).getBytes()); + } + + private Optional read(File file) { + READ_LOCK.lock(); + try { + return Optional.of(new String(Files.readAllBytes(file.toPath()))); + } catch (IOException e) { // NOSONAR + return Optional.empty(); + } finally { + READ_LOCK.unlock(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/openecomp/sparky/security/portal/config/PortalAuthenticationConfig.java b/src/main/java/org/openecomp/sparky/security/portal/config/PortalAuthenticationConfig.java new file mode 100644 index 0000000..c217615 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/portal/config/PortalAuthenticationConfig.java @@ -0,0 +1,99 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.security.portal.config; + +import java.util.Properties; + +import org.openecomp.sparky.util.ConfigHelper; +import org.openecomp.sparky.util.Encryptor; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +/** + * Provides Portal authentication configuration. + */ +public class PortalAuthenticationConfig { + + private String username; + private String password; + private boolean isOnapEnabled; + + public static final String PROP_USERNAME = "username"; + public static final String PROP_PASSWORD = "password"; // NOSONAR + public static final String PROP_IS_ONAP_ENABLED = "onap_enabled"; // NOSONAR + private static final String AUTHENTICATION_CONFIG_FILE = TierSupportUiConstants.PORTAL_AUTHENTICATION_FILE_LOCATION; + + private PortalAuthenticationConfig() { + // Prevent instantiation + } + + private static class PortalAuthenticationConfigHelper { + private static final PortalAuthenticationConfig INSTANCE = new PortalAuthenticationConfig(); + + private PortalAuthenticationConfigHelper() { + // Deliberately empty + } + } + + /** + * Get a singleton instance of the configuration. + * + * @return + */ + public static PortalAuthenticationConfig getInstance() { + PortalAuthenticationConfigHelper.INSTANCE.load(); + return PortalAuthenticationConfigHelper.INSTANCE; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + Encryptor encryptor = new Encryptor(); + return encryptor.decryptValue(password); + } + + public boolean getIsOnapEnabled() { + return isOnapEnabled; + } + + /** + * Reload the Portal authentication properties from the classpath. + */ + public void reload() { + load(); + } + + /** + * Load the Portal authentication properties from the classpath. + */ + private void load() { + Properties props = ConfigHelper.loadConfigFromExplicitPath(AUTHENTICATION_CONFIG_FILE); + username = props.getProperty(PROP_USERNAME); + password = props.getProperty(PROP_PASSWORD); + isOnapEnabled = Boolean.parseBoolean(props.getProperty(PROP_IS_ONAP_ENABLED, "true")); + } +} \ No newline at end of file diff --git a/src/main/java/org/openecomp/sparky/security/portal/config/RolesConfig.java b/src/main/java/org/openecomp/sparky/security/portal/config/RolesConfig.java new file mode 100644 index 0000000..18753a4 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/security/portal/config/RolesConfig.java @@ -0,0 +1,91 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.security.portal.config; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; + +import org.openecomp.portalsdk.core.restful.domain.EcompRole; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; + +/** + * Provides roles configuration. + */ +public class RolesConfig { + + private List roles; + + private static final Gson GSON = new Gson(); + private static final String ROLES_CONFIG_FILE = TierSupportUiConstants.ROLES_FILE_LOCATION; + + private RolesConfig() { + // Prevent instantiation + } + + private static class RolesConfigHelper { + private static final RolesConfig INSTANCE = new RolesConfig(); + + private RolesConfigHelper() { + // Deliberately empty + } + } + + /** + * Get a singleton instance of the configuration. + * + * @return + */ + public static RolesConfig getInstance() { + try { + RolesConfigHelper.INSTANCE.load(); + } catch (Exception e) { + throw new ExceptionInInitializerError(e); + } + + return RolesConfigHelper.INSTANCE; + } + + public List getRoles() { + return roles; + } + + private void load() throws JsonSyntaxException, IOException, URISyntaxException { + Type collectionType = new TypeToken>() { + }.getType(); + + roles = Collections.unmodifiableList(GSON + .fromJson(new String(Files.readAllBytes(Paths.get(ROLES_CONFIG_FILE))), collectionType)); + } +} \ No newline at end of file diff --git a/src/main/java/org/openecomp/sparky/suggestivesearch/SuggestionEntity.java b/src/main/java/org/openecomp/sparky/suggestivesearch/SuggestionEntity.java new file mode 100644 index 0000000..3badc50 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/suggestivesearch/SuggestionEntity.java @@ -0,0 +1,59 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.suggestivesearch; + +public class SuggestionEntity { + private String route; + private String hashId; + private String text; + + public SuggestionEntity() { + } + + public SuggestionEntity(String route, String hashId, String text) { + this.route = route; + this.hashId = hashId; + this.text = text; + } + + public String getRoute() { + return route; + } + public void setRoute(String route) { + this.route = route; + } + public String getHashId() { + return hashId; + } + public void setHashId(String hashId) { + this.hashId = hashId; + } + public String getText() { + return text; + } + public void setText(String text) { + this.text = text; + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/AbstractEntitySynchronizer.java b/src/main/java/org/openecomp/sparky/synchronizer/AbstractEntitySynchronizer.java new file mode 100644 index 0000000..14ea149 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/AbstractEntitySynchronizer.java @@ -0,0 +1,559 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.EnumSet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; + +import org.openecomp.cl.api.Logger; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.aai.ActiveInventoryEntityStatistics; +import org.openecomp.sparky.dal.aai.ActiveInventoryProcessingExceptionStatistics; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.elasticsearch.ElasticSearchDataProvider; +import org.openecomp.sparky.dal.elasticsearch.ElasticSearchEntityStatistics; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestOperationalStatistics; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.util.NodeUtils; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The Class AbstractEntitySynchronizer. + * + * @author davea. + */ +public abstract class AbstractEntitySynchronizer { + + protected static final int VERSION_CONFLICT_EXCEPTION_CODE = 409; + protected static final Integer RETRY_COUNT_PER_ENTITY_LIMIT = new Integer(3); + + protected final Logger logger; + protected ObjectMapper mapper; + protected OxmModelLoader oxmModelLoader; + + /** + * The Enum StatFlag. + */ + protected enum StatFlag { + AAI_REST_STATS, AAI_ENTITY_STATS, AAI_PROCESSING_EXCEPTION_STATS, + AAI_TASK_PROCESSING_STATS, ES_REST_STATS, ES_ENTITY_STATS, ES_TASK_PROCESSING_STATS + } + + protected EnumSet enabledStatFlags; + + protected ActiveInventoryDataProvider aaiDataProvider; + protected ElasticSearchDataProvider esDataProvider; + + protected ExecutorService synchronizerExecutor; + protected ExecutorService aaiExecutor; + protected ExecutorService esExecutor; + + private RestOperationalStatistics esRestStats; + protected ElasticSearchEntityStatistics esEntityStats; + + private RestOperationalStatistics aaiRestStats; + protected ActiveInventoryEntityStatistics aaiEntityStats; + private ActiveInventoryProcessingExceptionStatistics aaiProcessingExceptionStats; + + private TaskProcessingStats aaiTaskProcessingStats; + private TaskProcessingStats esTaskProcessingStats; + + private TransactionRateController aaiTransactionRateController; + private TransactionRateController esTransactionRateController; + + protected AtomicInteger aaiWorkOnHand; + protected AtomicInteger esWorkOnHand; + protected String synchronizerName; + + protected abstract boolean isSyncDone(); + + public String getActiveInventoryStatisticsReport() { + + StringBuilder sb = new StringBuilder(128); + + if (enabledStatFlags.contains(StatFlag.AAI_REST_STATS)) { + sb.append("\n\n ").append("REST Operational Stats:"); + sb.append(aaiRestStats.getStatisticsReport()); + } + + if (enabledStatFlags.contains(StatFlag.AAI_ENTITY_STATS)) { + sb.append("\n\n ").append("Entity Stats:"); + sb.append(aaiEntityStats.getStatisticsReport()); + } + + if (enabledStatFlags.contains(StatFlag.AAI_PROCESSING_EXCEPTION_STATS)) { + sb.append("\n\n ").append("Processing Exception Stats:"); + sb.append(aaiProcessingExceptionStats.getStatisticsReport()); + } + + return sb.toString(); + + } + + public String getElasticSearchStatisticsReport() { + + StringBuilder sb = new StringBuilder(128); + + if (enabledStatFlags.contains(StatFlag.ES_REST_STATS)) { + sb.append("\n\n ").append("REST Operational Stats:"); + sb.append(esRestStats.getStatisticsReport()); + } + + if (enabledStatFlags.contains(StatFlag.ES_ENTITY_STATS)) { + sb.append("\n\n ").append("Entity Stats:"); + sb.append(esEntityStats.getStatisticsReport()); + } + + return sb.toString(); + + } + + /** + * Adds the active inventory stat report. + * + * @param sb the sb + */ + private void addActiveInventoryStatReport(StringBuilder sb) { + + if (sb == null) { + return; + } + + sb.append("\n\n AAI"); + sb.append(getActiveInventoryStatisticsReport()); + + double currentTps = 0; + if (enabledStatFlags.contains(StatFlag.AAI_TASK_PROCESSING_STATS)) { + sb.append("\n\n ").append("Task Processor Stats:"); + sb.append(aaiTaskProcessingStats.getStatisticsReport(false, " ")); + + currentTps = aaiTransactionRateController.getCurrentTps(); + + sb.append("\n ").append("Current TPS: ").append(currentTps); + } + + sb.append("\n ").append("Current WOH: ").append(aaiWorkOnHand.get()); + + if (enabledStatFlags.contains(StatFlag.AAI_TASK_PROCESSING_STATS)) { + if (currentTps > 0) { + double numMillisecondsToCompletion = (aaiWorkOnHand.get() / currentTps) * 1000; + sb.append("\n ").append("SyncDurationRemaining=") + .append(NodeUtils.getDurationBreakdown((long) numMillisecondsToCompletion)); + } + } + + } + + /** + * Adds the elastic stat report. + * + * @param sb the sb + */ + private void addElasticStatReport(StringBuilder sb) { + + if (sb == null) { + return; + } + + sb.append("\n\n ELASTIC"); + sb.append(getElasticSearchStatisticsReport()); + + double currentTps = 0; + + if (enabledStatFlags.contains(StatFlag.ES_TASK_PROCESSING_STATS)) { + sb.append("\n\n ").append("Task Processor Stats:"); + sb.append(esTaskProcessingStats.getStatisticsReport(false, " ")); + + currentTps = esTransactionRateController.getCurrentTps(); + + sb.append("\n ").append("Current TPS: ").append(currentTps); + } + + sb.append("\n ").append("Current WOH: ").append(esWorkOnHand.get()); + + if (enabledStatFlags.contains(StatFlag.ES_TASK_PROCESSING_STATS)) { + if (currentTps > 0) { + double numMillisecondsToCompletion = (esWorkOnHand.get() / currentTps) * 1000; + sb.append("\n ").append("SyncDurationRemaining=") + .append(NodeUtils.getDurationBreakdown((long) numMillisecondsToCompletion)); + } + } + + + } + + /** + * Gets the stat report. + * + * @param syncOpTimeInMs the sync op time in ms + * @param showFinalReport the show final report + * @return the stat report + */ + protected String getStatReport(long syncOpTimeInMs, boolean showFinalReport) { + + StringBuilder sb = new StringBuilder(128); + + sb.append("\n").append(synchronizerName + " Statistics: ( Sync Operation Duration = " + + NodeUtils.getDurationBreakdown(syncOpTimeInMs) + " )"); + + addActiveInventoryStatReport(sb); + addElasticStatReport(sb); + + if (showFinalReport) { + sb.append("\n\n ").append("Sync Completed!\n"); + } else { + sb.append("\n\n ").append("Sync in Progress...\n"); + } + + return sb.toString(); + + } + + protected String indexName; + protected long syncStartedTimeStampInMs; + + /** + * Instantiates a new abstract entity synchronizer. + * + * @param logger the logger + * @param syncName the sync name + * @param numSyncWorkers the num sync workers + * @param numActiveInventoryWorkers the num active inventory workers + * @param numElasticsearchWorkers the num elasticsearch workers + * @param indexName the index name + * @throws Exception the exception + */ + protected AbstractEntitySynchronizer(Logger logger, String syncName, int numSyncWorkers, + int numActiveInventoryWorkers, int numElasticsearchWorkers, String indexName) + throws Exception { + this.logger = logger; + this.synchronizerExecutor = + NodeUtils.createNamedExecutor(syncName + "-INTERNAL", numSyncWorkers, logger); + this.aaiExecutor = + NodeUtils.createNamedExecutor(syncName + "-AAI", numActiveInventoryWorkers, logger); + this.esExecutor = + NodeUtils.createNamedExecutor(syncName + "-ES", numElasticsearchWorkers, logger); + this.mapper = new ObjectMapper(); + this.oxmModelLoader = OxmModelLoader.getInstance(); + this.indexName = indexName; + this.esRestStats = new RestOperationalStatistics(); + this.esEntityStats = new ElasticSearchEntityStatistics(oxmModelLoader); + this.aaiRestStats = new RestOperationalStatistics(); + this.aaiEntityStats = new ActiveInventoryEntityStatistics(oxmModelLoader); + this.aaiProcessingExceptionStats = new ActiveInventoryProcessingExceptionStatistics(); + this.aaiTaskProcessingStats = + new TaskProcessingStats(ActiveInventoryConfig.getConfig().getTaskProcessorConfig()); + this.esTaskProcessingStats = + new TaskProcessingStats(ElasticSearchConfig.getConfig().getProcessorConfig()); + + this.aaiTransactionRateController = + new TransactionRateController(ActiveInventoryConfig.getConfig().getTaskProcessorConfig()); + this.esTransactionRateController = + new TransactionRateController(ElasticSearchConfig.getConfig().getProcessorConfig()); + + this.aaiWorkOnHand = new AtomicInteger(0); + this.esWorkOnHand = new AtomicInteger(0); + + enabledStatFlags = EnumSet.allOf(StatFlag.class); + + this.synchronizerName = "Abstact Entity Synchronizer"; + + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "AbstractEntitySynchronizer", "", "Sync", ""); + + } + + /** + * Inc active inventory work on hand counter. + */ + protected void incActiveInventoryWorkOnHandCounter() { + aaiWorkOnHand.incrementAndGet(); + } + + /** + * Dec active inventory work on hand counter. + */ + protected void decActiveInventoryWorkOnHandCounter() { + aaiWorkOnHand.decrementAndGet(); + } + + /** + * Inc elastic search work on hand counter. + */ + protected void incElasticSearchWorkOnHandCounter() { + esWorkOnHand.incrementAndGet(); + } + + /** + * Dec elastic search work on hand counter. + */ + protected void decElasticSearchWorkOnHandCounter() { + esWorkOnHand.decrementAndGet(); + } + + /** + * Shutdown executors. + */ + protected void shutdownExecutors() { + try { + synchronizerExecutor.shutdown(); + aaiExecutor.shutdown(); + esExecutor.shutdown(); + aaiDataProvider.shutdown(); + esDataProvider.shutdown(); + } catch (Exception exc) { + logger.error(AaiUiMsgs.ERROR_SHUTDOWN_EXECUTORS, exc ); + } + } + + /** + * Clear cache. + */ + public void clearCache() { + if (aaiDataProvider != null) { + aaiDataProvider.clearCache(); + } + } + + protected ActiveInventoryDataProvider getAaiDataProvider() { + return aaiDataProvider; + } + + public void setAaiDataProvider(ActiveInventoryDataProvider aaiDataProvider) { + this.aaiDataProvider = aaiDataProvider; + } + + protected ElasticSearchDataProvider getEsDataProvider() { + return esDataProvider; + } + + public void setEsDataProvider(ElasticSearchDataProvider provider) { + this.esDataProvider = provider; + } + + /** + * Gets the elastic full url. + * + * @param resourceUrl the resource url + * @param indexName the index name + * @param indexType the index type + * @return the elastic full url + * @throws Exception the exception + */ + protected String getElasticFullUrl(String resourceUrl, String indexName, String indexType) + throws Exception { + return ElasticSearchConfig.getConfig().getElasticFullUrl(resourceUrl, indexName, indexType); + } + + /** + * Gets the elastic full url. + * + * @param resourceUrl the resource url + * @param indexName the index name + * @return the elastic full url + * @throws Exception the exception + */ + protected String getElasticFullUrl(String resourceUrl, String indexName) throws Exception { + return ElasticSearchConfig.getConfig().getElasticFullUrl(resourceUrl, indexName); + } + + public String getIndexName() { + return indexName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + + /** + * Gets the response length. + * + * @param txn the txn + * @return the response length + */ + private long getResponseLength(NetworkTransaction txn) { + + if (txn == null) { + return -1; + } + + OperationResult result = txn.getOperationResult(); + + if (result == null) { + return -1; + } + + if (result.getResult() != null) { + return result.getResult().length(); + } + + return -1; + } + + /** + * Update elastic search counters. + * + * @param method the method + * @param or the or + */ + protected void updateElasticSearchCounters(HttpMethod method, OperationResult or) { + updateElasticSearchCounters(new NetworkTransaction(method, null, or)); + } + + /** + * Update elastic search counters. + * + * @param method the method + * @param entityType the entity type + * @param or the or + */ + protected void updateElasticSearchCounters(HttpMethod method, String entityType, + OperationResult or) { + updateElasticSearchCounters(new NetworkTransaction(method, entityType, or)); + } + + /** + * Update elastic search counters. + * + * @param txn the txn + */ + protected void updateElasticSearchCounters(NetworkTransaction txn) { + + if (enabledStatFlags.contains(StatFlag.ES_REST_STATS)) { + esRestStats.updateCounters(txn); + } + + if (enabledStatFlags.contains(StatFlag.ES_ENTITY_STATS)) { + esEntityStats.updateCounters(txn); + } + + if (enabledStatFlags.contains(StatFlag.ES_TASK_PROCESSING_STATS)) { + + esTransactionRateController.trackResponseTime(txn.getOperationResult().getResponseTimeInMs()); + + esTaskProcessingStats + .updateTaskResponseStatsHistogram(txn.getOperationResult().getResponseTimeInMs()); + esTaskProcessingStats.updateTaskAgeStatsHistogram(txn.getTaskAgeInMs()); + + // don't know the cost of the lengh calc, we'll see if it causes a + // problem + + long responsePayloadSizeInBytes = getResponseLength(txn); + if (responsePayloadSizeInBytes >= 0) { + esTaskProcessingStats.updateResponseSizeInBytesHistogram(responsePayloadSizeInBytes); + } + + esTaskProcessingStats + .updateTransactionsPerSecondHistogram((long) esTransactionRateController.getCurrentTps()); + } + } + + /** + * Update active inventory counters. + * + * @param method the method + * @param or the or + */ + protected void updateActiveInventoryCounters(HttpMethod method, OperationResult or) { + updateActiveInventoryCounters(new NetworkTransaction(method, null, or)); + } + + /** + * Update active inventory counters. + * + * @param method the method + * @param entityType the entity type + * @param or the or + */ + protected void updateActiveInventoryCounters(HttpMethod method, String entityType, + OperationResult or) { + updateActiveInventoryCounters(new NetworkTransaction(method, entityType, or)); + } + + /** + * Update active inventory counters. + * + * @param txn the txn + */ + protected void updateActiveInventoryCounters(NetworkTransaction txn) { + + if (enabledStatFlags.contains(StatFlag.AAI_REST_STATS)) { + aaiRestStats.updateCounters(txn); + } + + if (enabledStatFlags.contains(StatFlag.AAI_ENTITY_STATS)) { + aaiEntityStats.updateCounters(txn); + } + + if (enabledStatFlags.contains(StatFlag.AAI_PROCESSING_EXCEPTION_STATS)) { + aaiProcessingExceptionStats.updateCounters(txn); + } + + if (enabledStatFlags.contains(StatFlag.AAI_TASK_PROCESSING_STATS)) { + aaiTransactionRateController + .trackResponseTime(txn.getOperationResult().getResponseTimeInMs()); + + aaiTaskProcessingStats + .updateTaskResponseStatsHistogram(txn.getOperationResult().getResponseTimeInMs()); + aaiTaskProcessingStats.updateTaskAgeStatsHistogram(txn.getTaskAgeInMs()); + + // don't know the cost of the lengh calc, we'll see if it causes a + // problem + + long responsePayloadSizeInBytes = getResponseLength(txn); + if (responsePayloadSizeInBytes >= 0) { + aaiTaskProcessingStats.updateResponseSizeInBytesHistogram(responsePayloadSizeInBytes); + } + + aaiTaskProcessingStats.updateTransactionsPerSecondHistogram( + (long) aaiTransactionRateController.getCurrentTps()); + } + } + + /** + * Reset counters. + */ + protected void resetCounters() { + aaiRestStats.reset(); + aaiEntityStats.reset(); + aaiProcessingExceptionStats.reset(); + + esRestStats.reset(); + esEntityStats.reset(); + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/AggregationSuggestionSynchronizer.java b/src/main/java/org/openecomp/sparky/synchronizer/AggregationSuggestionSynchronizer.java new file mode 100644 index 0000000..0337f6a --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/AggregationSuggestionSynchronizer.java @@ -0,0 +1,187 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import org.openecomp.cl.mdc.MdcContext; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.entity.AggregationSuggestionEntity; +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchPut; +import org.openecomp.sparky.util.NodeUtils; +import org.slf4j.MDC; + +public class AggregationSuggestionSynchronizer extends AbstractEntitySynchronizer + implements IndexSynchronizer { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(AggregationSuggestionSynchronizer.class); + + private boolean isSyncInProgress; + private boolean shouldPerformRetry; + private Map contextMap; + protected ExecutorService esPutExecutor; + + public AggregationSuggestionSynchronizer(String indexName) throws Exception { + super(LOG, "ASS-" + indexName.toUpperCase(), 2, 5, 5, indexName); + + this.isSyncInProgress = false; + this.shouldPerformRetry = false; + this.synchronizerName = "Aggregation Suggestion Synchronizer"; + this.contextMap = MDC.getCopyOfContextMap(); + this.esPutExecutor = NodeUtils.createNamedExecutor("ASS-ES-PUT", 2, LOG); + } + + @Override + protected boolean isSyncDone() { + int totalWorkOnHand = esWorkOnHand.get(); + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + indexName + ", isSyncDone(), totalWorkOnHand = " + totalWorkOnHand); + } + + if (totalWorkOnHand > 0 || !isSyncInProgress) { + return false; + } + + return true; + } + + @Override + public OperationState doSync() { + isSyncInProgress = true; + + syncEntity(); + + while (!isSyncDone()) { + try { + if (shouldPerformRetry) { + syncEntity(); + } + Thread.sleep(1000); + } catch (Exception exc) { + // We don't care about this exception + } + } + + return OperationState.OK; + } + + private void syncEntity() { + String txnId = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnId, "AggregationSuggestionSynchronizer", "", "Sync", ""); + + AggregationSuggestionEntity syncEntity = new AggregationSuggestionEntity(); + syncEntity.deriveFields(); + + String link = null; + try { + link = getElasticFullUrl("/" + syncEntity.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_LINK_UPSERT, exc.getLocalizedMessage()); + } + + try { + String jsonPayload = null; + jsonPayload = syncEntity.getIndexDocumentJson(); + if (link != null && jsonPayload != null) { + + NetworkTransaction elasticPutTxn = new NetworkTransaction(); + elasticPutTxn.setLink(link); + elasticPutTxn.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + final Map contextMap = MDC.getCopyOfContextMap(); + supplyAsync(new PerformElasticSearchPut(jsonPayload, elasticPutTxn, + esDataProvider, contextMap), esPutExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + String message = "Aggregation suggestion entity sync UPDATE PUT error - " + + error.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ES_AGGREGATION_SUGGESTION_ENTITY_SYNC_ERROR, message); + } else { + updateElasticSearchCounters(result); + wasEsOperationSuccessful(result); + } + }); + } + } catch (Exception exc) { + String message = + "Exception caught during aggregation suggestion entity sync PUT operation. Message - " + + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ES_AGGREGATION_SUGGESTION_ENTITY_SYNC_ERROR, message); + } + } + + private void wasEsOperationSuccessful(NetworkTransaction result) { + if (result != null) { + OperationResult opResult = result.getOperationResult(); + + if (!opResult.wasSuccessful()) { + shouldPerformRetry = true; + } else { + isSyncInProgress = false; + shouldPerformRetry = false; + } + } + } + + @Override + public SynchronizerState getState() { + if (!isSyncDone()) { + return SynchronizerState.PERFORMING_SYNCHRONIZATION; + } + + return SynchronizerState.IDLE; + } + + @Override + public String getStatReport(boolean shouldDisplayFinalReport) { + return getStatReport(System.currentTimeMillis() - this.syncStartedTimeStampInMs, + shouldDisplayFinalReport); + } + + @Override + public void shutdown() { + this.shutdownExecutors(); + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/AggregationSynchronizer.java b/src/main/java/org/openecomp/sparky/synchronizer/AggregationSynchronizer.java new file mode 100644 index 0000000..ba1fb24 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/AggregationSynchronizer.java @@ -0,0 +1,772 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import javax.json.Json; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.config.SynchronizerConfiguration; +import org.openecomp.sparky.synchronizer.entity.AggregationEntity; +import org.openecomp.sparky.synchronizer.entity.MergableEntity; +import org.openecomp.sparky.synchronizer.entity.SelfLinkDescriptor; +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.synchronizer.task.PerformActiveInventoryRetrieval; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchPut; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchRetrieval; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchUpdate; +import org.openecomp.sparky.util.NodeUtils; +import org.slf4j.MDC; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * The Class AutosuggestionSynchronizer. + */ +public class AggregationSynchronizer extends AbstractEntitySynchronizer + implements IndexSynchronizer { + + /** + * The Class RetryAggregationEntitySyncContainer. + */ + private class RetryAggregationEntitySyncContainer { + NetworkTransaction txn; + AggregationEntity ae; + + /** + * Instantiates a new retry aggregation entity sync container. + * + * @param txn the txn + * @param ae the se + */ + public RetryAggregationEntitySyncContainer(NetworkTransaction txn, AggregationEntity ae) { + this.txn = txn; + this.ae = ae; + } + + public NetworkTransaction getNetworkTransaction() { + return txn; + } + + public AggregationEntity getAggregationEntity() { + return ae; + } + } + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(AggregationSynchronizer.class); + private static final String INSERTION_DATE_TIME_FORMAT = "yyyyMMdd'T'HHmmssZ"; + + private boolean allWorkEnumerated; + private Deque selflinks; + private Deque retryQueue; + private Map retryLimitTracker; + protected ExecutorService esPutExecutor; + private ConcurrentHashMap entityCounters; + private boolean syncInProgress; + private Map contextMap; + private String entityType; + + /** + * Instantiates a new entity aggregation synchronizer. + * + * @param indexName the index name + * @throws Exception the exception + */ + public AggregationSynchronizer(String entityType, String indexName) throws Exception { + super(LOG, "AGGES-" + indexName.toUpperCase(), 2, 5, 5, indexName); // multiple Autosuggestion + // Entity Synchronizer will + // run for different indices + + this.entityType = entityType; + this.allWorkEnumerated = false; + this.entityCounters = new ConcurrentHashMap(); + this.synchronizerName = "Entity Aggregation Synchronizer"; + this.enabledStatFlags = EnumSet.of(StatFlag.AAI_REST_STATS, StatFlag.ES_REST_STATS); + this.syncInProgress = false; + this.allWorkEnumerated = false; + this.selflinks = new ConcurrentLinkedDeque(); + this.retryQueue = new ConcurrentLinkedDeque(); + this.retryLimitTracker = new ConcurrentHashMap(); + + this.esPutExecutor = NodeUtils.createNamedExecutor("AGGES-ES-PUT", 1, LOG); + Map descriptor = new HashMap(); + descriptor.put(entityType, oxmModelLoader.getEntityDescriptors().get(entityType)); + this.aaiEntityStats.initializeCountersFromOxmEntityDescriptors( + descriptor); + this.esEntityStats.initializeCountersFromOxmEntityDescriptors( + descriptor); + this.contextMap = MDC.getCopyOfContextMap(); + } + + /** + * Collect all the work. + * + * @return the operation state + */ + private OperationState collectAllTheWork() { + final Map contextMap = MDC.getCopyOfContextMap(); + final String entity = this.getEntityType(); + try { + + aaiWorkOnHand.set(1); + + supplyAsync(new Supplier() { + + @Override + public Void get() { + MDC.setContextMap(contextMap); + OperationResult typeLinksResult = null; + try { + typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(entity); + aaiWorkOnHand.decrementAndGet(); + processEntityTypeSelfLinks(typeLinksResult); + } catch (Exception exc) { + // TODO -> LOG, what should be logged here? + } + + return null; + } + + }, aaiExecutor).whenComplete((result, error) -> { + + if (error != null) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "An error occurred getting data from AAI. Error = " + error.getMessage()); + } + }); + + while (aaiWorkOnHand.get() != 0) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.WAIT_FOR_ALL_SELFLINKS_TO_BE_COLLECTED); + } + + Thread.sleep(1000); + } + + aaiWorkOnHand.set(selflinks.size()); + allWorkEnumerated = true; + syncEntityTypes(); + + while (!isSyncDone()) { + performRetrySync(); + Thread.sleep(1000); + } + + /* + * Make sure we don't hang on to retries that failed which could cause issues during future + * syncs + */ + retryLimitTracker.clear(); + + } catch (Exception exc) { + // TODO -> LOG, waht should be logged here? + } + + return OperationState.OK; + } + + + /** + * Perform retry sync. + */ + private void performRetrySync() { + while (retryQueue.peek() != null) { + + RetryAggregationEntitySyncContainer rsc = retryQueue.poll(); + if (rsc != null) { + + AggregationEntity ae = rsc.getAggregationEntity(); + NetworkTransaction txn = rsc.getNetworkTransaction(); + + String link = null; + try { + /* + * In this retry flow the se object has already derived its fields + */ + link = getElasticFullUrl("/" + ae.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_URI, exc.getLocalizedMessage()); + } + + if (link != null) { + NetworkTransaction retryTransaction = new NetworkTransaction(); + retryTransaction.setLink(link); + retryTransaction.setEntityType(txn.getEntityType()); + retryTransaction.setDescriptor(txn.getDescriptor()); + retryTransaction.setOperationType(HttpMethod.GET); + + /* + * IMPORTANT - DO NOT incrementAndGet the esWorkOnHand as this is a retry flow! We already + * called incrementAndGet when queuing the failed PUT! + */ + + supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, esDataProvider), + esExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED_RESYNC, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + performDocumentUpsert(result, ae); + } + }); + } + + } + } + } + + /** + * Perform document upsert. + * + * @param esGetTxn the es get txn + * @param ae the ae + */ + protected void performDocumentUpsert(NetworkTransaction esGetTxn, AggregationEntity ae) { + /** + *

    + *

      + * As part of the response processing we need to do the following: + *
    • 1. Extract the version (if present), it will be the ETAG when we use the + * Search-Abstraction-Service + *
    • 2. Spawn next task which is to do the PUT operation into elastic with or with the version + * tag + *
    • a) if version is null or RC=404, then standard put, no _update with version tag + *
    • b) if version != null, do PUT with _update?version= versionNumber in the URI to elastic + *
    + *

    + */ + String link = null; + try { + link = getElasticFullUrl("/" + ae.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_LINK_UPSERT, exc.getLocalizedMessage()); + return; + } + + String versionNumber = null; + boolean wasEntryDiscovered = false; + if (esGetTxn.getOperationResult().getResultCode() == 404) { + LOG.info(AaiUiMsgs.ES_SIMPLE_PUT, ae.getEntityPrimaryKeyValue()); + } else if (esGetTxn.getOperationResult().getResultCode() == 200) { + wasEntryDiscovered = true; + try { + versionNumber = NodeUtils.extractFieldValueFromObject( + NodeUtils.convertJsonStrToJsonNode(esGetTxn.getOperationResult().getResult()), + "_version"); + } catch (IOException exc) { + String message = + "Error extracting version number from response, aborting aggregation entity sync of " + + ae.getEntityPrimaryKeyValue() + ". Error - " + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ERROR_EXTRACTING_FROM_RESPONSE, message); + return; + } + } else { + /* + * Not being a 200 does not mean a failure. eg 201 is returned for created. TODO -> Should we + * return. + */ + LOG.error(AaiUiMsgs.ES_OPERATION_RETURN_CODE, + String.valueOf(esGetTxn.getOperationResult().getResultCode())); + return; + } + + try { + String jsonPayload = null; + if (wasEntryDiscovered) { + try { + ArrayList sourceObject = new ArrayList(); + NodeUtils.extractObjectsByKey( + NodeUtils.convertJsonStrToJsonNode(esGetTxn.getOperationResult().getResult()), + "_source", sourceObject); + + if (!sourceObject.isEmpty()) { + String responseSource = NodeUtils.convertObjectToJson(sourceObject.get(0), false); + MergableEntity me = mapper.readValue(responseSource, MergableEntity.class); + ObjectReader updater = mapper.readerForUpdating(me); + MergableEntity merged = updater.readValue(ae.getIndexDocumentJson()); + jsonPayload = mapper.writeValueAsString(merged); + } + } catch (IOException exc) { + String message = + "Error extracting source value from response, aborting aggregation entity sync of " + + ae.getEntityPrimaryKeyValue() + ". Error - " + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ERROR_EXTRACTING_FROM_RESPONSE, message); + return; + } + } else { + jsonPayload = ae.getIndexDocumentJson(); + } + + if (wasEntryDiscovered) { + if (versionNumber != null && jsonPayload != null) { + + String requestPayload = esDataProvider.buildBulkImportOperationRequest(getIndexName(), + ElasticSearchConfig.getConfig().getType(), ae.getId(), versionNumber, jsonPayload); + + NetworkTransaction transactionTracker = new NetworkTransaction(); + transactionTracker.setEntityType(esGetTxn.getEntityType()); + transactionTracker.setDescriptor(esGetTxn.getDescriptor()); + transactionTracker.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + supplyAsync(new PerformElasticSearchUpdate(ElasticSearchConfig.getConfig().getBulkUrl(), + requestPayload, esDataProvider, transactionTracker), esPutExecutor) + .whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + String message = "Aggregation entity sync UPDATE PUT error - " + + error.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } else { + updateElasticSearchCounters(result); + processStoreDocumentResult(result, esGetTxn, ae); + } + }); + } + + } else { + if (link != null && jsonPayload != null) { + + NetworkTransaction updateElasticTxn = new NetworkTransaction(); + updateElasticTxn.setLink(link); + updateElasticTxn.setEntityType(esGetTxn.getEntityType()); + updateElasticTxn.setDescriptor(esGetTxn.getDescriptor()); + updateElasticTxn.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, esDataProvider), + esPutExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + String message = + "Aggregation entity sync UPDATE PUT error - " + error.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } else { + updateElasticSearchCounters(result); + processStoreDocumentResult(result, esGetTxn, ae); + } + }); + } + } + } catch (Exception exc) { + String message = "Exception caught during aggregation entity sync PUT operation. Message - " + + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } + + /** + * Should allow retry. + * + * @param id the id + * @return true, if successful + */ + private boolean shouldAllowRetry(String id) { + boolean isRetryAllowed = true; + if (retryLimitTracker.get(id) != null) { + Integer currentCount = retryLimitTracker.get(id); + if (currentCount.intValue() >= RETRY_COUNT_PER_ENTITY_LIMIT.intValue()) { + isRetryAllowed = false; + String message = "Aggregation entity re-sync limit reached for " + id + + ", re-sync will no longer be attempted for this entity"; + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } else { + Integer newCount = new Integer(currentCount.intValue() + 1); + retryLimitTracker.put(id, newCount); + } + } else { + Integer firstRetryCount = new Integer(1); + retryLimitTracker.put(id, firstRetryCount); + } + + return isRetryAllowed; + } + + /** + * Process store document result. + * + * @param esPutResult the es put result + * @param esGetResult the es get result + * @param ae the ae + */ + private void processStoreDocumentResult(NetworkTransaction esPutResult, + NetworkTransaction esGetResult, AggregationEntity ae) { + + OperationResult or = esPutResult.getOperationResult(); + + if (!or.wasSuccessful()) { + if (or.getResultCode() == VERSION_CONFLICT_EXCEPTION_CODE) { + + if (shouldAllowRetry(ae.getId())) { + esWorkOnHand.incrementAndGet(); + + RetryAggregationEntitySyncContainer rsc = + new RetryAggregationEntitySyncContainer(esGetResult, ae); + retryQueue.push(rsc); + + String message = "Store document failed during aggregation entity synchronization" + + " due to version conflict. Entity will be re-synced."; + LOG.warn(AaiUiMsgs.ERROR_GENERIC, message); + } + } else { + String message = + "Store document failed during aggregation entity synchronization with result code " + + or.getResultCode() + " and result message " + or.getResult(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } + } + + /** + * Sync entity types. + */ + private void syncEntityTypes() { + + while (selflinks.peek() != null) { + + SelfLinkDescriptor linkDescriptor = selflinks.poll(); + aaiWorkOnHand.decrementAndGet(); + + OxmEntityDescriptor descriptor = null; + + if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { + + descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); + // go to next element in iterator + continue; + } + + NetworkTransaction txn = new NetworkTransaction(); + txn.setDescriptor(descriptor); + txn.setLink(linkDescriptor.getSelfLink()); + txn.setOperationType(HttpMethod.GET); + txn.setEntityType(linkDescriptor.getEntityType()); + + aaiWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + .whenComplete((result, error) -> { + + aaiWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.AAI_RETRIEVAL_FAILED_GENERIC, error.getLocalizedMessage()); + } else { + if (result == null) { + LOG.error(AaiUiMsgs.AAI_RETRIEVAL_FAILED_FOR_SELF_LINK, + linkDescriptor.getSelfLink()); + } else { + updateActiveInventoryCounters(result); + fetchDocumentForUpsert(result); + } + } + }); + } + + } + + } + + /** + * Fetch document for upsert. + * + * @param txn the txn + */ + private void fetchDocumentForUpsert(NetworkTransaction txn) { + // modified + if (!txn.getOperationResult().wasSuccessful()) { + String message = "Self link failure. Result - " + txn.getOperationResult().getResult(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + return; + } + + try { + final String jsonResult = txn.getOperationResult().getResult(); + if (jsonResult != null && jsonResult.length() > 0) { + + AggregationEntity ae = new AggregationEntity(oxmModelLoader); + ae.setLink( txn.getLink() ); + populateAggregationEntityDocument(ae, jsonResult, txn.getDescriptor()); + ae.deriveFields(); + + String link = null; + try { + link = getElasticFullUrl("/" + ae.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_QUERY, exc.getLocalizedMessage()); + } + + if (link != null) { + NetworkTransaction n2 = new NetworkTransaction(); + n2.setLink(link); + n2.setEntityType(txn.getEntityType()); + n2.setDescriptor(txn.getDescriptor()); + n2.setOperationType(HttpMethod.GET); + + esWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformElasticSearchRetrieval(n2, esDataProvider), esExecutor) + .whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + performDocumentUpsert(result, ae); + } + }); + } + } + + } catch (JsonProcessingException exc) { + // TODO -> LOG, waht should be logged here? + } catch (IOException exc) { + // TODO -> LOG, waht should be logged here? + } + } + + + /** + * Populate aggregation entity document. + * + * @param doc the doc + * @param result the result + * @param resultDescriptor the result descriptor + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + protected void populateAggregationEntityDocument(AggregationEntity doc, String result, + OxmEntityDescriptor resultDescriptor) throws JsonProcessingException, IOException { + doc.setEntityType(resultDescriptor.getEntityName()); + JsonNode entityNode = mapper.readTree(result); + Map map = mapper.convertValue(entityNode, Map.class); + doc.copyAttributeKeyValuePair(map); + } + + /** + * Process entity type self links. + * + * @param operationResult the operation result + */ + private void processEntityTypeSelfLinks(OperationResult operationResult) { + + JsonNode rootNode = null; + + final String jsonResult = operationResult.getResult(); + + if (jsonResult != null && jsonResult.length() > 0 && operationResult.wasSuccessful()) { + + try { + rootNode = mapper.readTree(jsonResult); + } catch (IOException exc) { + String message = + "Could not deserialize JSON (representing operation result) as node tree. " + + "Operation result = " + jsonResult + ". " + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, message); + } + + JsonNode resultData = rootNode.get("result-data"); + ArrayNode resultDataArrayNode = null; + + if (resultData.isArray()) { + resultDataArrayNode = (ArrayNode) resultData; + + Iterator elementIterator = resultDataArrayNode.elements(); + JsonNode element = null; + + while (elementIterator.hasNext()) { + element = elementIterator.next(); + + final String resourceType = NodeUtils.getNodeFieldAsText(element, "resource-type"); + final String resourceLink = NodeUtils.getNodeFieldAsText(element, "resource-link"); + + OxmEntityDescriptor descriptor = null; + + if (resourceType != null && resourceLink != null) { + + descriptor = oxmModelLoader.getEntityDescriptor(resourceType); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); + // go to next element in iterator + continue; + } + + selflinks.add(new SelfLinkDescriptor(resourceLink, SynchronizerConfiguration.NODES_ONLY_MODIFIER, resourceType)); + + + } + } + } + } + + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() + */ + @Override + public OperationState doSync() { + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "AggregationSynchronizer", "", "Sync", ""); + + return collectAllTheWork(); + } + + @Override + public SynchronizerState getState() { + + if (!isSyncDone()) { + return SynchronizerState.PERFORMING_SYNCHRONIZATION; + } + + return SynchronizerState.IDLE; + + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + */ + @Override + public String getStatReport(boolean showFinalReport) { + return getStatReport(System.currentTimeMillis() - this.syncStartedTimeStampInMs, + showFinalReport); + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() + */ + @Override + public void shutdown() { + this.shutdownExecutors(); + } + + @Override + protected boolean isSyncDone() { + + int totalWorkOnHand = aaiWorkOnHand.get() + esWorkOnHand.get(); + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, indexName + ", isSyncDone(), totalWorkOnHand = " + + totalWorkOnHand + " all work enumerated = " + allWorkEnumerated); + } + + if (totalWorkOnHand > 0 || !allWorkEnumerated) { + return false; + } + + this.syncInProgress = false; + + return true; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.AbstractEntitySynchronizer#clearCache() + */ + @Override + public void clearCache() { + + if (syncInProgress) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Autosuggestion Entity Summarizer in progress, request to clear cache ignored"); + return; + } + + super.clearCache(); + this.resetCounters(); + if (entityCounters != null) { + entityCounters.clear(); + } + + allWorkEnumerated = false; + + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/AutosuggestionSynchronizer.java b/src/main/java/org/openecomp/sparky/synchronizer/AutosuggestionSynchronizer.java new file mode 100644 index 0000000..05a9698 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/AutosuggestionSynchronizer.java @@ -0,0 +1,736 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Deque; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.config.SynchronizerConfiguration; +import org.openecomp.sparky.synchronizer.entity.SelfLinkDescriptor; +import org.openecomp.sparky.synchronizer.entity.SuggestionSearchEntity; +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.synchronizer.task.PerformActiveInventoryRetrieval; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchPut; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchRetrieval; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.util.SuggestionsPermutation; +import org.slf4j.MDC; + +import org.openecomp.cl.mdc.MdcContext; + +import org.openecomp.cl.mdc.MdcContext; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * The Class AutosuggestionSynchronizer. + */ +public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer + implements IndexSynchronizer { + + private class RetrySuggestionEntitySyncContainer { + NetworkTransaction txn; + SuggestionSearchEntity ssec; + + /** + * Instantiates a new RetrySuggestionEntitySyncContainer. + * + * @param txn the txn + * @param icer the icer + */ + public RetrySuggestionEntitySyncContainer(NetworkTransaction txn, SuggestionSearchEntity icer) { + this.txn = txn; + this.ssec = icer; + } + + public NetworkTransaction getNetworkTransaction() { + return txn; + } + + public SuggestionSearchEntity getSuggestionSearchEntity() { + return ssec; + } + } + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(AutosuggestionSynchronizer.class); + private static final String INSERTION_DATE_TIME_FORMAT = "yyyyMMdd'T'HHmmssZ"; + + private boolean allWorkEnumerated; + private Deque selflinks; + private ConcurrentHashMap entityCounters; + private boolean syncInProgress; + private Map contextMap; + protected ExecutorService esPutExecutor; + private Deque retryQueue; + private Map retryLimitTracker; + + /** + * Instantiates a new historical entity summarizer. + * + * @param indexName the index name + * @throws Exception the exception + */ + public AutosuggestionSynchronizer(String indexName) throws Exception { + super(LOG, "ASES-" + indexName.toUpperCase(), 2, 5, 5, indexName); // multiple Autosuggestion + // Entity Synchronizer will + // run for different indices + + this.allWorkEnumerated = false; + this.selflinks = new ConcurrentLinkedDeque(); + this.entityCounters = new ConcurrentHashMap(); + this.synchronizerName = "Autosuggestion Entity Synchronizer"; + this.enabledStatFlags = EnumSet.of(StatFlag.AAI_REST_STATS, StatFlag.ES_REST_STATS); + this.syncInProgress = false; + this.contextMap = MDC.getCopyOfContextMap(); + this.esPutExecutor = NodeUtils.createNamedExecutor("SUES-ES-PUT", 5, LOG); + } + + /** + * Collect all the work. + * + * @return the operation state + */ + private OperationState collectAllTheWork() { + final Map contextMap = MDC.getCopyOfContextMap(); + Map descriptorMap = + oxmModelLoader.getSuggestionSearchEntityDescriptors(); + + if (descriptorMap.isEmpty()) { + LOG.error(AaiUiMsgs.ERROR_LOADING_OXM_SUGGESTIBLE_ENTITIES); + LOG.info(AaiUiMsgs.ERROR_LOADING_OXM_SUGGESTIBLE_ENTITIES); + return OperationState.ERROR; + } + + Collection syncTypes = descriptorMap.keySet(); + + try { + + /* + * launch a parallel async thread to process the documents for each entity-type (to max the of + * the configured executor anyway) + */ + + aaiWorkOnHand.set(syncTypes.size()); + + for (String key : syncTypes) { + + supplyAsync(new Supplier() { + + @Override + public Void get() { + MDC.setContextMap(contextMap); + OperationResult typeLinksResult = null; + try { + typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(key); + aaiWorkOnHand.decrementAndGet(); + processEntityTypeSelfLinks(typeLinksResult); + } catch (Exception exc) { + // TODO -> LOG, what should be logged here? + } + + return null; + } + + }, aaiExecutor).whenComplete((result, error) -> { + + if (error != null) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "An error occurred getting data from AAI. Error = " + error.getMessage()); + } + }); + + } + + while (aaiWorkOnHand.get() != 0) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.WAIT_FOR_ALL_SELFLINKS_TO_BE_COLLECTED); + } + + Thread.sleep(1000); + } + + aaiWorkOnHand.set(selflinks.size()); + allWorkEnumerated = true; + syncEntityTypes(); + + while (!isSyncDone()) { + performRetrySync(); + Thread.sleep(1000); + } + + /* + * Make sure we don't hang on to retries that failed which could cause issues during future + * syncs + */ + retryLimitTracker.clear(); + + } catch (Exception exc) { + // TODO -> LOG, waht should be logged here? + } + + return OperationState.OK; + + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() + */ + @Override + public OperationState doSync() { + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "AutosuggestionSynchronizer", "", "Sync", ""); + + return collectAllTheWork(); + } + + /** + * Process entity type self links. + * + * @param operationResult the operation result + */ + private void processEntityTypeSelfLinks(OperationResult operationResult) { + + JsonNode rootNode = null; + + final String jsonResult = operationResult.getResult(); + + if (jsonResult != null && jsonResult.length() > 0 && operationResult.wasSuccessful()) { + + try { + rootNode = mapper.readTree(jsonResult); + } catch (IOException exc) { + String message = "Could not deserialize JSON (representing operation result) as node tree. " + + "Operation result = " + jsonResult + ". " + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, message); + } + + JsonNode resultData = rootNode.get("result-data"); + ArrayNode resultDataArrayNode = null; + + if (resultData.isArray()) { + resultDataArrayNode = (ArrayNode) resultData; + + Iterator elementIterator = resultDataArrayNode.elements(); + JsonNode element = null; + + while (elementIterator.hasNext()) { + element = elementIterator.next(); + + final String resourceType = NodeUtils.getNodeFieldAsText(element, "resource-type"); + final String resourceLink = NodeUtils.getNodeFieldAsText(element, "resource-link"); + + OxmEntityDescriptor descriptor = null; + + if (resourceType != null && resourceLink != null) { + + descriptor = oxmModelLoader.getEntityDescriptor(resourceType); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); + // go to next element in iterator + continue; + } + selflinks.add(new SelfLinkDescriptor(resourceLink, + SynchronizerConfiguration.NODES_ONLY_MODIFIER, resourceType)); + + + } + } + } + } + } + + /** + * Sync entity types. + */ + private void syncEntityTypes() { + + while (selflinks.peek() != null) { + + SelfLinkDescriptor linkDescriptor = selflinks.poll(); + aaiWorkOnHand.decrementAndGet(); + + OxmEntityDescriptor descriptor = null; + + if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { + + descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); + // go to next element in iterator + continue; + } + + NetworkTransaction txn = new NetworkTransaction(); + txn.setDescriptor(descriptor); + txn.setLink(linkDescriptor.getSelfLink()); + txn.setOperationType(HttpMethod.GET); + txn.setEntityType(linkDescriptor.getEntityType()); + + aaiWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + .whenComplete((result, error) -> { + + aaiWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.AAI_RETRIEVAL_FAILED_GENERIC, error.getLocalizedMessage()); + } else { + if (result == null) { + LOG.error(AaiUiMsgs.AAI_RETRIEVAL_FAILED_FOR_SELF_LINK, + linkDescriptor.getSelfLink()); + } else { + updateActiveInventoryCounters(result); + fetchDocumentForUpsert(result); + } + } + }); + } + + } + + } + /* + * Return a set of valid suggestion attributes for the provided entityName + * that are present in the JSON + * @param node JSON node in which the attributes should be found + * @param entityName Name of the entity + * @return List of all valid suggestion attributes(key's) + */ + public List getSuggestionFromReponse(JsonNode node, String entityName) { + List suggestableAttr = new ArrayList(); + HashMap desc = oxmModelLoader.getOxmModel().get(entityName); + String attr = desc.get("suggestibleAttributes"); + suggestableAttr = Arrays.asList(attr.split(",")); + List suggestableValue = new ArrayList<>(); + for (String attribute : suggestableAttr) { + if (node.get(attribute) != null && node.get(attribute).asText().length() > 0) { + suggestableValue.add(attribute); + } + } + return suggestableValue; + } + + /** + * Fetch all the documents for upsert. Based on the number of permutations that are available the + * number of documents will be different + * + * @param txn the txn + */ + private void fetchDocumentForUpsert(NetworkTransaction txn) { + if (!txn.getOperationResult().wasSuccessful()) { + String message = "Self link failure. Result - " + txn.getOperationResult().getResult(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + return; + } + try { + final String jsonResult = txn.getOperationResult().getResult(); + + if (jsonResult != null && jsonResult.length() > 0) { + + // Step 1: Calculate the number of possible permutations of attributes + String entityName = txn.getDescriptor().getEntityName(); + JsonNode entityNode = mapper.readTree(jsonResult); + + SuggestionsPermutation suggPermutation = new SuggestionsPermutation(); + ArrayList> uniqueLists = suggPermutation + .getSuggestionsPermutation(getSuggestionFromReponse(entityNode, entityName)); + + // Now we have a list of all possible permutations for the status that are + // defined for this entity type. Try inserting a document for every combination. + for (ArrayList uniqueList : uniqueLists) { + SuggestionSearchEntity sse = new SuggestionSearchEntity(oxmModelLoader); + sse.setSuggestableAttr(uniqueList); + sse.setPayloadFromResponse(entityNode); + sse.setLink(txn.getLink()); + populateSuggestionSearchEntityDocument(sse, jsonResult, txn); + // The unique id for the document will be created at derive fields + sse.deriveFields(); + // Insert the document only if it has valid statuses + if (sse.isSuggestableDoc()) { + String link = null; + try { + link = getElasticFullUrl("/" + sse.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_QUERY, exc.getLocalizedMessage()); + } + + if (link != null) { + NetworkTransaction n2 = new NetworkTransaction(); + n2.setLink(link); + n2.setEntityType(txn.getEntityType()); + n2.setDescriptor(txn.getDescriptor()); + n2.setOperationType(HttpMethod.GET); + + esWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformElasticSearchRetrieval(n2, esDataProvider), esExecutor) + .whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + performDocumentUpsert(result, sse); + } + }); + } + } + } + } + } catch (JsonProcessingException exc) { + // TODO -> LOG, waht should be logged here? + } catch (IOException exc) { + // TODO -> LOG, waht should be logged here? + } + } + + protected void populateSuggestionSearchEntityDocument(SuggestionSearchEntity sse, String result, + NetworkTransaction txn) throws JsonProcessingException, IOException { + + OxmEntityDescriptor resultDescriptor = txn.getDescriptor(); + + sse.setEntityType(resultDescriptor.getEntityName()); + + JsonNode entityNode = mapper.readTree(result); + + List primaryKeyValues = new ArrayList(); + String pkeyValue = null; + + for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) { + pkeyValue = NodeUtils.getNodeFieldAsText(entityNode, keyName); + if (pkeyValue != null) { + primaryKeyValues.add(pkeyValue); + } else { + String message = "populateSuggestionSearchEntityDocument()," + + " pKeyValue is null for entityType = " + resultDescriptor.getEntityName(); + LOG.warn(AaiUiMsgs.WARN_GENERIC, message); + } + } + + final String primaryCompositeKeyValue = NodeUtils.concatArray(primaryKeyValues, "/"); + sse.setEntityPrimaryKeyValue(primaryCompositeKeyValue); + sse.generateSuggestionInputPermutations(); + } + + protected void performDocumentUpsert(NetworkTransaction esGetTxn, SuggestionSearchEntity sse) { + /** + *

    + *

      + * As part of the response processing we need to do the following: + *
    • 1. Extract the version (if present), it will be the ETAG when we use the + * Search-Abstraction-Service + *
    • 2. Spawn next task which is to do the PUT operation into elastic with or with the version + * tag + *
    • a) if version is null or RC=404, then standard put, no _update with version tag + *
    • b) if version != null, do PUT with _update?version= versionNumber in the URI to elastic + *
    + *

    + */ + String link = null; + try { + link = getElasticFullUrl("/" + sse.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_LINK_UPSERT, exc.getLocalizedMessage()); + return; + } + + boolean wasEntryDiscovered = false; + if (esGetTxn.getOperationResult().getResultCode() == 404) { + LOG.info(AaiUiMsgs.ES_SIMPLE_PUT, sse.getEntityPrimaryKeyValue()); + } else if (esGetTxn.getOperationResult().getResultCode() == 200) { + wasEntryDiscovered = true; + } else { + /* + * Not being a 200 does not mean a failure. eg 201 is returned for created. and 500 for es not + * found TODO -> Should we return. + */ + LOG.error(AaiUiMsgs.ES_OPERATION_RETURN_CODE, + String.valueOf(esGetTxn.getOperationResult().getResultCode())); + return; + } + // Insert a new document only if the paylod is different. + // This is determined by hashing the payload and using it as a id for the document + // + if (!wasEntryDiscovered) { + try { + String jsonPayload = null; + + jsonPayload = sse.getIndexDocumentJson(); + if (link != null && jsonPayload != null) { + + NetworkTransaction updateElasticTxn = new NetworkTransaction(); + updateElasticTxn.setLink(link); + updateElasticTxn.setEntityType(esGetTxn.getEntityType()); + updateElasticTxn.setDescriptor(esGetTxn.getDescriptor()); + updateElasticTxn.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, esDataProvider), + esPutExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + String message = "Suggestion search entity sync UPDATE PUT error - " + + error.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ES_SUGGESTION_SEARCH_ENTITY_SYNC_ERROR, message); + } else { + updateElasticSearchCounters(result); + processStoreDocumentResult(result, esGetTxn, sse); + } + }); + } + } catch (Exception exc) { + String message = + "Exception caught during suggestion search entity sync PUT operation. Message - " + + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ES_SUGGESTION_SEARCH_ENTITY_SYNC_ERROR, message); + } + } + } + + private void processStoreDocumentResult(NetworkTransaction esPutResult, + NetworkTransaction esGetResult, SuggestionSearchEntity sse) { + + OperationResult or = esPutResult.getOperationResult(); + + if (!or.wasSuccessful()) { + if (or.getResultCode() == VERSION_CONFLICT_EXCEPTION_CODE) { + + if (shouldAllowRetry(sse.getId())) { + esWorkOnHand.incrementAndGet(); + + RetrySuggestionEntitySyncContainer rssec = + new RetrySuggestionEntitySyncContainer(esGetResult, sse); + retryQueue.push(rssec); + + String message = "Store document failed during suggestion search entity synchronization" + + " due to version conflict. Entity will be re-synced."; + LOG.warn(AaiUiMsgs.ES_SUGGESTION_SEARCH_ENTITY_SYNC_ERROR, message); + } + } else { + String message = + "Store document failed during suggestion search entity synchronization with result code " + + or.getResultCode() + " and result message " + or.getResult(); + LOG.error(AaiUiMsgs.ES_SUGGESTION_SEARCH_ENTITY_SYNC_ERROR, message); + } + } + } + + /** + * Perform retry sync. + */ + private void performRetrySync() { + while (retryQueue.peek() != null) { + + RetrySuggestionEntitySyncContainer susc = retryQueue.poll(); + if (susc != null) { + + SuggestionSearchEntity sus = susc.getSuggestionSearchEntity(); + NetworkTransaction txn = susc.getNetworkTransaction(); + + String link = null; + try { + /* + * In this retry flow the se object has already derived its fields + */ + link = getElasticFullUrl("/" + sus.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_URI, exc.getLocalizedMessage()); + } + + if (link != null) { + NetworkTransaction retryTransaction = new NetworkTransaction(); + retryTransaction.setLink(link); + retryTransaction.setEntityType(txn.getEntityType()); + retryTransaction.setDescriptor(txn.getDescriptor()); + retryTransaction.setOperationType(HttpMethod.GET); + + /* + * IMPORTANT - DO NOT incrementAndGet the esWorkOnHand as this is a retry flow! We already + * called incrementAndGet when queuing the failed PUT! + */ + + supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, esDataProvider), + esExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED_RESYNC, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + performDocumentUpsert(result, sus); + } + }); + } + + } + } + } + + /** + * Should allow retry. + * + * @param id the id + * @return true, if successful + */ + private boolean shouldAllowRetry(String id) { + boolean isRetryAllowed = true; + if (retryLimitTracker.get(id) != null) { + Integer currentCount = retryLimitTracker.get(id); + if (currentCount.intValue() >= RETRY_COUNT_PER_ENTITY_LIMIT.intValue()) { + isRetryAllowed = false; + String message = "Searchable entity re-sync limit reached for " + id + + ", re-sync will no longer be attempted for this entity"; + LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message); + } else { + Integer newCount = new Integer(currentCount.intValue() + 1); + retryLimitTracker.put(id, newCount); + } + } else { + Integer firstRetryCount = new Integer(1); + retryLimitTracker.put(id, firstRetryCount); + } + + return isRetryAllowed; + } + + + + @Override + public SynchronizerState getState() { + + if (!isSyncDone()) { + return SynchronizerState.PERFORMING_SYNCHRONIZATION; + } + + return SynchronizerState.IDLE; + + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + */ + @Override + public String getStatReport(boolean showFinalReport) { + return getStatReport(System.currentTimeMillis() - this.syncStartedTimeStampInMs, + showFinalReport); + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() + */ + @Override + public void shutdown() { + this.shutdownExecutors(); + } + + @Override + protected boolean isSyncDone() { + + int totalWorkOnHand = aaiWorkOnHand.get() + esWorkOnHand.get(); + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, indexName + ", isSyncDone(), totalWorkOnHand = " + + totalWorkOnHand + " all work enumerated = " + allWorkEnumerated); + } + + if (totalWorkOnHand > 0 || !allWorkEnumerated) { + return false; + } + + this.syncInProgress = false; + + return true; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.AbstractEntitySynchronizer#clearCache() + */ + @Override + public void clearCache() { + + if (syncInProgress) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Autosuggestion Entity Summarizer in progress, request to clear cache ignored"); + return; + } + + super.clearCache(); + this.resetCounters(); + if (entityCounters != null) { + entityCounters.clear(); + } + + allWorkEnumerated = false; + + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/CrossEntityReferenceSynchronizer.java b/src/main/java/org/openecomp/sparky/synchronizer/CrossEntityReferenceSynchronizer.java new file mode 100644 index 0000000..2ba2500 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/CrossEntityReferenceSynchronizer.java @@ -0,0 +1,879 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.CrossEntityReference; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.config.SynchronizerConfiguration; +import org.openecomp.sparky.synchronizer.entity.IndexableCrossEntityReference; +import org.openecomp.sparky.synchronizer.entity.MergableEntity; +import org.openecomp.sparky.synchronizer.entity.SelfLinkDescriptor; +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.synchronizer.task.PerformActiveInventoryRetrieval; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchPut; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchRetrieval; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchUpdate; +import org.openecomp.sparky.util.NodeUtils; +import org.slf4j.MDC; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * The Class CrossEntityReferenceSynchronizer. + */ +public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer + implements IndexSynchronizer { + + /** + * The Class RetryCrossEntitySyncContainer. + */ + private class RetryCrossEntitySyncContainer { + NetworkTransaction txn; + IndexableCrossEntityReference icer; + + /** + * Instantiates a new retry cross entity sync container. + * + * @param txn the txn + * @param icer the icer + */ + public RetryCrossEntitySyncContainer(NetworkTransaction txn, + IndexableCrossEntityReference icer) { + this.txn = txn; + this.icer = icer; + } + + public NetworkTransaction getNetworkTransaction() { + return txn; + } + + public IndexableCrossEntityReference getIndexableCrossEntityReference() { + return icer; + } + } + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(CrossEntityReferenceSynchronizer.class); + + private Deque selflinks; + private Deque retryQueue; + private Map retryLimitTracker; + private boolean isAllWorkEnumerated; + protected ExecutorService esPutExecutor; + protected ActiveInventoryConfig aaiConfig; + + /** + * Instantiates a new cross entity reference synchronizer. + * + * @param indexName the index name + * @throws Exception the exception + */ + public CrossEntityReferenceSynchronizer(String indexName, ActiveInventoryConfig aaiConfig) throws Exception { + super(LOG, "CERS", 2, 5, 5, indexName); + this.selflinks = new ConcurrentLinkedDeque(); + this.retryQueue = new ConcurrentLinkedDeque(); + this.retryLimitTracker = new ConcurrentHashMap(); + this.synchronizerName = "Cross Reference Entity Synchronizer"; + this.isAllWorkEnumerated = false; + this.esPutExecutor = NodeUtils.createNamedExecutor("CERS-ES-PUT", 5, LOG); + this.aaiEntityStats.initializeCountersFromOxmEntityDescriptors( + oxmModelLoader.getCrossReferenceEntityDescriptors()); + this.esEntityStats.initializeCountersFromOxmEntityDescriptors( + oxmModelLoader.getCrossReferenceEntityDescriptors()); + this.aaiConfig = aaiConfig; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() + */ + @Override + public OperationState doSync() { + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "CrossEntitySynchronizer", "", "Sync", ""); + + resetCounters(); + syncStartedTimeStampInMs = System.currentTimeMillis(); + launchSyncFlow(); + return OperationState.OK; + } + + @Override + public SynchronizerState getState() { + if (!isSyncDone()) { + return SynchronizerState.PERFORMING_SYNCHRONIZATION; + } + + return SynchronizerState.IDLE; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + */ + @Override + public String getStatReport(boolean showFinalReport) { + return this.getStatReport(System.currentTimeMillis() - syncStartedTimeStampInMs, + showFinalReport); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() + */ + @Override + public void shutdown() { + this.shutdownExecutors(); + } + + @Override + protected boolean isSyncDone() { + int totalWorkOnHand = aaiWorkOnHand.get() + esWorkOnHand.get(); + + if (totalWorkOnHand > 0 || !isAllWorkEnumerated) { + return false; + } + + return true; + } + + /** + * Launch sync flow. + * + * @return the operation state + */ + private OperationState launchSyncFlow() { + final Map contextMap = MDC.getCopyOfContextMap(); + Map descriptorMap = + oxmModelLoader.getCrossReferenceEntityDescriptors(); + + if (descriptorMap.isEmpty()) { + LOG.error(AaiUiMsgs.ERROR_LOADING_OXM); + + return OperationState.ERROR; + } + + Collection syncTypes = descriptorMap.keySet(); + + try { + + /* + * launch a parallel async thread to process the documents for each entity-type (to max the of + * the configured executor anyway) + */ + + aaiWorkOnHand.set(syncTypes.size()); + + for (String key : syncTypes) { + + supplyAsync(new Supplier() { + + @Override + public Void get() { + MDC.setContextMap(contextMap); + OperationResult typeLinksResult = null; + try { + typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(key); + aaiWorkOnHand.decrementAndGet(); + processEntityTypeSelfLinks(typeLinksResult); + } catch (Exception exc) { + // TODO -> LOG, what should be logged here? + } + + return null; + } + + }, aaiExecutor).whenComplete((result, error) -> { + if (error != null) { + LOG.error(AaiUiMsgs.ERROR_GETTING_DATA_FROM_AAI, error.getMessage()); + } + }); + } + + while (aaiWorkOnHand.get() != 0) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.WAIT_FOR_ALL_SELFLINKS_TO_BE_COLLECTED); + } + + Thread.sleep(1000); + } + + aaiWorkOnHand.set(selflinks.size()); + isAllWorkEnumerated = true; + performSync(); + + while (!isSyncDone()) { + performRetrySync(); + Thread.sleep(1000); + } + + /* + * Make sure we don't hang on to retries that failed which could cause issues during future + * syncs + */ + retryLimitTracker.clear(); + + } catch (Exception exc) { + // TODO -> LOG, waht should be logged here? + } + + return OperationState.OK; + } + + /** + * Perform sync. + */ + private void performSync() { + while (selflinks.peek() != null) { + + SelfLinkDescriptor linkDescriptor = selflinks.poll(); + aaiWorkOnHand.decrementAndGet(); + + OxmEntityDescriptor descriptor = null; + + if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { + + descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); + // go to next element in iterator + continue; + } + + if (descriptor.hasCrossEntityReferences()) { + + NetworkTransaction txn = new NetworkTransaction(); + txn.setDescriptor(descriptor); + txn.setLink(linkDescriptor.getSelfLink() + linkDescriptor.getDepthModifier()); + txn.setOperationType(HttpMethod.GET); + txn.setEntityType(linkDescriptor.getEntityType()); + + aaiWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + .whenComplete((result, error) -> { + + aaiWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.SELF_LINK_GET, error.getLocalizedMessage()); + } else { + if (result == null) { + LOG.error(AaiUiMsgs.SELF_LINK_CROSS_REF_SYNC); + } else { + updateActiveInventoryCounters(result); + fetchDocumentForUpsert(result); + } + } + }); + } + } + } + } + + /** + * Process entity type self links. + * + * @param operationResult the operation result + */ + private void processEntityTypeSelfLinks(OperationResult operationResult) { + + JsonNode rootNode = null; + + final String jsonResult = operationResult.getResult(); + + if (jsonResult != null && jsonResult.length() > 0) { + + try { + rootNode = mapper.readTree(jsonResult); + } catch (IOException exc) { + // TODO // TODO -> LOG, waht should be logged here? + } + + JsonNode resultData = rootNode.get("result-data"); + ArrayNode resultDataArrayNode = null; + + if (resultData.isArray()) { + resultDataArrayNode = (ArrayNode) resultData; + + Iterator elementIterator = resultDataArrayNode.elements(); + JsonNode element = null; + + while (elementIterator.hasNext()) { + element = elementIterator.next(); + + final String resourceType = NodeUtils.getNodeFieldAsText(element, "resource-type"); + final String resourceLink = NodeUtils.getNodeFieldAsText(element, "resource-link"); + + OxmEntityDescriptor descriptor = null; + + if (resourceType != null && resourceLink != null) { + descriptor = oxmModelLoader.getEntityDescriptor(resourceType); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); + // go to next element in iterator + continue; + } + if (descriptor.hasCrossEntityReferences()) { + selflinks.add(new SelfLinkDescriptor( + resourceLink,SynchronizerConfiguration.DEPTH_ALL_MODIFIER, resourceType)); + } + } + } + } + } + } + + + + /** + * By providing the entity type and a json node for the entity, determine the + * primary key name(s) + primary key value(s) sufficient to build an entity query string + * of the following format: + * + * .: + * + * @return - a composite string in the above format or null + */ + private String determineEntityQueryString(String entityType, JsonNode entityJsonNode) { + + OxmEntityDescriptor entityDescriptor = + oxmModelLoader.getEntityDescriptor(entityType); + + String queryString = null; + + if ( entityDescriptor != null ) { + + final List primaryKeyNames = entityDescriptor.getPrimaryKeyAttributeName(); + final List keyValues = new ArrayList(); + NodeUtils.extractFieldValuesFromObject(entityJsonNode, primaryKeyNames, keyValues); + + queryString = entityType + "." + NodeUtils.concatArray(primaryKeyNames,"/") + ":" + NodeUtils.concatArray(keyValues); + + } + + return queryString; + + + } + + /** + * Fetch document for upsert. + * + * @param txn the txn + */ + private void fetchDocumentForUpsert(NetworkTransaction txn) { + + if (!txn.getOperationResult().wasSuccessful()) { + LOG.error(AaiUiMsgs.SELF_LINK_GET, txn.getOperationResult().getResult()); + return; + } + + if (txn.getDescriptor().hasCrossEntityReferences()) { + + final String jsonResult = txn.getOperationResult().getResult(); + + if (jsonResult != null && jsonResult.length() > 0) { + + /** + * Here's what we are going to do: + * + *
  • Extract primary key name and value from the parent type. + *
  • Extract the primary key and value from the nested child instance. + *
  • Build a generic query to discover the self-link for the nested-child-instance using + * parent and child. + *
  • Set the self-link on the child. + *
  • Generate the id that will allow the elastic-search upsert to work. + *
  • Rinse and repeat. + */ + + OxmEntityDescriptor parentEntityDescriptor = + oxmModelLoader.getEntityDescriptor(txn.getEntityType()); + + if ( parentEntityDescriptor != null ) { + + CrossEntityReference cerDefinition = parentEntityDescriptor.getCrossEntityReference(); + + if (cerDefinition != null) { + JsonNode convertedNode = null; + try { + convertedNode = NodeUtils.convertJsonStrToJsonNode(txn.getOperationResult().getResult()); + + final String parentEntityQueryString = determineEntityQueryString(txn.getEntityType(), convertedNode); + + List extractedParentEntityAttributeValues = new ArrayList(); + + NodeUtils.extractFieldValuesFromObject(convertedNode, + cerDefinition.getReferenceAttributes(), + extractedParentEntityAttributeValues); + + List nestedTargetEntityInstances = new ArrayList(); + NodeUtils.extractObjectsByKey(convertedNode, cerDefinition.getTargetEntityType(), + nestedTargetEntityInstances); + + for (JsonNode targetEntityInstance : nestedTargetEntityInstances) { + + OxmEntityDescriptor cerDescriptor = + oxmModelLoader.getSearchableEntityDescriptor(cerDefinition.getTargetEntityType()); + + if (cerDescriptor != null) { + + String childEntityType = cerDefinition.getTargetEntityType(); + + List childPrimaryKeyNames = cerDescriptor.getPrimaryKeyAttributeName(); + + List childKeyValues = new ArrayList(); + NodeUtils.extractFieldValuesFromObject(targetEntityInstance, childPrimaryKeyNames, childKeyValues); + + String childEntityQueryKeyString = childEntityType + "." + NodeUtils.concatArray(childPrimaryKeyNames,"/") + ":" + NodeUtils.concatArray(childKeyValues); + + /** + * Build generic-query to query child instance self-link from AAI + */ + List orderedQueryKeyParams = new ArrayList(); + orderedQueryKeyParams.add(parentEntityQueryString); + orderedQueryKeyParams.add(childEntityQueryKeyString); + String genericQueryStr = null; + try { + genericQueryStr = aaiDataProvider.getGenericQueryForSelfLink(childEntityType, orderedQueryKeyParams); + + if (genericQueryStr != null) { + + OperationResult aaiQueryResult = aaiDataProvider.queryActiveInventoryWithRetries( + genericQueryStr, "application/json", + aaiConfig.getAaiRestConfig().getNumRequestRetries()); + + if (aaiQueryResult!= null && aaiQueryResult.wasSuccessful()) { + + Collection entityLinks = new ArrayList(); + JsonNode genericQueryResult = null; + try { + genericQueryResult = NodeUtils.convertJsonStrToJsonNode(aaiQueryResult.getResult()); + + if ( genericQueryResult != null ) { + + NodeUtils.extractObjectsByKey(genericQueryResult, "resource-link", entityLinks); + + String selfLink = null; + + if (entityLinks.size() != 1) { + /** + * an ambiguity exists where we can't reliably determine the self + * link, this should be a permanent error + */ + LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_SELFLINK_AMBIGUITY, String.valueOf(entityLinks.size())); + } else { + selfLink = ((JsonNode) entityLinks.toArray()[0]).asText(); + + if (!cerDescriptor.getSearchableAttributes().isEmpty()) { + + IndexableCrossEntityReference icer = + getPopulatedDocument(targetEntityInstance, cerDescriptor); + + for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) { + icer.addCrossEntityReferenceValue( + parentCrossEntityReferenceAttributeValue); + } + + icer.setLink(selfLink); + + icer.deriveFields(); + + String link = null; + try { + link = getElasticFullUrl("/" + icer.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_QUERY, exc.getLocalizedMessage()); + } + + if (link != null) { + NetworkTransaction n2 = new NetworkTransaction(); + n2.setLink(link); + n2.setEntityType(txn.getEntityType()); + n2.setDescriptor(txn.getDescriptor()); + n2.setOperationType(HttpMethod.GET); + + esWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformElasticSearchRetrieval(n2, esDataProvider), + esExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + performDocumentUpsert(result, icer); + } + }); + } + } + } + } else { + LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_DURING_AAI_RESPONSE_CONVERSION); + } + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, JsonNode.class.toString(), exc.getLocalizedMessage()); + } + + } else { + String message = "Entity sync failed because AAI query failed with error " + aaiQueryResult.getResult(); + LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_QUERY_ERROR, message); + } + + } else { + String message = "Entity Sync failed because generic query str could not be determined."; + LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_QUERY_ERROR, message); + } + } catch (Exception exc) { + String message = "Failed to sync entity because generation of generic query failed with error = " + exc.getMessage(); + LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_QUERY_ERROR, message); + } + + } + } + + } catch (IOException ioe) { + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, ioe.getMessage()); + } + } + + } else { + LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_DESCRIPTOR_NOT_FOUND, txn.getEntityType()); + } + } + } + } + + /** + * Perform document upsert. + * + * @param esGetResult the es get result + * @param icer the icer + */ + protected void performDocumentUpsert(NetworkTransaction esGetResult, + IndexableCrossEntityReference icer) { + /** + *

    + *

      + * As part of the response processing we need to do the following: + *
    • 1. Extract the version (if present), it will be the ETAG when we use the + * Search-Abstraction-Service + *
    • 2. Spawn next task which is to do the PUT operation into elastic with or with the version + * tag + *
    • a) if version is null or RC=404, then standard put, no _update with version tag + *
    • b) if version != null, do PUT with _update?version= (versionNumber) in the URI to elastic + *
    + *

    + */ + String link = null; + try { + link = getElasticFullUrl("/" + icer.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_LINK_UPSERT, exc.getLocalizedMessage()); + return; + } + + boolean wasEntryDiscovered = false; + String versionNumber = null; + if (esGetResult.getOperationResult().getResultCode() == 404) { + LOG.info(AaiUiMsgs.ES_SIMPLE_PUT, icer.getEntityPrimaryKeyValue()); + } else if (esGetResult.getOperationResult().getResultCode() == 200) { + wasEntryDiscovered = true; + try { + versionNumber = NodeUtils.extractFieldValueFromObject( + NodeUtils.convertJsonStrToJsonNode(esGetResult.getOperationResult().getResult()), + "_version"); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.ES_ABORT_CROSS_ENTITY_REF_SYNC, "version Number", + icer.getEntityPrimaryKeyValue(), exc.getLocalizedMessage()); + return; + } + } else { + /* + * Not being a 200 does not mean a failure. eg 201 is returned for created. TODO -> Should we + * return. + */ + LOG.info(AaiUiMsgs.ES_OPERATION_RETURN_CODE, + String.valueOf(esGetResult.getOperationResult().getResultCode())); + return; + } + + try { + String jsonPayload = null; + if (wasEntryDiscovered) { + try { + ArrayList sourceObject = new ArrayList(); + NodeUtils.extractObjectsByKey( + NodeUtils.convertJsonStrToJsonNode(esGetResult.getOperationResult().getResult()), + "_source", sourceObject); + + if (!sourceObject.isEmpty()) { + String responseSource = NodeUtils.convertObjectToJson(sourceObject.get(0), false); + MergableEntity me = mapper.readValue(responseSource, MergableEntity.class); + ObjectReader updater = mapper.readerForUpdating(me); + MergableEntity merged = updater.readValue(icer.getIndexDocumentJson()); + jsonPayload = mapper.writeValueAsString(merged); + } + } catch (IOException exc) { + LOG.error(AaiUiMsgs.ES_ABORT_CROSS_ENTITY_REF_SYNC, "source value", + icer.getEntityPrimaryKeyValue(), exc.getLocalizedMessage()); + return; + } + } else { + jsonPayload = icer.getIndexDocumentJson(); + } + + if (wasEntryDiscovered) { + if (versionNumber != null && jsonPayload != null) { + + String requestPayload = esDataProvider.buildBulkImportOperationRequest(getIndexName(), + ElasticSearchConfig.getConfig().getType(), icer.getId(), versionNumber, jsonPayload); + + NetworkTransaction transactionTracker = new NetworkTransaction(); + transactionTracker.setEntityType(esGetResult.getEntityType()); + transactionTracker.setDescriptor(esGetResult.getDescriptor()); + transactionTracker.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + supplyAsync(new PerformElasticSearchUpdate(ElasticSearchConfig.getConfig().getBulkUrl(), + requestPayload, esDataProvider, transactionTracker), esPutExecutor) + .whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_CROSS_ENTITY_REF_PUT, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + processStoreDocumentResult(result, esGetResult, icer); + } + }); + } + + } else { + if (link != null && jsonPayload != null) { + + NetworkTransaction updateElasticTxn = new NetworkTransaction(); + updateElasticTxn.setLink(link); + updateElasticTxn.setEntityType(esGetResult.getEntityType()); + updateElasticTxn.setDescriptor(esGetResult.getDescriptor()); + updateElasticTxn.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, esDataProvider), + esPutExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_CROSS_ENTITY_REF_PUT, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + processStoreDocumentResult(result, esGetResult, icer); + } + }); + } + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_CROSS_ENTITY_REF_PUT, exc.getLocalizedMessage()); + } + } + + /** + * Process store document result. + * + * @param esPutResult the es put result + * @param esGetResult the es get result + * @param icer the icer + */ + private void processStoreDocumentResult(NetworkTransaction esPutResult, + NetworkTransaction esGetResult, IndexableCrossEntityReference icer) { + + OperationResult or = esPutResult.getOperationResult(); + + if (!or.wasSuccessful()) { + if (or.getResultCode() == VERSION_CONFLICT_EXCEPTION_CODE) { + + if (shouldAllowRetry(icer.getId())) { + + esWorkOnHand.incrementAndGet(); + + RetryCrossEntitySyncContainer rsc = new RetryCrossEntitySyncContainer(esGetResult, icer); + retryQueue.push(rsc); + + LOG.warn(AaiUiMsgs.ES_CROSS_REF_SYNC_VERSION_CONFLICT); + } + } else { + LOG.error(AaiUiMsgs.ES_CROSS_REF_SYNC_FAILURE, String.valueOf(or.getResultCode()), + or.getResult()); + } + } + } + + /** + * Perform retry sync. + */ + private void performRetrySync() { + while (retryQueue.peek() != null) { + + RetryCrossEntitySyncContainer rsc = retryQueue.poll(); + if (rsc != null) { + + IndexableCrossEntityReference icer = rsc.getIndexableCrossEntityReference(); + NetworkTransaction txn = rsc.getNetworkTransaction(); + + String link = null; + try { + // In this retry flow the icer object has already + // derived its fields + link = getElasticFullUrl("/" + icer.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_URI, exc.getLocalizedMessage()); + } + + if (link != null) { + NetworkTransaction retryTransaction = new NetworkTransaction(); + retryTransaction.setLink(link); + retryTransaction.setEntityType(txn.getEntityType()); + retryTransaction.setDescriptor(txn.getDescriptor()); + retryTransaction.setOperationType(HttpMethod.GET); + + /* + * IMPORTANT - DO NOT incrementAndGet the esWorkOnHand as this is a retry flow and we did + * that for this request already when queuing the failed PUT! + */ + + supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, esDataProvider), + esExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED_RESYNC, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + performDocumentUpsert(result, icer); + } + }); + } + + } + } + } + + /** + * Should allow retry. + * + * @param id the id + * @return true, if successful + */ + private boolean shouldAllowRetry(String id) { + boolean isRetryAllowed = true; + if (retryLimitTracker.get(id) != null) { + Integer currentCount = retryLimitTracker.get(id); + if (currentCount.intValue() >= RETRY_COUNT_PER_ENTITY_LIMIT.intValue()) { + isRetryAllowed = false; + LOG.error(AaiUiMsgs.ES_CROSS_ENTITY_RESYNC_LIMIT, id); + } else { + Integer newCount = new Integer(currentCount.intValue() + 1); + retryLimitTracker.put(id, newCount); + } + + } else { + Integer firstRetryCount = new Integer(1); + retryLimitTracker.put(id, firstRetryCount); + } + + return isRetryAllowed; + } + + /** + * Gets the populated document. + * + * @param entityNode the entity node + * @param resultDescriptor the result descriptor + * @return the populated document + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + protected IndexableCrossEntityReference getPopulatedDocument(JsonNode entityNode, + OxmEntityDescriptor resultDescriptor) throws JsonProcessingException, IOException { + + IndexableCrossEntityReference icer = new IndexableCrossEntityReference(oxmModelLoader); + + icer.setEntityType(resultDescriptor.getEntityName()); + + List primaryKeyValues = new ArrayList(); + String pkeyValue = null; + + for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) { + pkeyValue = NodeUtils.getNodeFieldAsText(entityNode, keyName); + if (pkeyValue != null) { + primaryKeyValues.add(pkeyValue); + } else { + LOG.warn(AaiUiMsgs.ES_PKEYVALUE_NULL, resultDescriptor.getEntityName()); + } + } + + final String primaryCompositeKeyValue = NodeUtils.concatArray(primaryKeyValues, "/"); + icer.setEntityPrimaryKeyValue(primaryCompositeKeyValue); + + return icer; + + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/ElasticSearchIndexCleaner.java b/src/main/java/org/openecomp/sparky/synchronizer/ElasticSearchIndexCleaner.java new file mode 100644 index 0000000..37b27fd --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/ElasticSearchIndexCleaner.java @@ -0,0 +1,642 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestDataProvider; +import org.openecomp.sparky.synchronizer.entity.ObjectIdCollection; +import org.openecomp.sparky.synchronizer.entity.SearchableEntity; +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.logging.AaiUiMsgs; + +/** + * The Class ElasticSearchIndexCleaner. + */ +public class ElasticSearchIndexCleaner implements IndexCleaner { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(ElasticSearchIndexCleaner.class); + + private static final String BULK_OP_LINE_TEMPLATE = "%s\n"; + private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + + private ObjectIdCollection before; + private ObjectIdCollection after; + + private String host; + private String port; + + private String indexName; + private String indexType; + private int scrollContextTimeToLiveInMinutes; + private int numItemsToGetBulkRequest; + + private RestDataProvider restDataProvider; + private ObjectMapper mapper; + + /** + * Instantiates a new elastic search index cleaner. + * + * @param restDataProvider the rest data provider + * @param indexName the index name + * @param indexType the index type + * @param host the host + * @param port the port + * @param scrollContextTimeToLiveInMinutes the scroll context time to live in minutes + * @param numItemsToGetBulkRequest the num items to get bulk request + */ + protected ElasticSearchIndexCleaner(RestDataProvider restDataProvider, String indexName, + String indexType, String host, String port, int scrollContextTimeToLiveInMinutes, + int numItemsToGetBulkRequest) { + this.restDataProvider = restDataProvider; + this.before = null; + this.after = null; + this.indexName = indexName; + this.indexType = indexType; + this.mapper = new ObjectMapper(); + this.host = host; + this.port = port; + this.scrollContextTimeToLiveInMinutes = scrollContextTimeToLiveInMinutes; + this.numItemsToGetBulkRequest = numItemsToGetBulkRequest; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexCleaner#populatePreOperationCollection() + */ + @Override + public OperationState populatePreOperationCollection() { + + try { + before = retrieveAllDocumentIdentifiers(); + return OperationState.OK; + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_PRE_SYNC_FAILURE, indexName, exc.getMessage()); + return OperationState.ERROR; + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexCleaner#populatePostOperationCollection() + */ + @Override + public OperationState populatePostOperationCollection() { + try { + after = retrieveAllDocumentIdentifiers(); + return OperationState.OK; + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_PRE_SYNC_FAILURE, indexName, exc.getMessage()); + return OperationState.ERROR; + } + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexCleaner#performCleanup() + */ + @Override + public OperationState performCleanup() { + // TODO Auto-generated method stub + LOG.info(AaiUiMsgs.ES_SYNC_CLEAN_UP, indexName); + + int sizeBefore = before.getSize(); + int sizeAfter = after.getSize(); + + LOG.info(AaiUiMsgs.ES_SYNC_CLEAN_UP_SIZE, String.valueOf(sizeBefore), + String.valueOf(sizeAfter)); + + /* + * If the processedImportIds size <= 0, then something has failed in the sync operation and we + * shouldn't do the selective delete right now. + */ + + if (sizeAfter > 0) { + + Collection presyncIds = before.getImportedObjectIds(); + presyncIds.removeAll(after.getImportedObjectIds()); + + try { + LOG.info(AaiUiMsgs.ES_SYNC_SELECTIVE_DELETE, indexName, indexType, + String.valueOf(presyncIds.size())); + + ObjectIdCollection bulkIds = new ObjectIdCollection(); + + Iterator it = presyncIds.iterator(); + int numItemsInBulkRequest = 0; + int numItemsRemainingToBeDeleted = presyncIds.size(); + + while (it.hasNext()) { + + bulkIds.addObjectId(it.next()); + numItemsInBulkRequest++; + + if (numItemsInBulkRequest >= this.numItemsToGetBulkRequest) { + LOG.info(AaiUiMsgs.ES_BULK_DELETE, indexName, String.valueOf(bulkIds.getSize())); + OperationResult bulkDeleteResult = bulkDelete(bulkIds.getImportedObjectIds()); + // pegCountersForElasticBulkDelete(bulkDeleteResult); + numItemsRemainingToBeDeleted -= numItemsInBulkRequest; + numItemsInBulkRequest = 0; + bulkIds.clear(); + } + } + + if (numItemsRemainingToBeDeleted > 0) { + LOG.info(AaiUiMsgs.ES_BULK_DELETE, indexName, String.valueOf(bulkIds.getSize())); + OperationResult bulkDeleteResult = bulkDelete(bulkIds.getImportedObjectIds()); + // pegCountersForElasticBulkDelete(bulkDeleteResult); + } + + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_BULK_DELETE_ERROR, indexName, exc.getLocalizedMessage()); + + } + } + + return OperationState.OK; + } + + @Override + public String getIndexName() { + return indexName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + /** + * Builds the initial scroll request payload. + * + * @param numItemsToGetPerRequest the num items to get per request + * @param fieldList the field list + * @return the string + * @throws JsonProcessingException the json processing exception + */ + protected String buildInitialScrollRequestPayload(int numItemsToGetPerRequest, + List fieldList) throws JsonProcessingException { + + ObjectNode rootNode = mapper.createObjectNode(); + rootNode.put("size", numItemsToGetPerRequest); + + ArrayNode fields = mapper.createArrayNode(); + + for (String f : fieldList) { + fields.add(f); + } + + rootNode.set("fields", fields); + + ObjectNode queryNode = mapper.createObjectNode(); + queryNode.set("match_all", mapper.createObjectNode()); + + rootNode.set("query", queryNode); + + return mapper.writeValueAsString(rootNode); + + } + + /** + * Builds the subsequent scroll context request payload. + * + * @param scrollId the scroll id + * @param contextTimeToLiveInMinutes the context time to live in minutes + * @return the string + * @throws JsonProcessingException the json processing exception + */ + protected String buildSubsequentScrollContextRequestPayload(String scrollId, + int contextTimeToLiveInMinutes) throws JsonProcessingException { + + ObjectNode rootNode = mapper.createObjectNode(); + + rootNode.put("scroll", contextTimeToLiveInMinutes + "m"); + rootNode.put("scroll_id", scrollId); + + return mapper.writeValueAsString(rootNode); + + } + + /** + * Parses the elastic search result. + * + * @param jsonResult the json result + * @return the json node + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + protected JsonNode parseElasticSearchResult(String jsonResult) + throws JsonProcessingException, IOException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(jsonResult); + } + + /** + * Lookup index doc. + * + * @param ids the ids + * @param docs the docs + * @return the array list + */ + protected ArrayList lookupIndexDoc(ArrayList ids, + List docs) { + ArrayList objs = new ArrayList(); + + if (ids != null && docs != null) { + for (SearchableEntity d : docs) { + if (ids.contains(d.getId())) { + objs.add(d); + } + } + } + + return objs; + } + + /** + * Builds the delete data object. + * + * @param index the index + * @param type the type + * @param id the id + * @return the object node + */ + protected ObjectNode buildDeleteDataObject(String index, String type, String id) { + + ObjectNode indexDocProperties = mapper.createObjectNode(); + + indexDocProperties.put("_index", index); + indexDocProperties.put("_type", type); + indexDocProperties.put("_id", id); + + ObjectNode rootNode = mapper.createObjectNode(); + rootNode.set("delete", indexDocProperties); + + return rootNode; + } + + /** + * This method might appear to be a little strange, and is simply an optimization to take an + * elipsed JsonNode key path and retrieve the node at the end of the path, if it exists. + * + * @param startNode the start node + * @param fieldPath the field path + * @return the node path + */ + protected JsonNode getNodePath(JsonNode startNode, String... fieldPath) { + + JsonNode jsonNode = null; + + for (String field : fieldPath) { + if (jsonNode == null) { + jsonNode = startNode.get(field); + } else { + jsonNode = jsonNode.get(field); + } + + /* + * This is our safety net in case any intermediate path returns a null + */ + + if (jsonNode == null) { + return null; + } + + } + + return jsonNode; + } + + /** + * Gets the full url. + * + * @param resourceUrl the resource url + * @return the full url + */ + private String getFullUrl(String resourceUrl) { + return String.format("http://%s:%s%s", host, port, resourceUrl); + } + + /** + * Retrieve all document identifiers. + * + * @return the object id collection + * @throws IOException Signals that an I/O exception has occurred. + */ + public ObjectIdCollection retrieveAllDocumentIdentifiers() throws IOException { + + ObjectIdCollection currentDocumentIds = new ObjectIdCollection(); + + long opStartTimeInMs = System.currentTimeMillis(); + + List fields = new ArrayList(); + fields.add("_id"); + // fields.add("entityType"); + + String scrollRequestPayload = + buildInitialScrollRequestPayload(this.numItemsToGetBulkRequest, fields); + + final String fullUrlStr = getFullUrl("/" + indexName + "/" + indexType + "/_search?scroll=" + + this.scrollContextTimeToLiveInMinutes + "m"); + + OperationResult result = + restDataProvider.doPost(fullUrlStr, scrollRequestPayload, "application/json"); + + if (result.wasSuccessful()) { + + JsonNode rootNode = parseElasticSearchResult(result.getResult()); + + /* + * Check the result for success / failure, and enumerate all the index ids that resulted in + * success, and ignore the ones that failed or log them so we have a record of the failure. + */ + int totalRecordsAvailable = 0; + String scrollId = null; + int numRecordsFetched = 0; + + if (rootNode != null) { + + scrollId = getFieldValue(rootNode, "_scroll_id"); + final String tookStr = getFieldValue(rootNode, "took"); + int tookInMs = (tookStr == null) ? 0 : Integer.parseInt(tookStr); + boolean timedOut = Boolean.parseBoolean(getFieldValue(rootNode, "timed_out")); + + if (timedOut) { + LOG.error(AaiUiMsgs.COLLECT_TIME_WITH_ERROR, "all document Identifiers", + String.valueOf(tookInMs)); + } else { + LOG.info(AaiUiMsgs.COLLECT_TIME_WITH_SUCCESS, "all document Identifiers", + String.valueOf(tookInMs)); + } + + JsonNode hitsNode = rootNode.get("hits"); + totalRecordsAvailable = Integer.parseInt(hitsNode.get("total").asText()); + + LOG.info(AaiUiMsgs.COLLECT_TOTAL, "all document Identifiers", + String.valueOf(totalRecordsAvailable)); + + /* + * Collect all object ids + */ + + ArrayNode hitsArray = (ArrayNode) hitsNode.get("hits"); + + Iterator nodeIterator = hitsArray.iterator(); + + String key = null; + String value = null; + JsonNode jsonNode = null; + + while (nodeIterator.hasNext()) { + + jsonNode = nodeIterator.next(); + + key = getFieldValue(jsonNode, "_id"); + + if (key != null) { + currentDocumentIds.addObjectId(key); + } + + /* + * if (key != null) { + * + * JsonNode fieldsNode = jNode.get("fields"); + * + * if (fieldsNode != null) { + * + * JsonNode entityTypeNode = fieldsNode.get("entityType"); + * + * if (entityTypeNode != null) { ArrayNode aNode = (ArrayNode) entityTypeNode; + * + * if (aNode.size() > 0) { value = aNode.get(0).asText(); objAndtTypesMap.put(key, value); + * numRecordsFetched++; } } } } + */ + + } + + int totalRecordsRemainingToFetch = (totalRecordsAvailable - numRecordsFetched); + + int numRequiredAdditionalFetches = + (totalRecordsRemainingToFetch / this.numItemsToGetBulkRequest); + + /* + * Do an additional fetch for the remaining items (if needed) + */ + + if (totalRecordsRemainingToFetch % numItemsToGetBulkRequest != 0) { + numRequiredAdditionalFetches += 1; + } + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.SYNC_NUMBER_REQ_FETCHES, + String.valueOf(numRequiredAdditionalFetches)); + } + + + for (int x = 0; x < numRequiredAdditionalFetches; x++) { + + if (collectItemsFromScrollContext(scrollId, currentDocumentIds) != OperationState.OK) { + // abort the whole thing because now we can't reliably cleanup the orphans. + throw new IOException( + "Failed to collect pre-sync doc collection from index. Aborting operation"); + } + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.SYNC_NUMBER_TOTAL_FETCHES, + String.valueOf(currentDocumentIds.getSize()), + String.valueOf(totalRecordsAvailable)); + } + + } + + } + + } else { + // scroll context get failed, nothing else to do + LOG.error(AaiUiMsgs.ERROR_GENERIC, result.toString()); + } + + LOG.info(AaiUiMsgs.COLLECT_TOTAL_TIME, "all document Identifiers", + String.valueOf((System.currentTimeMillis() - opStartTimeInMs))); + + return currentDocumentIds; + + } + + /** + * Collect items from scroll context. + * + * @param scrollId the scroll id + * @param objectIds the object ids + * @return the operation state + * @throws IOException Signals that an I/O exception has occurred. + */ + private OperationState collectItemsFromScrollContext(String scrollId, + ObjectIdCollection objectIds) throws IOException { + + // ObjectIdCollection documentIdCollection = new ObjectIdCollection(); + + String requestPayload = + buildSubsequentScrollContextRequestPayload(scrollId, scrollContextTimeToLiveInMinutes); + + final String fullUrlStr = getFullUrl("/_search/scroll"); + + OperationResult opResult = + restDataProvider.doPost(fullUrlStr, requestPayload, "application/json"); + + if (opResult.getResultCode() >= 300) { + LOG.warn(AaiUiMsgs.ES_SCROLL_CONTEXT_ERROR, opResult.getResult()); + return OperationState.ERROR; + } + + JsonNode rootNode = parseElasticSearchResult(opResult.getResult()); + boolean timedOut = Boolean.parseBoolean(getFieldValue(rootNode, "timed_out")); + final String tookStr = getFieldValue(rootNode, "took"); + int tookInMs = (tookStr == null) ? 0 : Integer.parseInt(tookStr); + + JsonNode hitsNode = rootNode.get("hits"); + + /* + * Check the result for success / failure, and enumerate all the index ids that resulted in + * success, and ignore the ones that failed or log them so we have a record of the failure. + */ + + if (rootNode != null) { + + if (timedOut) { + LOG.info(AaiUiMsgs.COLLECT_TIME_WITH_ERROR, "Scroll Context", String.valueOf(tookInMs)); + } else { + LOG.info(AaiUiMsgs.COLLECT_TIME_WITH_SUCCESS, "Scroll Context", String.valueOf(tookInMs)); + } + + /* + * Collect all object ids + */ + + ArrayNode hitsArray = (ArrayNode) hitsNode.get("hits"); + String key = null; + String value = null; + JsonNode jsonNode = null; + + Iterator nodeIterator = hitsArray.iterator(); + + while (nodeIterator.hasNext()) { + + jsonNode = nodeIterator.next(); + + key = getFieldValue(jsonNode, "_id"); + + if (key != null) { + objectIds.addObjectId(key); + + /* + * JsonNode fieldsNode = jNode.get("fields"); + * + * if (fieldsNode != null) { + * + * JsonNode entityTypeNode = fieldsNode.get("entityType"); + * + * if (entityTypeNode != null) { ArrayNode aNode = (ArrayNode) entityTypeNode; + * + * if (aNode.size() > 0) { value = aNode.get(0).asText(); objectIdsAndTypes.put(key, + * value); } } } } + */ + + } + + } + } + + return OperationState.OK; + } + + /** + * Gets the field value. + * + * @param node the node + * @param fieldName the field name + * @return the field value + */ + protected String getFieldValue(JsonNode node, String fieldName) { + + JsonNode field = node.get(fieldName); + + if (field != null) { + return field.asText(); + } + + return null; + + } + + /** + * Bulk delete. + * + * @param docIds the doc ids + * @return the operation result + * @throws IOException Signals that an I/O exception has occurred. + */ + public OperationResult bulkDelete(Collection docIds) throws IOException { + + if (docIds == null || docIds.size() == 0) { + LOG.info(AaiUiMsgs.ES_BULK_DELETE_SKIP); + return new OperationResult(500, + "Skipping bulkDelete(); operation because docs to delete list is empty"); + } + + LOG.info(AaiUiMsgs.ES_BULK_DELETE_START, String.valueOf(docIds.size())); + + StringBuilder sb = new StringBuilder(128); + + for (String id : docIds) { + sb.append( + String.format(BULK_OP_LINE_TEMPLATE, buildDeleteDataObject(indexName, indexType, id))); + } + + sb.append("\n"); + + final String fullUrlStr = getFullUrl("/_bulk"); + + return restDataProvider.doPost(fullUrlStr, sb.toString(), "application/x-www-form-urlencoded"); + + } + + /* + + */ + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/GeoSynchronizer.java b/src/main/java/org/openecomp/sparky/synchronizer/GeoSynchronizer.java new file mode 100644 index 0000000..e53c5a7 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/GeoSynchronizer.java @@ -0,0 +1,469 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Supplier; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.inventory.entity.GeoIndexDocument; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.entity.SelfLinkDescriptor; +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.synchronizer.task.PerformActiveInventoryRetrieval; +import org.openecomp.sparky.synchronizer.task.StoreDocumentTask; +import org.openecomp.sparky.util.NodeUtils; +import org.slf4j.MDC; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + + +/** + * The Class GeoSynchronizer. + */ +public class GeoSynchronizer extends AbstractEntitySynchronizer implements IndexSynchronizer { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(GeoSynchronizer.class); + + private boolean allWorkEnumerated; + private Deque selflinks; + + private ElasticSearchConfig elasticConfig = null; + private Map geoDescriptorMap = null; + + /** + * Instantiates a new geo synchronizer. + * + * @param indexName the index name + * @throws Exception the exception + */ + public GeoSynchronizer(String indexName) throws Exception { + + super(LOG, "GEO", 2, 5, 5, indexName); + this.allWorkEnumerated = false; + this.selflinks = new ConcurrentLinkedDeque(); + this.synchronizerName = "Geo Synchronizer"; + this.geoDescriptorMap = oxmModelLoader.getGeoEntityDescriptors(); + this.aaiEntityStats.initializeCountersFromOxmEntityDescriptors(geoDescriptorMap); + this.esEntityStats.initializeCountersFromOxmEntityDescriptors(geoDescriptorMap); + + } + + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() + */ + @Override + public OperationState doSync() { + resetCounters(); + allWorkEnumerated = false; + syncStartedTimeStampInMs = System.currentTimeMillis(); + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "GeoSynchronizer", "", "Sync", ""); + + collectAllTheWork(); + return OperationState.OK; + } + + + /** + * Collect all the work. + * + * @return the operation state + */ + public OperationState collectAllTheWork() { + final Map contextMap = MDC.getCopyOfContextMap(); + if (elasticConfig == null) { + try { + elasticConfig = ElasticSearchConfig.getConfig(); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.CONFIGURATION_ERROR, "Search"); + } + } + + if (geoDescriptorMap.isEmpty()) { + LOG.error(AaiUiMsgs.OXM_FAILED_RETRIEVAL, "geo entities"); + return OperationState.ERROR; + } + + Collection syncTypes = geoDescriptorMap.keySet(); + + try { + + /* + * launch a parallel async thread to process the documents for each entity-type (to max the of + * the configured executor anyway) + */ + + aaiWorkOnHand.set(syncTypes.size()); + + for (String key : syncTypes) { + + supplyAsync(new Supplier() { + + @Override + public Void get() { + MDC.setContextMap(contextMap); + OperationResult typeLinksResult = null; + try { + typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(key); + aaiWorkOnHand.decrementAndGet(); + processEntityTypeSelfLinks(typeLinksResult); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_GETTING_DATA_FROM_AAI, exc); + } + + return null; + } + + }, aaiExecutor).whenComplete((result, error) -> { + + if (error != null) { + LOG.error(AaiUiMsgs.ERROR_GETTING_DATA_FROM_AAI, error.getMessage()); + } + }); + + } + + while (aaiWorkOnHand.get() != 0) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.WAIT_FOR_ALL_SELFLINKS_TO_BE_COLLECTED); + } + + Thread.sleep(1000); + } + + aaiWorkOnHand.set(selflinks.size()); + allWorkEnumerated = true; + syncEntityTypes(); + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_GETTING_DATA_FROM_AAI, exc); + } + return OperationState.OK; + } + + /** + * Sync entity types. + */ + private void syncEntityTypes() { + + while (selflinks.peek() != null) { + + SelfLinkDescriptor linkDescriptor = selflinks.poll(); + aaiWorkOnHand.decrementAndGet(); + + OxmEntityDescriptor descriptor = null; + + if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { + + descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); + // go to next element in iterator + continue; + } + + NetworkTransaction txn = new NetworkTransaction(); + txn.setDescriptor(descriptor); + txn.setLink(linkDescriptor.getSelfLink()); + txn.setOperationType(HttpMethod.GET); + txn.setEntityType(linkDescriptor.getEntityType()); + + aaiWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + .whenComplete((result, error) -> { + + aaiWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ERROR_GETTING_DATA_FROM_AAI, error.getMessage()); + } else { + if (result == null) { + LOG.error(AaiUiMsgs.SELF_LINK_GET_NO_RESPONSE, linkDescriptor.getSelfLink()); + } else { + processEntityTypeSelfLinkResult(result); + } + } + }); + } + } + } + + /** + * Process entity type self links. + * + * @param operationResult the operation result + */ + private void processEntityTypeSelfLinks(OperationResult operationResult) { + + JsonNode rootNode = null; + + final String jsonResult = operationResult.getResult(); + + if (jsonResult != null && jsonResult.length() > 0 && operationResult.wasSuccessful()) { + + try { + rootNode = mapper.readTree(jsonResult); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, exc); + } + + JsonNode resultData = rootNode.get("result-data"); + ArrayNode resultDataArrayNode = null; + + if (resultData.isArray()) { + resultDataArrayNode = (ArrayNode) resultData; + + Iterator elementIterator = resultDataArrayNode.elements(); + JsonNode element = null; + + while (elementIterator.hasNext()) { + element = elementIterator.next(); + + final String resourceType = NodeUtils.getNodeFieldAsText(element, "resource-type"); + final String resourceLink = NodeUtils.getNodeFieldAsText(element, "resource-link"); + + if (resourceType != null && resourceLink != null) { + + if (geoDescriptorMap.containsKey(resourceType)) { + selflinks.add(new SelfLinkDescriptor(resourceLink + "?nodes-only", resourceType)); + } else { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); + // go to next element in iterator + continue; + } + + } + } + } + } + + } + + /** + * Process entity type self link result. + * + * @param txn the txn + */ + private void processEntityTypeSelfLinkResult(NetworkTransaction txn) { + + updateActiveInventoryCounters(txn); + + if (!txn.getOperationResult().wasSuccessful()) { + return; + } + + try { + if (!(txn.getDescriptor().getGeoLatName().isEmpty() + && txn.getDescriptor().getGeoLongName().isEmpty())) { + + GeoIndexDocument geoDoc = new GeoIndexDocument(oxmModelLoader); + + final String jsonResult = txn.getOperationResult().getResult(); + + if (jsonResult != null && jsonResult.length() > 0) { + + populateGeoDocument(geoDoc, jsonResult, txn.getDescriptor(), txn.getLink()); + + if (!geoDoc.isValidGeoDocument()) { + + LOG.info(AaiUiMsgs.GEO_SYNC_IGNORING_ENTITY, geoDoc.getEntityType(), geoDoc.toString()); + + } else { + + String link = null; + try { + link = getElasticFullUrl("/" + geoDoc.getId(), getIndexName(), "default"); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_URI, exc); + } + + if (link != null) { + + NetworkTransaction n2 = new NetworkTransaction(); + n2.setLink(link); + n2.setEntityType(txn.getEntityType()); + n2.setDescriptor(txn.getDescriptor()); + n2.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + + supplyAsync(new StoreDocumentTask(geoDoc, n2, esDataProvider), esExecutor) + .whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_STORE_FAILURE, error.getMessage()); + } else { + updateElasticSearchCounters(result); + processStoreDocumentResult(result); + } + }); + } + } + } + } + } catch (JsonProcessingException exc) { + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, exc); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, exc); + } + + return; + } + + + /** + * Process store document result. + * + * @param txn the txn + */ + private void processStoreDocumentResult(NetworkTransaction txn) { + + OperationResult or = txn.getOperationResult(); + + if (!or.wasSuccessful()) { + LOG.error(AaiUiMsgs.ES_STORE_FAILURE, or.toString()); + /* + * if(or.getResultCode() != 404 || (or.getResultCode() == 404 && + * !synchronizerConfig.isResourceNotFoundErrorsSupressed())) { logger.error( + * "Skipping failed resource = " + "link" + " RC=[" + or.getResultCode() + "]. Message: " + + * or.getResult()); } + */ + + } + + } + + + @Override + public SynchronizerState getState() { + + if (!isSyncDone()) { + return SynchronizerState.PERFORMING_SYNCHRONIZATION; + } + + return SynchronizerState.IDLE; + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + */ + @Override + public String getStatReport(boolean showFinalReport) { + return this.getStatReport(System.currentTimeMillis() - syncStartedTimeStampInMs, + showFinalReport); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() + */ + @Override + public void shutdown() { + this.shutdownExecutors(); + } + + /** + * Populate geo document. + * + * @param doc the doc + * @param result the result + * @param resultDescriptor the result descriptor + * @param entityLink the entity link + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + protected void populateGeoDocument(GeoIndexDocument doc, String result, + OxmEntityDescriptor resultDescriptor, String entityLink) + throws JsonProcessingException, IOException { + + doc.setSelfLink(entityLink); + doc.setEntityType(resultDescriptor.getEntityName()); + + JsonNode entityNode = mapper.readTree(result); + + List primaryKeyValues = new ArrayList(); + String pkeyValue = null; + + for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) { + pkeyValue = NodeUtils.getNodeFieldAsText(entityNode, keyName); + if (pkeyValue != null) { + primaryKeyValues.add(pkeyValue); + } else { + LOG.warn(AaiUiMsgs.ES_PKEYVALUE_NULL, resultDescriptor.getEntityName()); + } + } + + final String primaryCompositeKeyValue = NodeUtils.concatArray(primaryKeyValues, "/"); + doc.setEntityPrimaryKeyValue(primaryCompositeKeyValue); + String geoLatKey = resultDescriptor.getGeoLatName(); + String geoLongKey = resultDescriptor.getGeoLongName(); + + doc.setLatitude(NodeUtils.getNodeFieldAsText(entityNode, geoLatKey)); + doc.setLongitude(NodeUtils.getNodeFieldAsText(entityNode, geoLongKey)); + doc.deriveFields(); + + } + + @Override + protected boolean isSyncDone() { + int totalWorkOnHand = aaiWorkOnHand.get() + esWorkOnHand.get(); + + if (totalWorkOnHand > 0 || !allWorkEnumerated) { + return false; + } + + return true; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/HistoricalEntitySummarizer.java b/src/main/java/org/openecomp/sparky/synchronizer/HistoricalEntitySummarizer.java new file mode 100644 index 0000000..81201d2 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/HistoricalEntitySummarizer.java @@ -0,0 +1,374 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +import javax.json.Json; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.util.NodeUtils; +import org.slf4j.MDC; + +import org.openecomp.cl.mdc.MdcContext; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * The Class HistoricalEntitySummarizer. + */ +public class HistoricalEntitySummarizer extends AbstractEntitySynchronizer + implements IndexSynchronizer { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(HistoricalEntitySummarizer.class); + private static final String INSERTION_DATE_TIME_FORMAT = "yyyyMMdd'T'HHmmssZ"; + + private boolean allWorkEnumerated; + private ConcurrentHashMap entityCounters; + private boolean syncInProgress; + private Map contextMap; + + /** + * Instantiates a new historical entity summarizer. + * + * @param indexName the index name + * @throws Exception the exception + */ + public HistoricalEntitySummarizer(String indexName) throws Exception { + super(LOG, "HES", 2, 5, 5, indexName); + + this.allWorkEnumerated = false; + this.entityCounters = new ConcurrentHashMap(); + this.synchronizerName = "Historical Entity Summarizer"; + this.enabledStatFlags = EnumSet.of(StatFlag.AAI_REST_STATS, StatFlag.ES_REST_STATS); + this.syncInProgress = false; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /** + * Collect all the work. + * + * @return the operation state + */ + private OperationState collectAllTheWork() { + + Map descriptorMap = + oxmModelLoader.getSearchableEntityDescriptors(); + + if (descriptorMap.isEmpty()) { + LOG.error(AaiUiMsgs.OXM_FAILED_RETRIEVAL, "historical entities"); + + return OperationState.ERROR; + } + + Collection entityTypes = descriptorMap.keySet(); + + AtomicInteger asyncWoH = new AtomicInteger(0); + + asyncWoH.set(entityTypes.size()); + + try { + for (String entityType : entityTypes) { + + supplyAsync(new Supplier() { + + @Override + public Void get() { + MDC.setContextMap(contextMap); + try { + OperationResult typeLinksResult = + aaiDataProvider.getSelfLinksByEntityType(entityType); + updateActiveInventoryCounters(HttpMethod.GET, entityType, typeLinksResult); + processEntityTypeSelfLinks(entityType, typeLinksResult); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_GETTING_DATA_FROM_AAI, exc.getMessage()); + + } + + return null; + } + + }, aaiExecutor).whenComplete((result, error) -> { + + asyncWoH.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.HISTORICAL_COLLECT_ERROR, error.getMessage()); + } + + }); + + } + + + while (asyncWoH.get() > 0) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, indexName + " summarizer waiting for all the links to be processed."); + } + + Thread.sleep(250); + } + + esWorkOnHand.set(entityCounters.size()); + + // start doing the real work + allWorkEnumerated = true; + + insertEntityTypeCounters(); + + if (LOG.isDebugEnabled()) { + + StringBuilder sb = new StringBuilder(128); + + sb.append("\n\nHistorical Entity Counters:"); + + for (Entry entry : entityCounters.entrySet()) { + sb.append("\n").append(entry.getKey()).append(" = ").append(entry.getValue().get()); + } + + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, sb.toString()); + + } + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.HISTORICAL_COLLECT_ERROR, exc.getMessage()); + + + esWorkOnHand.set(0); + allWorkEnumerated = true; + + return OperationState.ERROR; + } + + return OperationState.OK; + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() + */ + @Override + public OperationState doSync() { + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "HistoricalEntitySynchronizer", "", "Sync", ""); + + if (syncInProgress) { + LOG.info(AaiUiMsgs.HISTORICAL_SYNC_PENDING); + return OperationState.PENDING; + } + + clearCache(); + + syncInProgress = true; + this.syncStartedTimeStampInMs = System.currentTimeMillis(); + allWorkEnumerated = false; + + return collectAllTheWork(); + } + + /** + * Process entity type self links. + * + * @param entityType the entity type + * @param operationResult the operation result + */ + private void processEntityTypeSelfLinks(String entityType, OperationResult operationResult) { + + JsonNode rootNode = null; + + final String jsonResult = operationResult.getResult(); + + if (jsonResult != null && jsonResult.length() > 0 && operationResult.wasSuccessful()) { + + try { + rootNode = mapper.readTree(jsonResult); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, exc.getMessage()); + return; + } + + JsonNode resultData = rootNode.get("result-data"); + ArrayNode resultDataArrayNode = null; + + if (resultData != null && resultData.isArray()) { + resultDataArrayNode = (ArrayNode) resultData; + entityCounters.put(entityType, new AtomicInteger(resultDataArrayNode.size())); + } + } + + } + + /** + * Insert entity type counters. + */ + private void insertEntityTypeCounters() { + + if (esWorkOnHand.get() <= 0) { + return; + } + + SimpleDateFormat dateFormat = new SimpleDateFormat(INSERTION_DATE_TIME_FORMAT); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + String currentFormattedTimeStamp = dateFormat.format(timestamp); + + Set> entityCounterEntries = entityCounters.entrySet(); + + for (Entry entityCounterEntry : entityCounterEntries) { + + supplyAsync(new Supplier() { + + @Override + public Void get() { + MDC.setContextMap(contextMap); + String jsonString = Json.createObjectBuilder().add( + "count", entityCounterEntry.getValue().get()) + .add("entityType", entityCounterEntry.getKey()) + .add("timestamp", currentFormattedTimeStamp).build().toString(); + + String link = null; + try { + link = getElasticFullUrl("", indexName); + OperationResult or = esDataProvider.doPost(link, jsonString, "application/json"); + updateElasticSearchCounters(HttpMethod.POST, entityCounterEntry.getKey(), or); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_STORE_FAILURE, exc.getMessage() ); + } + + return null; + } + + }, esExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + }); + + } + + while (esWorkOnHand.get() > 0) { + + try { + Thread.sleep(500); + } catch (InterruptedException exc) { + LOG.error(AaiUiMsgs.INTERRUPTED, "historical Entities", exc.getMessage()); + } + } + + } + + @Override + public SynchronizerState getState() { + + if (!isSyncDone()) { + return SynchronizerState.PERFORMING_SYNCHRONIZATION; + } + + return SynchronizerState.IDLE; + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + */ + @Override + public String getStatReport(boolean showFinalReport) { + return getStatReport(System.currentTimeMillis() - this.syncStartedTimeStampInMs, + showFinalReport); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() + */ + @Override + public void shutdown() { + this.shutdownExecutors(); + } + + @Override + protected boolean isSyncDone() { + + int totalWorkOnHand = aaiWorkOnHand.get() + esWorkOnHand.get(); + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC,indexName + ", isSyncDone(), totalWorkOnHand = " + totalWorkOnHand + + " all work enumerated = " + allWorkEnumerated); + } + + if (totalWorkOnHand > 0 || !allWorkEnumerated) { + return false; + } + + this.syncInProgress = false; + + return true; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.AbstractEntitySynchronizer#clearCache() + */ + @Override + public void clearCache() { + + if (syncInProgress) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "Historical Entity Summarizer in progress, request to clear cache ignored"); + return; + } + + super.clearCache(); + this.resetCounters(); + if (entityCounters != null) { + entityCounters.clear(); + } + + allWorkEnumerated = false; + + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/IndexCleaner.java b/src/main/java/org/openecomp/sparky/synchronizer/IndexCleaner.java new file mode 100644 index 0000000..51ce652 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/IndexCleaner.java @@ -0,0 +1,58 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import org.openecomp.sparky.synchronizer.enumeration.OperationState; + +/** + * The Interface IndexCleaner. + */ +public interface IndexCleaner { + + /** + * Populate pre operation collection. + * + * @return the operation state + */ + public OperationState populatePreOperationCollection(); + + /** + * Populate post operation collection. + * + * @return the operation state + */ + public OperationState populatePostOperationCollection(); + + /** + * Perform cleanup. + * + * @return the operation state + */ + public OperationState performCleanup(); + + public String getIndexName(); + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/IndexIntegrityValidator.java b/src/main/java/org/openecomp/sparky/synchronizer/IndexIntegrityValidator.java new file mode 100644 index 0000000..dcd016b --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/IndexIntegrityValidator.java @@ -0,0 +1,165 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestDataProvider; +import org.openecomp.sparky.logging.AaiUiMsgs; + +/** + * The Class IndexIntegrityValidator. + * + * @author davea. + */ +public class IndexIntegrityValidator implements IndexValidator { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(IndexIntegrityValidator.class); + + private String host; + private String port; + private String indexName; + private String indexType; + private String tableConfigJson; + + private final RestDataProvider restDataProvider; + + /** + * Instantiates a new index integrity validator. + * + * @param restDataProvider the rest data provider + * @param indexName the index name + * @param indexType the index type + * @param host the host + * @param port the port + * @param tableConfigJson the table config json + */ + public IndexIntegrityValidator(RestDataProvider restDataProvider, String indexName, + String indexType, String host, String port, String tableConfigJson) { + this.restDataProvider = restDataProvider; + this.host = host; + this.port = port; + this.indexName = indexName; + this.indexType = indexType; + this.tableConfigJson = tableConfigJson; + } + + @Override + public String getIndexName() { + return indexName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + public String getIndexType() { + return indexType; + } + + public void setIndexType(String indexType) { + this.indexType = indexType; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexValidator#exists() + */ + @Override + public boolean exists() { + final String fullUrlStr = getFullUrl("/" + indexName + "/"); + OperationResult existsResult = restDataProvider.doHead(fullUrlStr, "application/json"); + + int rc = existsResult.getResultCode(); + + if (rc >= 200 && rc < 300) { + LOG.info(AaiUiMsgs.INDEX_EXISTS, indexName); + return true; + } else { + LOG.info(AaiUiMsgs.INDEX_NOT_EXIST, indexName); + return false; + } + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexValidator#integrityValid() + */ + @Override + public boolean integrityValid() { + // TODO Auto-generated method stub + // logger.info("; + // System.out.println("IndexIntegrityValidator.integrityValid() for + // indexName = " + indexName); + return true; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexValidator#createOrRepair() + */ + @Override + public void createOrRepair() { + // TODO Auto-generated method stub + String message = "IndexIntegrityValidator.createOrRepair() for indexName = " + indexName; + LOG.info(AaiUiMsgs.INFO_GENERIC, message); + + final String fullUrlStr = getFullUrl("/" + indexName + "/"); + OperationResult createResult = + restDataProvider.doPut(fullUrlStr, tableConfigJson, "application/json"); + + int rc = createResult.getResultCode(); + + if (rc >= 200 && rc < 300) { + LOG.info(AaiUiMsgs.INDEX_RECREATED, indexName); + } else if (rc == 400) { + LOG.info(AaiUiMsgs.INDEX_ALREADY_EXISTS, indexName); + } else { + LOG.warn(AaiUiMsgs.INDEX_INTEGRITY_CHECK_FAILED, indexName, createResult.getResult()); + } + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexValidator#destroyIndex() + */ + @Override + public void destroyIndex() { + // TODO Auto-generated method stub + // we don't do this for now + + } + + /** + * Gets the full url. + * + * @param resourceUrl the resource url + * @return the full url + */ + private String getFullUrl(String resourceUrl) { + return String.format("http://%s:%s%s", host, port, resourceUrl); + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/IndexSynchronizer.java b/src/main/java/org/openecomp/sparky/synchronizer/IndexSynchronizer.java new file mode 100644 index 0000000..520606d --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/IndexSynchronizer.java @@ -0,0 +1,68 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; + +/** + * The Interface IndexSynchronizer. + * + * @author davea. + */ +public interface IndexSynchronizer { + + /** + * Do sync. + * + * @return the operation state + */ + public OperationState doSync(); + + public SynchronizerState getState(); + + /** + * Gets the stat report. + * + * @param finalReport the final report + * @return the stat report + */ + public String getStatReport(boolean finalReport); + + /** + * Shutdown. + */ + public void shutdown(); + + public String getIndexName(); + + /** + * Clear cache. + */ + public void clearCache(); + + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/IndexValidator.java b/src/main/java/org/openecomp/sparky/synchronizer/IndexValidator.java new file mode 100644 index 0000000..29d44e3 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/IndexValidator.java @@ -0,0 +1,59 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +/** + * The Interface IndexValidator. + */ +public interface IndexValidator { + + /** + * Exists. + * + * @return true, if successful + */ + public boolean exists(); + + /** + * Integrity valid. + * + * @return true, if successful + */ + public boolean integrityValid(); + + /** + * Creates the or repair. + */ + public void createOrRepair(); + + /** + * Destroy index. + */ + public void destroyIndex(); + + public String getIndexName(); + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/MyErrorHandler.java b/src/main/java/org/openecomp/sparky/synchronizer/MyErrorHandler.java new file mode 100644 index 0000000..e1a695c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/MyErrorHandler.java @@ -0,0 +1,94 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import java.io.PrintWriter; + +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * The Class MyErrorHandler. + */ +public class MyErrorHandler implements ErrorHandler { + + /** Error handler output goes here. */ + private PrintWriter out; + + /** + * Instantiates a new my error handler. + * + * @param out the out + */ + public MyErrorHandler(PrintWriter out) { + this.out = out; + } + + /** + * Returns a string describing parse exception details. + * + * @param spe the spe + * @return the parses the exception info + */ + private String getParseExceptionInfo(SAXParseException spe) { + String systemId = spe.getSystemId(); + if (systemId == null) { + systemId = "null"; + } + String info = "URI=" + systemId + " Line=" + spe.getLineNumber() + ": " + spe.getMessage(); + return info; + } + + // The following methods are standard SAX ErrorHandler methods. + // See SAX documentation for more info. + + /* (non-Javadoc) + * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException) + */ + @Override + public void warning(SAXParseException spe) throws SAXException { + out.println("Warning: " + getParseExceptionInfo(spe)); + } + + /* (non-Javadoc) + * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException) + */ + @Override + public void error(SAXParseException spe) throws SAXException { + String message = "Error: " + getParseExceptionInfo(spe); + throw new SAXException(message); + } + + /* (non-Javadoc) + * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException) + */ + @Override + public void fatalError(SAXParseException spe) throws SAXException { + String message = "Fatal Error: " + getParseExceptionInfo(spe); + throw new SAXException(message); + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/SearchableEntitySynchronizer.java b/src/main/java/org/openecomp/sparky/synchronizer/SearchableEntitySynchronizer.java new file mode 100644 index 0000000..3ebf203 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/SearchableEntitySynchronizer.java @@ -0,0 +1,760 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import org.openecomp.cl.mdc.MdcContext; + +import org.openecomp.cl.mdc.MdcContext; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.node.ArrayNode; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.HttpMethod; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.config.SynchronizerConfiguration; +import org.openecomp.sparky.synchronizer.entity.MergableEntity; +import org.openecomp.sparky.synchronizer.entity.SearchableEntity; +import org.openecomp.sparky.synchronizer.entity.SelfLinkDescriptor; +import org.openecomp.sparky.synchronizer.enumeration.OperationState; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.synchronizer.task.PerformActiveInventoryRetrieval; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchPut; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchRetrieval; +import org.openecomp.sparky.synchronizer.task.PerformElasticSearchUpdate; +import org.openecomp.sparky.util.NodeUtils; +import org.slf4j.MDC; + +/** + * The Class SearchableEntitySynchronizer. + */ +public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer + implements IndexSynchronizer { + + /** + * The Class RetrySearchableEntitySyncContainer. + */ + private class RetrySearchableEntitySyncContainer { + NetworkTransaction txn; + SearchableEntity se; + + /** + * Instantiates a new retry searchable entity sync container. + * + * @param txn the txn + * @param se the se + */ + public RetrySearchableEntitySyncContainer(NetworkTransaction txn, SearchableEntity se) { + this.txn = txn; + this.se = se; + } + + public NetworkTransaction getNetworkTransaction() { + return txn; + } + + public SearchableEntity getSearchableEntity() { + return se; + } + } + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(SearchableEntitySynchronizer.class); + + private boolean allWorkEnumerated; + private Deque selflinks; + private Deque retryQueue; + private Map retryLimitTracker; + protected ExecutorService esPutExecutor; + + /** + * Instantiates a new searchable entity synchronizer. + * + * @param indexName the index name + * @throws Exception the exception + */ + public SearchableEntitySynchronizer(String indexName) throws Exception { + super(LOG, "SES", 2, 5, 5, indexName); + this.allWorkEnumerated = false; + this.selflinks = new ConcurrentLinkedDeque(); + this.retryQueue = new ConcurrentLinkedDeque(); + this.retryLimitTracker = new ConcurrentHashMap(); + this.synchronizerName = "Searchable Entity Synchronizer"; + this.esPutExecutor = NodeUtils.createNamedExecutor("SES-ES-PUT", 5, LOG); + this.aaiEntityStats.initializeCountersFromOxmEntityDescriptors( + oxmModelLoader.getSearchableEntityDescriptors()); + this.esEntityStats.initializeCountersFromOxmEntityDescriptors( + oxmModelLoader.getSearchableEntityDescriptors()); + } + + /** + * Collect all the work. + * + * @return the operation state + */ + private OperationState collectAllTheWork() { + final Map contextMap = MDC.getCopyOfContextMap(); + Map descriptorMap = + oxmModelLoader.getSearchableEntityDescriptors(); + + if (descriptorMap.isEmpty()) { + LOG.error(AaiUiMsgs.ERROR_LOADING_OXM_SEARCHABLE_ENTITIES); + LOG.info(AaiUiMsgs.ERROR_LOADING_OXM_SEARCHABLE_ENTITIES); + return OperationState.ERROR; + } + + Collection syncTypes = descriptorMap.keySet(); + + /*Collection syncTypes = new ArrayList(); + syncTypes.add("service-instance");*/ + + try { + + /* + * launch a parallel async thread to process the documents for each entity-type (to max the + * of the configured executor anyway) + */ + + aaiWorkOnHand.set(syncTypes.size()); + + for (String key : syncTypes) { + + supplyAsync(new Supplier() { + + @Override + public Void get() { + MDC.setContextMap(contextMap); + OperationResult typeLinksResult = null; + try { + typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(key); + aaiWorkOnHand.decrementAndGet(); + processEntityTypeSelfLinks(typeLinksResult); + } catch (Exception exc) { + // TODO -> LOG, what should be logged here? + } + + return null; + } + + }, aaiExecutor).whenComplete((result, error) -> { + + if (error != null) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "An error occurred getting data from AAI. Error = " + error.getMessage()); + } + }); + + } + + while (aaiWorkOnHand.get() != 0) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.WAIT_FOR_ALL_SELFLINKS_TO_BE_COLLECTED); + } + + Thread.sleep(1000); + } + + aaiWorkOnHand.set(selflinks.size()); + allWorkEnumerated = true; + syncEntityTypes(); + + while (!isSyncDone()) { + performRetrySync(); + Thread.sleep(1000); + } + + /* + * Make sure we don't hang on to retries that failed which could cause issues during future + * syncs + */ + retryLimitTracker.clear(); + + } catch (Exception exc) { + // TODO -> LOG, waht should be logged here? + } + + return OperationState.OK; + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() + */ + @Override + public OperationState doSync() { + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "SearchableEntitySynchronizer", "", "Sync", ""); + + resetCounters(); + this.allWorkEnumerated = false; + syncStartedTimeStampInMs = System.currentTimeMillis(); + collectAllTheWork(); + + return OperationState.OK; + } + + /** + * Process entity type self links. + * + * @param operationResult the operation result + */ + private void processEntityTypeSelfLinks(OperationResult operationResult) { + + JsonNode rootNode = null; + + final String jsonResult = operationResult.getResult(); + + if (jsonResult != null && jsonResult.length() > 0 && operationResult.wasSuccessful()) { + + try { + rootNode = mapper.readTree(jsonResult); + } catch (IOException exc) { + String message = + "Could not deserialize JSON (representing operation result) as node tree. " + + "Operation result = " + jsonResult + ". " + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, message); + } + + JsonNode resultData = rootNode.get("result-data"); + ArrayNode resultDataArrayNode = null; + + if (resultData.isArray()) { + resultDataArrayNode = (ArrayNode) resultData; + + Iterator elementIterator = resultDataArrayNode.elements(); + JsonNode element = null; + + while (elementIterator.hasNext()) { + element = elementIterator.next(); + + final String resourceType = NodeUtils.getNodeFieldAsText(element, "resource-type"); + final String resourceLink = NodeUtils.getNodeFieldAsText(element, "resource-link"); + + OxmEntityDescriptor descriptor = null; + + if (resourceType != null && resourceLink != null) { + + descriptor = oxmModelLoader.getEntityDescriptor(resourceType); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); + // go to next element in iterator + continue; + } + + if (descriptor.hasSearchableAttributes()) { + selflinks.add(new SelfLinkDescriptor(resourceLink, SynchronizerConfiguration.NODES_ONLY_MODIFIER, resourceType)); + } + + } + } + } + } + + } + + /** + * Sync entity types. + */ + private void syncEntityTypes() { + + while (selflinks.peek() != null) { + + SelfLinkDescriptor linkDescriptor = selflinks.poll(); + aaiWorkOnHand.decrementAndGet(); + + OxmEntityDescriptor descriptor = null; + + if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { + + descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); + // go to next element in iterator + continue; + } + + NetworkTransaction txn = new NetworkTransaction(); + txn.setDescriptor(descriptor); + txn.setLink(linkDescriptor.getSelfLink()); + txn.setOperationType(HttpMethod.GET); + txn.setEntityType(linkDescriptor.getEntityType()); + + aaiWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + .whenComplete((result, error) -> { + + aaiWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.AAI_RETRIEVAL_FAILED_GENERIC, error.getLocalizedMessage()); + } else { + if (result == null) { + LOG.error(AaiUiMsgs.AAI_RETRIEVAL_FAILED_FOR_SELF_LINK, + linkDescriptor.getSelfLink()); + } else { + updateActiveInventoryCounters(result); + fetchDocumentForUpsert(result); + } + } + }); + } + + } + + } + + /** + * Perform document upsert. + * + * @param esGetTxn the es get txn + * @param se the se + */ + protected void performDocumentUpsert(NetworkTransaction esGetTxn, SearchableEntity se) { + /** + *

    + *

      + * As part of the response processing we need to do the following: + *
    • 1. Extract the version (if present), it will be the ETAG when we use the + * Search-Abstraction-Service + *
    • 2. Spawn next task which is to do the PUT operation into elastic with or with the version + * tag + *
    • a) if version is null or RC=404, then standard put, no _update with version tag + *
    • b) if version != null, do PUT with _update?version= versionNumber in the URI to elastic + *
    + *

    + */ + String link = null; + try { + link = getElasticFullUrl("/" + se.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_LINK_UPSERT, exc.getLocalizedMessage()); + return; + } + + String versionNumber = null; + boolean wasEntryDiscovered = false; + if (esGetTxn.getOperationResult().getResultCode() == 404) { + LOG.info(AaiUiMsgs.ES_SIMPLE_PUT, se.getEntityPrimaryKeyValue()); + } else if (esGetTxn.getOperationResult().getResultCode() == 200) { + wasEntryDiscovered = true; + try { + versionNumber = NodeUtils.extractFieldValueFromObject( + NodeUtils.convertJsonStrToJsonNode(esGetTxn.getOperationResult().getResult()), + "_version"); + } catch (IOException exc) { + String message = + "Error extracting version number from response, aborting searchable entity sync of " + + se.getEntityPrimaryKeyValue() + ". Error - " + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ERROR_EXTRACTING_FROM_RESPONSE, message); + return; + } + } else { + /* + * Not being a 200 does not mean a failure. eg 201 is returned for created. TODO -> Should we + * return. + */ + LOG.error(AaiUiMsgs.ES_OPERATION_RETURN_CODE, + String.valueOf(esGetTxn.getOperationResult().getResultCode())); + return; + } + + try { + String jsonPayload = null; + if (wasEntryDiscovered) { + try { + ArrayList sourceObject = new ArrayList(); + NodeUtils.extractObjectsByKey( + NodeUtils.convertJsonStrToJsonNode(esGetTxn.getOperationResult().getResult()), + "_source", sourceObject); + + if (!sourceObject.isEmpty()) { + String responseSource = NodeUtils.convertObjectToJson(sourceObject.get(0), false); + MergableEntity me = mapper.readValue(responseSource, MergableEntity.class); + ObjectReader updater = mapper.readerForUpdating(me); + MergableEntity merged = updater.readValue(se.getIndexDocumentJson()); + jsonPayload = mapper.writeValueAsString(merged); + } + } catch (IOException exc) { + String message = + "Error extracting source value from response, aborting searchable entity sync of " + + se.getEntityPrimaryKeyValue() + ". Error - " + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ERROR_EXTRACTING_FROM_RESPONSE, message); + return; + } + } else { + jsonPayload = se.getIndexDocumentJson(); + } + + if (wasEntryDiscovered) { + if (versionNumber != null && jsonPayload != null) { + + String requestPayload = esDataProvider.buildBulkImportOperationRequest(getIndexName(), + ElasticSearchConfig.getConfig().getType(), se.getId(), versionNumber, jsonPayload); + + NetworkTransaction transactionTracker = new NetworkTransaction(); + transactionTracker.setEntityType(esGetTxn.getEntityType()); + transactionTracker.setDescriptor(esGetTxn.getDescriptor()); + transactionTracker.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + supplyAsync(new PerformElasticSearchUpdate(ElasticSearchConfig.getConfig().getBulkUrl(), + requestPayload, esDataProvider, transactionTracker), esPutExecutor) + .whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + String message = "Searchable entity sync UPDATE PUT error - " + + error.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message); + } else { + updateElasticSearchCounters(result); + processStoreDocumentResult(result, esGetTxn, se); + } + }); + } + + } else { + if (link != null && jsonPayload != null) { + + NetworkTransaction updateElasticTxn = new NetworkTransaction(); + updateElasticTxn.setLink(link); + updateElasticTxn.setEntityType(esGetTxn.getEntityType()); + updateElasticTxn.setDescriptor(esGetTxn.getDescriptor()); + updateElasticTxn.setOperationType(HttpMethod.PUT); + + esWorkOnHand.incrementAndGet(); + supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, esDataProvider), + esPutExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + String message = + "Searchable entity sync UPDATE PUT error - " + error.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message); + } else { + updateElasticSearchCounters(result); + processStoreDocumentResult(result, esGetTxn, se); + } + }); + } + } + } catch (Exception exc) { + String message = "Exception caught during searchable entity sync PUT operation. Message - " + + exc.getLocalizedMessage(); + LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message); + } + } + + /** + * Populate searchable entity document. + * + * @param doc the doc + * @param result the result + * @param resultDescriptor the result descriptor + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + protected void populateSearchableEntityDocument(SearchableEntity doc, String result, + OxmEntityDescriptor resultDescriptor) throws JsonProcessingException, IOException { + + doc.setEntityType(resultDescriptor.getEntityName()); + + JsonNode entityNode = mapper.readTree(result); + + List primaryKeyValues = new ArrayList(); + String pkeyValue = null; + + for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) { + pkeyValue = NodeUtils.getNodeFieldAsText(entityNode, keyName); + if (pkeyValue != null) { + primaryKeyValues.add(pkeyValue); + } else { + String message = "populateSearchableEntityDocument(), pKeyValue is null for entityType = " + + resultDescriptor.getEntityName(); + LOG.warn(AaiUiMsgs.WARN_GENERIC, message); + } + } + + final String primaryCompositeKeyValue = NodeUtils.concatArray(primaryKeyValues, "/"); + doc.setEntityPrimaryKeyValue(primaryCompositeKeyValue); + + final List searchTagFields = resultDescriptor.getSearchableAttributes(); + + /* + * Based on configuration, use the configured field names for this entity-Type to build a + * multi-value collection of search tags for elastic search entity search criteria. + */ + for (String searchTagField : searchTagFields) { + String searchTagValue = NodeUtils.getNodeFieldAsText(entityNode, searchTagField); + if (searchTagValue != null && !searchTagValue.isEmpty()) { + doc.addSearchTagWithKey(searchTagValue, searchTagField); + } + } + } + + /** + * Fetch document for upsert. + * + * @param txn the txn + */ + private void fetchDocumentForUpsert(NetworkTransaction txn) { + if (!txn.getOperationResult().wasSuccessful()) { + String message = "Self link failure. Result - " + txn.getOperationResult().getResult(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + return; + } + + try { + if (txn.getDescriptor().hasSearchableAttributes()) { + + final String jsonResult = txn.getOperationResult().getResult(); + if (jsonResult != null && jsonResult.length() > 0) { + + SearchableEntity se = new SearchableEntity(oxmModelLoader); + se.setLink( txn.getLink() ); + populateSearchableEntityDocument(se, jsonResult, txn.getDescriptor()); + se.deriveFields(); + + String link = null; + try { + link = getElasticFullUrl("/" + se.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_QUERY, exc.getLocalizedMessage()); + } + + if (link != null) { + NetworkTransaction n2 = new NetworkTransaction(); + n2.setLink(link); + n2.setEntityType(txn.getEntityType()); + n2.setDescriptor(txn.getDescriptor()); + n2.setOperationType(HttpMethod.GET); + + esWorkOnHand.incrementAndGet(); + + supplyAsync(new PerformElasticSearchRetrieval(n2, esDataProvider), esExecutor) + .whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + performDocumentUpsert(result, se); + } + }); + } + } + + } + } catch (JsonProcessingException exc) { + // TODO -> LOG, waht should be logged here? + } catch (IOException exc) { + // TODO -> LOG, waht should be logged here? + } + } + + /** + * Process store document result. + * + * @param esPutResult the es put result + * @param esGetResult the es get result + * @param se the se + */ + private void processStoreDocumentResult(NetworkTransaction esPutResult, + NetworkTransaction esGetResult, SearchableEntity se) { + + OperationResult or = esPutResult.getOperationResult(); + + if (!or.wasSuccessful()) { + if (or.getResultCode() == VERSION_CONFLICT_EXCEPTION_CODE) { + + if (shouldAllowRetry(se.getId())) { + esWorkOnHand.incrementAndGet(); + + RetrySearchableEntitySyncContainer rsc = + new RetrySearchableEntitySyncContainer(esGetResult, se); + retryQueue.push(rsc); + + String message = "Store document failed during searchable entity synchronization" + + " due to version conflict. Entity will be re-synced."; + LOG.warn(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message); + } + } else { + String message = + "Store document failed during searchable entity synchronization with result code " + + or.getResultCode() + " and result message " + or.getResult(); + LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message); + } + } + } + + /** + * Perform retry sync. + */ + private void performRetrySync() { + while (retryQueue.peek() != null) { + + RetrySearchableEntitySyncContainer rsc = retryQueue.poll(); + if (rsc != null) { + + SearchableEntity se = rsc.getSearchableEntity(); + NetworkTransaction txn = rsc.getNetworkTransaction(); + + String link = null; + try { + /* + * In this retry flow the se object has already derived its fields + */ + link = getElasticFullUrl("/" + se.getId(), getIndexName()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ES_FAILED_TO_CONSTRUCT_URI, exc.getLocalizedMessage()); + } + + if (link != null) { + NetworkTransaction retryTransaction = new NetworkTransaction(); + retryTransaction.setLink(link); + retryTransaction.setEntityType(txn.getEntityType()); + retryTransaction.setDescriptor(txn.getDescriptor()); + retryTransaction.setOperationType(HttpMethod.GET); + + /* + * IMPORTANT - DO NOT incrementAndGet the esWorkOnHand as this is a retry flow! We already + * called incrementAndGet when queuing the failed PUT! + */ + + supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, esDataProvider), + esExecutor).whenComplete((result, error) -> { + + esWorkOnHand.decrementAndGet(); + + if (error != null) { + LOG.error(AaiUiMsgs.ES_RETRIEVAL_FAILED_RESYNC, error.getLocalizedMessage()); + } else { + updateElasticSearchCounters(result); + performDocumentUpsert(result, se); + } + }); + } + + } + } + } + + /** + * Should allow retry. + * + * @param id the id + * @return true, if successful + */ + private boolean shouldAllowRetry(String id) { + boolean isRetryAllowed = true; + if (retryLimitTracker.get(id) != null) { + Integer currentCount = retryLimitTracker.get(id); + if (currentCount.intValue() >= RETRY_COUNT_PER_ENTITY_LIMIT.intValue()) { + isRetryAllowed = false; + String message = "Searchable entity re-sync limit reached for " + id + + ", re-sync will no longer be attempted for this entity"; + LOG.error(AaiUiMsgs.ES_SEARCHABLE_ENTITY_SYNC_ERROR, message); + } else { + Integer newCount = new Integer(currentCount.intValue() + 1); + retryLimitTracker.put(id, newCount); + } + } else { + Integer firstRetryCount = new Integer(1); + retryLimitTracker.put(id, firstRetryCount); + } + + return isRetryAllowed; + } + + @Override + public SynchronizerState getState() { + if (!isSyncDone()) { + return SynchronizerState.PERFORMING_SYNCHRONIZATION; + } + + return SynchronizerState.IDLE; + + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + */ + @Override + public String getStatReport(boolean showFinalReport) { + return this.getStatReport(System.currentTimeMillis() - syncStartedTimeStampInMs, + showFinalReport); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() + */ + @Override + public void shutdown() { + this.shutdownExecutors(); + } + + @Override + protected boolean isSyncDone() { + int totalWorkOnHand = aaiWorkOnHand.get() + esWorkOnHand.get(); + + if (totalWorkOnHand > 0 || !allWorkEnumerated) { + return false; + } + + return true; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/SyncController.java b/src/main/java/org/openecomp/sparky/synchronizer/SyncController.java new file mode 100644 index 0000000..85cbeb5 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/SyncController.java @@ -0,0 +1,480 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.util.NodeUtils; + +/** + * The Class SyncController. + * + * @author davea. + */ +public class SyncController { + private static final Logger LOG = LoggerFactory.getInstance().getLogger(SyncController.class); + + /** + * The Enum InternalState. + */ + private enum InternalState { + IDLE, PRE_SYNC, SYNC_OPERATION, SELECTIVE_DELETE, ABORTING_SYNC, REPAIRING_INDEX, POST_SYNC, + TEST_INDEX_INTEGRITY, GENERATE_FINAL_REPORT + } + + /** + * The Enum SyncActions. + */ + public enum SyncActions { + SYNCHRONIZE, REPAIR_INDEX, INDEX_INTEGRITY_VALIDATION_COMPLETE, PRE_SYNC_COMPLETE, + SYNC_COMPLETE, SYNC_ABORTED, SYNC_FAILURE, POST_SYNC_COMPLETE, PURGE_COMPLETE, REPORT_COMPLETE + } + + private Collection registeredSynchronizers; + private Collection registeredIndexValidators; + private Collection registeredIndexCleaners; + private InternalState currentInternalState; + private ExecutorService syncControllerExecutor; + private ExecutorService statReporterExecutor; + private final String controllerName; + + /** + * Instantiates a new sync controller. + * + * @param name the name + * @throws Exception the exception + */ + public SyncController(String name) throws Exception { + + this.controllerName = name; + /* + * Does LHS result in a non-duplicated object collection?? What happens if you double-add an + * object? + */ + + registeredSynchronizers = new LinkedHashSet(); + registeredIndexValidators = new LinkedHashSet(); + registeredIndexCleaners = new LinkedHashSet(); + + this.syncControllerExecutor = NodeUtils.createNamedExecutor("SyncController", 5, LOG); + this.statReporterExecutor = NodeUtils.createNamedExecutor("StatReporter", 1, LOG); + + this.currentInternalState = InternalState.IDLE; + } + + /** + * Change internal state. + * + * @param newState the new state + * @param causedByAction the caused by action + */ + private void changeInternalState(InternalState newState, SyncActions causedByAction) { + LOG.info(AaiUiMsgs.SYNC_INTERNAL_STATE_CHANGED, controllerName, + currentInternalState.toString(), newState.toString(), causedByAction.toString()); + + this.currentInternalState = newState; + + performStateAction(); + } + + public String getControllerName() { + return controllerName; + } + + /** + * Perform action. + * + * @param requestedAction the requested action + */ + public void performAction(SyncActions requestedAction) { + + if (currentInternalState == InternalState.IDLE) { + + try { + switch (requestedAction) { + case SYNCHRONIZE: + changeInternalState(InternalState.TEST_INDEX_INTEGRITY, requestedAction); + break; + + default: + break; + } + + } catch (Exception exc) { + String message = "An error occurred while performing action = " + requestedAction + + ". Error = " + exc.getMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } else { + LOG.error(AaiUiMsgs.SYNC_NOT_VALID_STATE_DURING_REQUEST, currentInternalState.toString()); + } + } + + /** + * Perform state action. + */ + private void performStateAction() { + + try { + switch (currentInternalState) { + + case TEST_INDEX_INTEGRITY: + performIndexIntegrityValidation(); + break; + + case PRE_SYNC: + performPreSyncCleanupCollection(); + break; + + case SYNC_OPERATION: + performSynchronization(); + break; + + case POST_SYNC: + performIndexSyncPostCollection(); + changeInternalState(InternalState.SELECTIVE_DELETE, SyncActions.POST_SYNC_COMPLETE); + break; + + case SELECTIVE_DELETE: + performIndexCleanup(); + changeInternalState(InternalState.GENERATE_FINAL_REPORT, SyncActions.PURGE_COMPLETE); + break; + + case GENERATE_FINAL_REPORT: + + dumpStatReport(true); + clearCaches(); + changeInternalState(InternalState.IDLE, SyncActions.REPORT_COMPLETE); + break; + + case ABORTING_SYNC: + performSyncAbort(); + break; + + default: + break; + } + } catch (Exception exc) { + String message = "Caught an error which performing action. Error = " + exc.getMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } + + /** + * Register entity synchronizer. + * + * @param entitySynchronizer the entity synchronizer + */ + public void registerEntitySynchronizer(IndexSynchronizer entitySynchronizer) { + + String indexName = entitySynchronizer.getIndexName(); + + if (indexName != null) { + registeredSynchronizers.add(entitySynchronizer); + } else { + String message = "Failed to register entity synchronizer because index name is null"; + LOG.error(AaiUiMsgs.FAILED_TO_REGISTER_DUE_TO_NULL, message); + } + + } + + /** + * Register index validator. + * + * @param indexValidator the index validator + */ + public void registerIndexValidator(IndexValidator indexValidator) { + + String indexName = indexValidator.getIndexName(); + + if (indexName != null) { + registeredIndexValidators.add(indexValidator); + } else { + String message = "Failed to register index validator because index name is null"; + LOG.error(AaiUiMsgs.FAILED_TO_REGISTER_DUE_TO_NULL, message); + } + + } + + /** + * Register index cleaner. + * + * @param indexCleaner the index cleaner + */ + public void registerIndexCleaner(IndexCleaner indexCleaner) { + + String indexName = indexCleaner.getIndexName(); + + if (indexName != null) { + registeredIndexCleaners.add(indexCleaner); + } else { + String message = "Failed to register index cleaner because index name is null"; + LOG.error(AaiUiMsgs.FAILED_TO_REGISTER_DUE_TO_NULL, message); + } + } + + /* + * State machine should drive our flow dosync just dispatches an action and the state machine + * determines what is in play and what is next + */ + + /** + * Dump stat report. + * + * @param showFinalReport the show final report + */ + private void dumpStatReport(boolean showFinalReport) { + + for (IndexSynchronizer synchronizer : registeredSynchronizers) { + + String statReport = synchronizer.getStatReport(showFinalReport); + + if (statReport != null) { + LOG.info(AaiUiMsgs.INFO_GENERIC, statReport); + } + } + } + + /** + * Clear caches. + */ + private void clearCaches() { + + /* + * Any entity caches that were built as part of the sync operation should be cleared to save + * memory. The original intent of the caching was to provide a short-lived cache to satisfy + * entity requests from multiple synchronizers yet minimizing interactions with the AAI. + */ + + for (IndexSynchronizer synchronizer : registeredSynchronizers) { + synchronizer.clearCache(); + } + } + + /** + * Perform pre sync cleanup collection. + */ + private void performPreSyncCleanupCollection() { + + /* + * ask the index cleaners to collect the their pre-sync object id collections + */ + + for (IndexCleaner cleaner : registeredIndexCleaners) { + cleaner.populatePreOperationCollection(); + } + + changeInternalState(InternalState.SYNC_OPERATION, SyncActions.PRE_SYNC_COMPLETE); + + } + + /** + * Perform index sync post collection. + */ + private void performIndexSyncPostCollection() { + + /* + * ask the entity purgers to collect the their pre-sync object id collections + */ + + for (IndexCleaner cleaner : registeredIndexCleaners) { + cleaner.populatePostOperationCollection(); + } + + } + + /** + * Perform index cleanup. + */ + private void performIndexCleanup() { + + /* + * ask the entity purgers to collect the their pre-sync object id collections + */ + + for (IndexCleaner cleaner : registeredIndexCleaners) { + cleaner.performCleanup(); + } + + } + + /** + * Perform sync abort. + */ + private void performSyncAbort() { + changeInternalState(InternalState.IDLE, SyncActions.SYNC_ABORTED); + } + + /** + * Perform index integrity validation. + */ + private void performIndexIntegrityValidation() { + + /* + * loop through registered index validators and test and fix, if needed + */ + + for (IndexValidator validator : registeredIndexValidators) { + try { + if (!validator.exists()) { + validator.createOrRepair(); + } + } catch (Exception exc) { + String message = "Index validator caused an error = " + exc.getMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } + + changeInternalState(InternalState.PRE_SYNC, SyncActions.INDEX_INTEGRITY_VALIDATION_COMPLETE); + + } + + /** + * Shutdown. + */ + public void shutdown() { + + this.syncControllerExecutor.shutdown(); + for (IndexSynchronizer synchronizer : registeredSynchronizers) { + + try { + synchronizer.shutdown(); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Synchronizer shutdown caused an error = " + exc.getMessage()); + } + + } + this.statReporterExecutor.shutdown(); + } + + /* + * Need some kind of task running that responds to a transient boolean to kill it or we just stop + * the executor that it is in? + */ + + + + /** + * Perform synchronization. + */ + private void performSynchronization() { + + /* + * Get all the synchronizers running in parallel + */ + + for (IndexSynchronizer synchronizer : registeredSynchronizers) { + supplyAsync(new Supplier() { + + @Override + public Void get() { + + synchronizer.doSync(); + return null; + } + + }, this.syncControllerExecutor).whenComplete((result, error) -> { + + /* + * We don't bother checking the result, because it will always be null as the doSync() is + * non-blocking. + */ + + if (error != null) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "doSync operation failed with an error = " + error.getMessage()); + } + }); + } + + boolean allDone = false; + long nextReportTimeStampInMs = System.currentTimeMillis() + 30000L; + + while (!allDone) { + + // allDone = false; + + int totalFinished = 0; + + for (IndexSynchronizer synchronizer : registeredSynchronizers) { + if (System.currentTimeMillis() > nextReportTimeStampInMs) { + + nextReportTimeStampInMs = System.currentTimeMillis() + 30000L; + + String statReport = synchronizer.getStatReport(false); + + if (statReport != null) { + LOG.info(AaiUiMsgs.INFO_GENERIC, statReport); + } + } + + if (synchronizer.getState() == SynchronizerState.IDLE) { + totalFinished++; + } + } + + allDone = (totalFinished == registeredSynchronizers.size()); + + try { + Thread.sleep(250); + } catch (InterruptedException exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "An error occurred while waiting for sync to complete. Error = " + exc.getMessage()); + } + + } + + changeInternalState(InternalState.POST_SYNC, SyncActions.SYNC_COMPLETE); + + } + + public SynchronizerState getState() { + + switch (currentInternalState) { + + case IDLE: { + return SynchronizerState.IDLE; + } + + default: { + return SynchronizerState.PERFORMING_SYNCHRONIZATION; + + } + } + + } + +} \ No newline at end of file diff --git a/src/main/java/org/openecomp/sparky/synchronizer/SyncHelper.java b/src/main/java/org/openecomp/sparky/synchronizer/SyncHelper.java new file mode 100644 index 0000000..7c37859 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/SyncHelper.java @@ -0,0 +1,705 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.aai.ActiveInventoryAdapter; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryRestConfig; +import org.openecomp.sparky.dal.cache.EntityCache; +import org.openecomp.sparky.dal.cache.InMemoryEntityCache; +import org.openecomp.sparky.dal.cache.PersistentEntityCache; +import org.openecomp.sparky.dal.elasticsearch.ElasticSearchAdapter; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.RestClientBuilder; +import org.openecomp.sparky.dal.rest.RestfulDataAccessor; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.SyncController.SyncActions; +import org.openecomp.sparky.synchronizer.config.SynchronizerConfiguration; +import org.openecomp.sparky.synchronizer.config.SynchronizerConstants; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.openecomp.sparky.util.ErrorUtil; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; +import org.slf4j.MDC; + +/** + * The Class SyncHelper. + * + * @author davea. + */ +public class SyncHelper { + + private final Logger LOG = LoggerFactory.getInstance().getLogger(SyncHelper.class); + private SyncController syncController = null; + private SyncController entityCounterHistorySummarizer = null; + + private ScheduledExecutorService oneShotExecutor = Executors.newSingleThreadScheduledExecutor(); + private ScheduledExecutorService periodicExecutor = null; + private ScheduledExecutorService historicalExecutor = + Executors.newSingleThreadScheduledExecutor(); + + private SynchronizerConfiguration syncConfig; + private ElasticSearchConfig esConfig; + private OxmModelLoader oxmModelLoader; + + private Boolean initialSyncRunning = false; + private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + private AtomicLong timeNextSync = new AtomicLong(); + Map contextMap; + + /** + * The Class SyncTask. + */ + private class SyncTask implements Runnable { + + private boolean isInitialSync; + + /** + * Instantiates a new sync task. + * + * @param initialSync the initial sync + */ + public SyncTask(boolean initialSync) { + this.isInitialSync = initialSync; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + long opStartTime = System.currentTimeMillis(); + MDC.setContextMap(contextMap); + + LOG.info(AaiUiMsgs.SEARCH_ENGINE_SYNC_STARTED, sdf.format(opStartTime) + .replaceAll(SynchronizerConstants.TIME_STD, SynchronizerConstants.TIME_CONFIG_STD)); + + try { + + if (syncController == null) { + LOG.error(AaiUiMsgs.SYNC_SKIPPED_SYNCCONTROLLER_NOT_INITIALIZED); + return; + } + + int taskFrequencyInDays = SynchronizerConfiguration.getConfig().getSyncTaskFrequencyInDay(); + + /* + * Do nothing if the initial start-up sync hasn't finished yet, but the regular sync + * scheduler fired up a regular sync. + */ + if (!initialSyncRunning) { + if (isInitialSync) { + initialSyncRunning = true; + } else { + // update 'timeNextSync' for periodic sync + timeNextSync.getAndAdd(taskFrequencyInDays * SynchronizerConstants.MILLISEC_IN_A_DAY); + + } + + LOG.info(AaiUiMsgs.INFO_GENERIC, "SyncTask, starting syncrhonization"); + + syncController.performAction(SyncActions.SYNCHRONIZE); + + while (syncController.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + Thread.sleep(1000); + } + + } else { + LOG.info(AaiUiMsgs.SKIP_PERIODIC_SYNC_AS_SYNC_DIDNT_FINISH, sdf.format(opStartTime) + .replaceAll(SynchronizerConstants.TIME_STD, SynchronizerConstants.TIME_CONFIG_STD)); + + return; + } + + long opEndTime = System.currentTimeMillis(); + + if (isInitialSync) { + /* + * Handle corner case when start-up sync operation overlapped with a scheduled + * sync-start-time. Note that the scheduled sync does nothing if 'initialSyncRunning' is + * TRUE. So the actual next-sync is one more sync-cycle away + */ + long knownNextSyncTime = timeNextSync.get(); + if (knownNextSyncTime != SynchronizerConstants.DELAY_NO_PERIODIC_SYNC_IN_MS + && opEndTime > knownNextSyncTime) { + timeNextSync.compareAndSet(knownNextSyncTime, + knownNextSyncTime + taskFrequencyInDays * SynchronizerConstants.MILLISEC_IN_A_DAY); + initialSyncRunning = false; + } + } + + String durationMessage = + String.format(syncController.getControllerName() + " synchronization took '%d' ms.", + (opEndTime - opStartTime)); + + LOG.info(AaiUiMsgs.SYNC_DURATION, durationMessage); + + // Provide log about the time for next synchronization + if (syncConfig.isConfigOkForPeriodicSync() + && timeNextSync.get() != SynchronizerConstants.DELAY_NO_PERIODIC_SYNC_IN_MS) { + TimeZone tz = TimeZone.getTimeZone(syncConfig.getSyncTaskStartTimeTimeZone()); + sdf.setTimeZone(tz); + if (opEndTime - opStartTime > taskFrequencyInDays + * SynchronizerConstants.MILLISEC_IN_A_DAY) { + String durationWasLongerMessage = String.format( + syncController.getControllerName() + + " synchronization took '%d' ms which is larger than" + + " synchronization interval of '%d' ms.", + (opEndTime - opStartTime), + taskFrequencyInDays * SynchronizerConstants.MILLISEC_IN_A_DAY); + + LOG.info(AaiUiMsgs.SYNC_DURATION, durationWasLongerMessage); + } + + LOG.info(AaiUiMsgs.SYNC_TO_BEGIN, syncController.getControllerName(), + sdf.format(timeNextSync).replaceAll(SynchronizerConstants.TIME_STD, + SynchronizerConstants.TIME_CONFIG_STD)); + } + + } catch (Exception exc) { + String message = "Caught an exception while attempt to synchronize elastic search " + + "with an error cause = " + ErrorUtil.extractStackTraceElements(5, exc); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + + } + + } + + /** + * The Class HistoricalEntityCountSummaryTask. + */ + private class HistoricalEntityCountSummaryTask implements Runnable { + + /** + * Instantiates a new historical entity count summary task. + */ + public HistoricalEntityCountSummaryTask() {} + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + + long opStartTime = System.currentTimeMillis(); + MDC.setContextMap(contextMap); + LOG.info(AaiUiMsgs.HISTORICAL_ENTITY_COUNT_SUMMARIZER_STARTING, sdf.format(opStartTime) + .replaceAll(SynchronizerConstants.TIME_STD, SynchronizerConstants.TIME_CONFIG_STD)); + + try { + if (entityCounterHistorySummarizer == null) { + LOG.error(AaiUiMsgs.HISTORICAL_ENTITY_COUNT_SUMMARIZER_NOT_STARTED); + return; + } + + LOG.info(AaiUiMsgs.INFO_GENERIC, + "EntityCounterHistorySummarizer, starting syncrhonization"); + + entityCounterHistorySummarizer.performAction(SyncActions.SYNCHRONIZE); + + while (entityCounterHistorySummarizer + .getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + Thread.sleep(1000); + } + + long opEndTime = System.currentTimeMillis(); + + LOG.info(AaiUiMsgs.HISTORICAL_SYNC_DURATION, + entityCounterHistorySummarizer.getControllerName(), + String.valueOf(opEndTime - opStartTime)); + + long taskFrequencyInMs = + syncConfig.getHistoricalEntitySummarizedFrequencyInMinutes() * 60 * 1000; + + if (syncConfig.isHistoricalEntitySummarizerEnabled()) { + String time = sdf.format(System.currentTimeMillis() + taskFrequencyInMs) + .replaceAll(SynchronizerConstants.TIME_STD, SynchronizerConstants.TIME_CONFIG_STD); + + LOG.info(AaiUiMsgs.HISTORICAL_SYNC_TO_BEGIN, time); + } + + + } catch (Exception exc) { + String message = "Caught an exception while attempting to populate entity country " + + "history elasticsearch table with an error cause = " + + ErrorUtil.extractStackTraceElements(5, exc); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + + } + + } + + /** + * Gets the first sync time. + * + * @param calendar the calendar + * @param timeNow the time now + * @param taskFreqInDay the task freq in day + * @return the first sync time + */ + public long getFirstSyncTime(Calendar calendar, long timeNow, int taskFreqInDay) { + if (taskFreqInDay == SynchronizerConstants.DELAY_NO_PERIODIC_SYNC_IN_MS) { + return SynchronizerConstants.DELAY_NO_PERIODIC_SYNC_IN_MS; + } else if (timeNow > calendar.getTimeInMillis()) { + calendar.add(Calendar.DAY_OF_MONTH, taskFreqInDay); + } + return calendar.getTimeInMillis(); + } + + /** + * Boot strap and configure the moving pieces of the Sync Controller. + */ + + private void initializeSyncController() { + + try { + + /* + * TODO: it would be nice to have XML IoC / dependency injection kind of thing for these + * pieces maybe Spring? + */ + + /* + * Sync Controller itself + */ + + syncController = new SyncController("entitySyncController"); + + /* + * Create common elements + */ + + ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + ActiveInventoryRestConfig aaiRestConfig = + ActiveInventoryConfig.getConfig().getAaiRestConfig(); + + + EntityCache cache = null; + + if (aaiRestConfig.isCacheEnabled()) { + cache = new PersistentEntityCache(aaiRestConfig.getStorageFolderOverride(), + aaiRestConfig.getNumCacheWorkers()); + } else { + cache = new InMemoryEntityCache(); + } + + RestClientBuilder clientBuilder = new RestClientBuilder(); + + aaiAdapter.setCacheEnabled(true); + aaiAdapter.setEntityCache(cache); + + clientBuilder.setUseHttps(false); + + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(clientBuilder); + + ElasticSearchConfig esConfig = ElasticSearchConfig.getConfig(); + ElasticSearchAdapter esAdapter = new ElasticSearchAdapter(nonCachingRestProvider, esConfig); + + /* + * Register Index Validators + */ + + IndexIntegrityValidator entitySearchIndexValidator = + new IndexIntegrityValidator(nonCachingRestProvider, esConfig.getIndexName(), + esConfig.getType(), esConfig.getIpAddress(), esConfig.getHttpPort(), + esConfig.buildElasticSearchTableConfig()); + + syncController.registerIndexValidator(entitySearchIndexValidator); + + // TODO: Insert IndexValidator for TopographicalEntityIndex + // we should have one, but one isn't 100% required as none of the fields are analyzed + + /* + * Register Synchronizers + */ + + SearchableEntitySynchronizer ses = new SearchableEntitySynchronizer(esConfig.getIndexName()); + ses.setAaiDataProvider(aaiAdapter); + ses.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(ses); + + CrossEntityReferenceSynchronizer cers = new CrossEntityReferenceSynchronizer( + esConfig.getIndexName(), ActiveInventoryConfig.getConfig()); + cers.setAaiDataProvider(aaiAdapter); + cers.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(cers); + + GeoSynchronizer geo = new GeoSynchronizer(esConfig.getTopographicalSearchIndex()); + geo.setAaiDataProvider(aaiAdapter); + geo.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(geo); + + if (syncConfig.isAutosuggestSynchronizationEnabled()) { + initAutoSuggestionSynchronizer(esConfig, aaiAdapter, esAdapter, nonCachingRestProvider); + initAggregationSynchronizer(esConfig, aaiAdapter, esAdapter, nonCachingRestProvider); + } + + /* + * Register Cleaners + */ + + IndexCleaner searchableIndexCleaner = new ElasticSearchIndexCleaner(nonCachingRestProvider, + esConfig.getIndexName(), esConfig.getType(), esConfig.getIpAddress(), + esConfig.getHttpPort(), syncConfig.getScrollContextTimeToLiveInMinutes(), + syncConfig.getNumScrollContextItemsToRetrievePerRequest()); + + syncController.registerIndexCleaner(searchableIndexCleaner); + + IndexCleaner geoIndexCleaner = new ElasticSearchIndexCleaner(nonCachingRestProvider, + esConfig.getTopographicalSearchIndex(), esConfig.getType(), esConfig.getIpAddress(), + esConfig.getHttpPort(), syncConfig.getScrollContextTimeToLiveInMinutes(), + syncConfig.getNumScrollContextItemsToRetrievePerRequest()); + + syncController.registerIndexCleaner(geoIndexCleaner); + + + } catch (Exception exc) { + String message = "Error: failed to sync with message = " + exc.getMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + + } + + /** + * Inits the entity counter history summarizer. + */ + private void initEntityCounterHistorySummarizer() { + + LOG.info(AaiUiMsgs.INFO_GENERIC, "initEntityCounterHistorySummarizer"); + + try { + entityCounterHistorySummarizer = new SyncController("entityCounterHistorySummarizer"); + + ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + aaiAdapter.setCacheEnabled(false); + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(false); + + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(clientBuilder); + ElasticSearchConfig esConfig = ElasticSearchConfig.getConfig(); + ElasticSearchAdapter esAdapter = new ElasticSearchAdapter(nonCachingRestProvider, esConfig); + + IndexIntegrityValidator entityCounterHistoryValidator = + new IndexIntegrityValidator(nonCachingRestProvider, esConfig.getEntityCountHistoryIndex(), + esConfig.getType(), esConfig.getIpAddress(), esConfig.getHttpPort(), + esConfig.buildElasticSearchEntityCountHistoryTableConfig()); + + entityCounterHistorySummarizer.registerIndexValidator(entityCounterHistoryValidator); + + HistoricalEntitySummarizer historicalSummarizer = + new HistoricalEntitySummarizer(esConfig.getEntityCountHistoryIndex()); + historicalSummarizer.setAaiDataProvider(aaiAdapter); + historicalSummarizer.setEsDataProvider(esAdapter); + //entityCounterHistorySummarizer.registerEntitySynchronizer(historicalSummarizer); + + } catch (Exception exc) { + String message = "Error: failed to sync with message = " + exc.getMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } + + private List getAutosuggestableEntitiesFromOXM() { + Map map = oxmModelLoader.getSuggestionSearchEntityDescriptors(); + List suggestableEntities = new ArrayList(); + + for (String entity: map.keySet()){ + suggestableEntities.add(entity); + } + return suggestableEntities; + } + + /** + * Initialize the AutosuggestionSynchronizer and + * AggregationSuggestionSynchronizer + * + * @param esConfig + * @param aaiAdapter + * @param esAdapter + * @param nonCachingRestProvider + */ + private void initAutoSuggestionSynchronizer(ElasticSearchConfig esConfig, + ActiveInventoryAdapter aaiAdapter, ElasticSearchAdapter esAdapter, + RestfulDataAccessor nonCachingRestProvider) { + LOG.info(AaiUiMsgs.INFO_GENERIC, "initAutoSuggestionSynchronizer"); + + // Initialize for entityautosuggestindex + try { + IndexIntegrityValidator autoSuggestionIndexValidator = + new IndexIntegrityValidator(nonCachingRestProvider, esConfig.getAutosuggestIndexname(), + esConfig.getType(), esConfig.getIpAddress(), esConfig.getHttpPort(), + esConfig.buildAutosuggestionTableConfig()); + + syncController.registerIndexValidator(autoSuggestionIndexValidator); + + AutosuggestionSynchronizer suggestionSynchronizer = + new AutosuggestionSynchronizer(esConfig.getAutosuggestIndexname()); + suggestionSynchronizer.setAaiDataProvider(aaiAdapter); + suggestionSynchronizer.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(suggestionSynchronizer); + + AggregationSuggestionSynchronizer aggregationSuggestionSynchronizer = + new AggregationSuggestionSynchronizer(esConfig.getAutosuggestIndexname()); + aggregationSuggestionSynchronizer.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(aggregationSuggestionSynchronizer); + + IndexCleaner autosuggestIndexCleaner = new ElasticSearchIndexCleaner(nonCachingRestProvider, + esConfig.getAutosuggestIndexname(), esConfig.getType(), esConfig.getIpAddress(), + esConfig.getHttpPort(), syncConfig.getScrollContextTimeToLiveInMinutes(), + syncConfig.getNumScrollContextItemsToRetrievePerRequest()); + + syncController.registerIndexCleaner(autosuggestIndexCleaner); + } catch (Exception exc) { + String message = "Error: failed to sync with message = " + exc.getMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } + + /** + * Initialize the AggregationSynchronizer + * + * @param esConfig + * @param aaiAdapter + * @param esAdapter + * @param nonCachingRestProvider + */ + private void initAggregationSynchronizer(ElasticSearchConfig esConfig, + ActiveInventoryAdapter aaiAdapter, ElasticSearchAdapter esAdapter, + RestfulDataAccessor nonCachingRestProvider) { + LOG.info(AaiUiMsgs.INFO_GENERIC, "initAggregationSynchronizer"); + + List aggregationEntities = getAutosuggestableEntitiesFromOXM(); + + // For each index: create an IndexValidator, a Synchronizer, and an IndexCleaner + for (String entity : aggregationEntities) { + try { + String indexName = TierSupportUiConstants.getAggregationIndexName(entity); + + IndexIntegrityValidator aggregationIndexValidator = new IndexIntegrityValidator( + nonCachingRestProvider, indexName, esConfig.getType(), esConfig.getIpAddress(), + esConfig.getHttpPort(), esConfig.buildAggregationTableConfig()); + + syncController.registerIndexValidator(aggregationIndexValidator); + + /* + * TODO: This per-entity-synchronizer approach will eventually result in AAI / ES overload + * because of the existing dedicated thread pools for ES + AAI operations within the + * synchronizer. If we had 50 types to sync then the thread pools within each Synchronizer + * would cause some heartburn as there would be hundreds of threads trying to talk to AAI. + * Given that we our running out of time, let's make sure we can get it functional and then + * we'll re-visit. + */ + AggregationSynchronizer aggSynchronizer = new AggregationSynchronizer(entity, indexName); + aggSynchronizer.setAaiDataProvider(aaiAdapter); + aggSynchronizer.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(aggSynchronizer); + + IndexCleaner entityDataIndexCleaner = new ElasticSearchIndexCleaner(nonCachingRestProvider, + indexName, esConfig.getType(), esConfig.getIpAddress(), esConfig.getHttpPort(), + syncConfig.getScrollContextTimeToLiveInMinutes(), + syncConfig.getNumScrollContextItemsToRetrievePerRequest()); + + syncController.registerIndexCleaner(entityDataIndexCleaner); + + } catch (Exception exc) { + String message = "Error: failed to sync with message = " + exc.getMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } + } + + /** + * Instantiates a new sync helper. + * + * @param loader the loader + */ + public SyncHelper(OxmModelLoader loader) { + try { + this.contextMap = MDC.getCopyOfContextMap(); + this.syncConfig = SynchronizerConfiguration.getConfig(); + this.esConfig = ElasticSearchConfig.getConfig(); + this.oxmModelLoader = loader; + + UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread thread, Throwable exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, thread.getName() + ": " + exc); + } + }; + + ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("SyncHelper-%d") + .setUncaughtExceptionHandler(uncaughtExceptionHandler).build(); + + periodicExecutor = Executors.newScheduledThreadPool(3, namedThreadFactory); + + /* + * We only want to initialize the synchronizer if sync has been configured to start + */ + if (syncConfig.isConfigOkForStartupSync() || syncConfig.isConfigOkForPeriodicSync()) { + initializeSyncController(); + } + + if (syncConfig.isHistoricalEntitySummarizerEnabled()) { + initEntityCounterHistorySummarizer(); + } else { + LOG.info(AaiUiMsgs.INFO_GENERIC, "history summarizer disabled"); + } + + + // schedule startup synchronization + if (syncConfig.isConfigOkForStartupSync()) { + + long taskInitialDelayInMs = syncConfig.getSyncTaskInitialDelayInMs(); + if (taskInitialDelayInMs != SynchronizerConstants.DELAY_NO_STARTUP_SYNC_IN_MS) { + oneShotExecutor.schedule(new SyncTask(true), taskInitialDelayInMs, TimeUnit.MILLISECONDS); + LOG.info(AaiUiMsgs.INFO_GENERIC, "Search Engine startup synchronization is enabled."); + } else { + LOG.info(AaiUiMsgs.INFO_GENERIC, "Search Engine startup synchronization is disabled."); + } + } + + // schedule periodic synchronization + if (syncConfig.isConfigOkForPeriodicSync()) { + + TimeZone tz = TimeZone.getTimeZone(syncConfig.getSyncTaskStartTimeTimeZone()); + Calendar calendar = Calendar.getInstance(tz); + sdf.setTimeZone(tz); + + calendar.set(Calendar.HOUR_OF_DAY, syncConfig.getSyncTaskStartTimeHr()); + calendar.set(Calendar.MINUTE, syncConfig.getSyncTaskStartTimeMin()); + calendar.set(Calendar.SECOND, syncConfig.getSyncTaskStartTimeSec()); + + long timeCurrent = calendar.getTimeInMillis(); + int taskFrequencyInDay = syncConfig.getSyncTaskFrequencyInDay(); + timeNextSync.getAndSet(getFirstSyncTime(calendar, timeCurrent, taskFrequencyInDay)); + + long delayUntilFirstRegSyncInMs = 0; + delayUntilFirstRegSyncInMs = timeNextSync.get() - timeCurrent; + + // Do all calculation in milliseconds + long taskFreqencyInMs = taskFrequencyInDay * SynchronizerConstants.MILLISEC_IN_A_DAY; + + if (taskFreqencyInMs != SynchronizerConstants.DELAY_NO_PERIODIC_SYNC_IN_MS) { + periodicExecutor.scheduleAtFixedRate(new SyncTask(false), delayUntilFirstRegSyncInMs, + taskFreqencyInMs, TimeUnit.MILLISECONDS); + LOG.info(AaiUiMsgs.INFO_GENERIC, "Search Engine periodic synchronization is enabled."); + // case: when - startup sync is misconfigured or is disabled + // - give a clue to user when is the next periodic sync + if (!syncConfig.isConfigOkForStartupSync() + || syncConfig.isConfigDisabledForInitialSync()) { + LOG.info(AaiUiMsgs.SYNC_TO_BEGIN, syncController.getControllerName(), + sdf.format(timeNextSync).replaceAll(SynchronizerConstants.TIME_STD, + SynchronizerConstants.TIME_CONFIG_STD)); + } + } else { + LOG.info(AaiUiMsgs.INFO_GENERIC, "Search Engine periodic synchronization is disabled."); + } + } + + // schedule periodic synchronization + if (syncConfig.isHistoricalEntitySummarizerEnabled()) { + scheduleHistoricalCounterSyncTask(); + } + + } catch (Exception exc) { + String message = "Caught an exception while starting up the SyncHelper. Error cause = \n" + + ErrorUtil.extractStackTraceElements(5, exc); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } + } + + /** + * Schedule historical counter sync task. + */ + private void scheduleHistoricalCounterSyncTask() { + long taskFrequencyInMs = + syncConfig.getHistoricalEntitySummarizedFrequencyInMinutes() * 60 * 1000; + historicalExecutor.scheduleWithFixedDelay(new HistoricalEntityCountSummaryTask(), 0, + taskFrequencyInMs, TimeUnit.MILLISECONDS); + LOG.info(AaiUiMsgs.INFO_GENERIC, + "Historical Entity Count Summarizer synchronization is enabled."); + } + + /** + * Shutdown. + */ + public void shutdown() { + + if (oneShotExecutor != null) { + oneShotExecutor.shutdown(); + } + + if (periodicExecutor != null) { + periodicExecutor.shutdown(); + } + + if (historicalExecutor != null) { + historicalExecutor.shutdown(); + } + + if (syncController != null) { + syncController.shutdown(); + } + + if (entityCounterHistorySummarizer != null) { + entityCounterHistorySummarizer.shutdown(); + } + + } + + public OxmModelLoader getOxmModelLoader() { + return oxmModelLoader; + } + + public void setOxmModelLoader(OxmModelLoader oxmModelLoader) { + this.oxmModelLoader = oxmModelLoader; + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/TaskProcessingStats.java b/src/main/java/org/openecomp/sparky/synchronizer/TaskProcessingStats.java new file mode 100644 index 0000000..deb83a5 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/TaskProcessingStats.java @@ -0,0 +1,136 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import org.openecomp.sparky.analytics.AbstractStatistics; +import org.openecomp.sparky.synchronizer.config.TaskProcessorConfig; + +/** + * The Class TaskProcessingStats. + */ +public class TaskProcessingStats extends AbstractStatistics { + + private static String TASK_AGE_STATS = "taskAgeStats"; + private static String TASK_RESPONSE_STATS = "taskResponseStats"; + private static String RESPONSE_SIZE_IN_BYTES = "taskResponseSizeInBytes"; + // private static String QUEUE_ITEM_LENGTH = "queueItemLength"; + private static String TPS = "transactionsPerSecond"; + + /** + * Instantiates a new task processing stats. + * + * @param config the config + */ + public TaskProcessingStats(TaskProcessorConfig config) { + + addHistogram(TASK_AGE_STATS, config.getTaskAgeHistogramLabel(), + config.getTaskAgeHistogramMaxYAxis(), config.getTaskAgeHistogramNumBins(), + config.getTaskAgeHistogramNumDecimalPoints()); + + addHistogram(TASK_RESPONSE_STATS, config.getResponseTimeHistogramLabel(), + config.getResponseTimeHistogramMaxYAxis(), config.getResponseTimeHistogramNumBins(), + config.getResponseTimeHistogramNumDecimalPoints()); + + addHistogram(RESPONSE_SIZE_IN_BYTES, config.getBytesHistogramLabel(), + config.getBytesHistogramMaxYAxis(), config.getBytesHistogramNumBins(), + config.getBytesHistogramNumDecimalPoints()); + + /* + * addHistogram(QUEUE_ITEM_LENGTH, config.getQueueLengthHistogramLabel(), + * config.getQueueLengthHistogramMaxYAxis(), config.getQueueLengthHistogramNumBins(), + * config.getQueueLengthHistogramNumDecimalPoints()); + */ + + addHistogram(TPS, config.getTpsHistogramLabel(), config.getTpsHistogramMaxYAxis(), + config.getTpsHistogramNumBins(), config.getTpsHistogramNumDecimalPoints()); + + } + + /* + * public void updateQueueItemLengthHistogram(long value) { updateHistogram(QUEUE_ITEM_LENGTH, + * value); } + */ + + /** + * Update task age stats histogram. + * + * @param value the value + */ + public void updateTaskAgeStatsHistogram(long value) { + updateHistogram(TASK_AGE_STATS, value); + } + + /** + * Update task response stats histogram. + * + * @param value the value + */ + public void updateTaskResponseStatsHistogram(long value) { + updateHistogram(TASK_RESPONSE_STATS, value); + } + + /** + * Update response size in bytes histogram. + * + * @param value the value + */ + public void updateResponseSizeInBytesHistogram(long value) { + updateHistogram(RESPONSE_SIZE_IN_BYTES, value); + } + + /** + * Update transactions per second histogram. + * + * @param value the value + */ + public void updateTransactionsPerSecondHistogram(long value) { + updateHistogram(TPS, value); + } + + /** + * Gets the statistics report. + * + * @param verboseEnabled the verbose enabled + * @param indentPadding the indent padding + * @return the statistics report + */ + public String getStatisticsReport(boolean verboseEnabled, String indentPadding) { + + StringBuilder sb = new StringBuilder(); + + sb.append("\n").append(getHistogramStats(TASK_AGE_STATS, verboseEnabled, indentPadding)); + // sb.append("\n").append(getHistogramStats(QUEUE_ITEM_LENGTH, verboseEnabled, indentPadding)); + sb.append("\n").append(getHistogramStats(TASK_RESPONSE_STATS, verboseEnabled, indentPadding)); + sb.append("\n") + .append(getHistogramStats(RESPONSE_SIZE_IN_BYTES, verboseEnabled, indentPadding)); + sb.append("\n").append(getHistogramStats(TPS, verboseEnabled, indentPadding)); + + return sb.toString(); + + } + + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/TransactionRateController.java b/src/main/java/org/openecomp/sparky/synchronizer/TransactionRateController.java new file mode 100644 index 0000000..8cc3409 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/TransactionRateController.java @@ -0,0 +1,113 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.openecomp.sparky.analytics.AveragingRingBuffer; +import org.openecomp.sparky.synchronizer.config.TaskProcessorConfig; + +/** + * TODO: Fill in description. + * + * @author davea. + */ +public class TransactionRateController { + + private AveragingRingBuffer responseTimeTracker; + private double msPerTransaction; + private int numThreads; + private TaskProcessorConfig config; + private long startTimeInMs; + private AtomicInteger numTransactions; + + /** + * Instantiates a new transaction rate controller. + * + * @param config the config + */ + public TransactionRateController(TaskProcessorConfig config) { + + this.config = config; + this.responseTimeTracker = new AveragingRingBuffer( + config.getNumSamplesPerThreadForRunningAverage() * config.getMaxConcurrentWorkers()); + this.msPerTransaction = 1000 / config.getTargetTps(); + this.numThreads = config.getMaxConcurrentWorkers(); + this.startTimeInMs = System.currentTimeMillis(); + this.numTransactions = new AtomicInteger(0); + } + + /** + * Track response time. + * + * @param responseTimeInMs the response time in ms + */ + public void trackResponseTime(long responseTimeInMs) { + this.numTransactions.incrementAndGet(); + responseTimeTracker.addSample(responseTimeInMs); + } + + public long getFixedDelayInMs() { + + /* + * The math here is pretty simple: + * + * 1. Target TPS is 10. Then the msPerTxn = 1000/10 = 100ms + * + * 2. If the calculated avgResponseTime = 40 ms, then the proposed delay is 60ms per thread. + * + * 3. If the calculated avgResponseTime = 200ms, then the proposed delay is -100 ms, which is + * not possible, we can't speed it up, so we don't propose any further delay. + */ + + double proposedDelay = 0; + + if (config.isTransactionRateControllerEnabled()) { + proposedDelay = ((msPerTransaction - responseTimeTracker.getAvg()) * this.numThreads); + + if (proposedDelay > 0) { + return (long) (proposedDelay); + } + } + + return (long) proposedDelay; + } + + public long getAvg() { + return responseTimeTracker.getAvg(); + } + + public double getCurrentTps() { + if (numTransactions.get() > 0) { + double timeDelta = System.currentTimeMillis() - startTimeInMs; + double numTxns = numTransactions.get(); + return (numTxns / timeDelta) * 1000.0; + } + + return 0.0; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/config/SynchronizerConfiguration.java b/src/main/java/org/openecomp/sparky/synchronizer/config/SynchronizerConfiguration.java new file mode 100644 index 0000000..34286b4 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/config/SynchronizerConfiguration.java @@ -0,0 +1,444 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.config; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.util.ConfigHelper; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + + +/** + * The Class SynchronizerConfiguration. + */ +public class SynchronizerConfiguration { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(SynchronizerConfiguration.class); + + public static final String CONFIG_FILE = + TierSupportUiConstants.DYNAMIC_CONFIG_APP_LOCATION + "synchronizer.properties"; + + private static SynchronizerConfiguration instance; + + public static final String DEPTH_MODIFIER = "?depth=0"; + public static final String DEPTH_ALL_MODIFIER = "?depth=all"; + public static final String DEPTH_AND_NODES_ONLY_MODIFIER = "?depth=0&nodes-only"; + public static final String NODES_ONLY_MODIFIER = "?nodes-only"; + + public static SynchronizerConfiguration getConfig() throws Exception { + + if (instance == null) { + instance = new SynchronizerConfiguration(); + instance.initialize(); + } + + return instance; + } + + /** + * Instantiates a new synchronizer configuration. + */ + public SynchronizerConfiguration() { + // test method + } + + /** + * Initialize. + * + * @throws Exception the exception + */ + protected void initialize() throws Exception { + + Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); + + // parse config for startup sync + try { + syncTaskInitialDelayInMs = + Integer.parseInt(props.getProperty("synchronizer.syncTask.initialDelayInMs", + SynchronizerConstants.DEFAULT_INITIAL_DELAY_IN_MS)); + if (syncTaskInitialDelayInMs < 0) { + throw new Exception(); + } + } catch (Exception exc) { + this.setConfigOkForStartupSync(false); + syncTaskInitialDelayInMs = SynchronizerConstants.DEFAULT_CONFIG_ERROR_INT_VALUE; + String message = "Invalid configuration for synchronizer parameter:" + + " 'synchronizer.syncTask.initialDelayInMs'"; + LOG.error(AaiUiMsgs.SYNC_INVALID_CONFIG_PARAM, message); + } + + // parse config for periodic sync + try { + syncTaskFrequencyInDay = + Integer.parseInt(props.getProperty("synchronizer.syncTask.taskFrequencyInDay", + SynchronizerConstants.DEFAULT_TASK_FREQUENCY_IN_DAY)); + if (syncTaskFrequencyInDay < 0) { + throw new Exception(); + } + } catch (Exception exc) { + this.setConfigOkForPeriodicSync(false); + syncTaskFrequencyInDay = SynchronizerConstants.DEFAULT_CONFIG_ERROR_INT_VALUE; + String message = "Invalid configuration for synchronizer parameter:" + + " 'synchronizer.syncTask.taskFrequencyInDay'"; + LOG.error(AaiUiMsgs.SYNC_INVALID_CONFIG_PARAM, message); + } + + try { + syncTaskStartTime = props.getProperty("synchronizer.syncTask.startTimestamp", + SynchronizerConstants.DEFAULT_START_TIMESTAMP); // Default 05:00:00 UTC + Pattern pattern = Pattern.compile(SynchronizerConstants.TIMESTAMP24HOURS_PATTERN); + Matcher matcher = pattern.matcher(syncTaskStartTime); + if (!matcher.matches()) { + throw new Exception(); + } + + List timestampVal = Arrays.asList(syncTaskStartTime.split(" ")); + + if (timestampVal.size() == SynchronizerConstants.COMPONENTS_IN_TIMESTAMP) { + // Need both time and timezone offset + syncTaskStartTimeTimeZone = timestampVal + .get(SynchronizerConstants.IDX_TIMEZONE_IN_TIMESTAMP).replaceAll("UTC", "GMT"); + + String time = timestampVal.get(SynchronizerConstants.IDX_TIME_IN_TIMESTAMP); + DateFormat format = new SimpleDateFormat("HH:mm:ss"); + Date date = format.parse(time); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + + syncTaskStartTimeHr = calendar.get(Calendar.HOUR_OF_DAY); + syncTaskStartTimeMin = calendar.get(Calendar.MINUTE); + syncTaskStartTimeSec = calendar.get(Calendar.SECOND); + } else { + LOG.info(AaiUiMsgs.SYNC_START_TIME); + } + } catch (Exception exc) { + this.setConfigOkForPeriodicSync(false); + String message = "Invalid configuration for synchronizer parameter:" + + " 'synchronizer.syncTask.startTimestamp'"; + LOG.error(AaiUiMsgs.SYNC_INVALID_CONFIG_PARAM, message); + } + + scrollContextTimeToLiveInMinutes = + Integer.parseInt(props.getProperty("synchronizer.scrollContextTimeToLiveInMinutes", "5")); + numScrollContextItemsToRetrievePerRequest = Integer.parseInt( + props.getProperty("synchronizer.numScrollContextItemsToRetrievePerRequest", "5000")); + + resolverProgressLogFrequencyInMs = Long + .parseLong(props.getProperty("synchronizer.resolver.progressLogFrequencyInMs", "60000")); + resolverQueueMonitorFrequencyInMs = Long + .parseLong(props.getProperty("synchronizer.resolver.queueMonitorFrequencyInMs", "1000")); + + indexIntegrityValidatorEnabled = Boolean + .parseBoolean(props.getProperty("synchronizer.indexIntegrityValidator.enabled", "false")); + indexIntegrityValidatorFrequencyInMs = Long.parseLong( + props.getProperty("synchronizer.indexIntegrityValidatorFrequencyInMs", "300000")); + + displayVerboseQueueManagerStats = Boolean + .parseBoolean(props.getProperty("synchronizer.resolver.displayVerboseQueueManagerStats")); + + resourceNotFoundErrorsSupressed = + Boolean.parseBoolean(props.getProperty("synchronizer.suppressResourceNotFoundErrors")); + + nodesOnlyModifierEnabled = + Boolean.parseBoolean(props.getProperty("synchronizer.applyNodesOnlyModifier")); + + historicalEntitySummarizerEnabled = Boolean + .parseBoolean(props.getProperty("synchronizer.historicalEntitySummarizerEnabled", "true")); + historicalEntitySummarizedFrequencyInMinutes = Long.parseLong( + props.getProperty("synchronizer.historicalEntitySummarizedFrequencyInMinutes", "60")); + + autosuggestSynchronizationEnabled = Boolean + .parseBoolean(props.getProperty("synchronizer.autosuggestSynchronizationEnabled", "true")); + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, this.toString()); + } + } + + public boolean isNodesOnlyModifierEnabled() { + return nodesOnlyModifierEnabled; + } + + public void setNodesOnlyModifierEnabled(boolean nodesOnlyModifierEnabled) { + this.nodesOnlyModifierEnabled = nodesOnlyModifierEnabled; + } + + public int getSyncTaskInitialDelayInMs() { + return syncTaskInitialDelayInMs; + } + + public void setSyncTaskInitialDelayInMs(int syncTaskInitialDelayInMs) { + this.syncTaskInitialDelayInMs = syncTaskInitialDelayInMs; + } + + public boolean isDisplayVerboseQueueManagerStats() { + return displayVerboseQueueManagerStats; + } + + public void setDisplayVerboseQueueManagerStats(boolean displayVerboseQueueManagerStats) { + this.displayVerboseQueueManagerStats = displayVerboseQueueManagerStats; + } + + public boolean isHistoricalEntitySummarizerEnabled() { + return historicalEntitySummarizerEnabled; + } + + public void setHistoricalEntitySummarizerEnabled(boolean historicalEntitySummarizerEnabled) { + this.historicalEntitySummarizerEnabled = historicalEntitySummarizerEnabled; + } + + public long getHistoricalEntitySummarizedFrequencyInMinutes() { + return historicalEntitySummarizedFrequencyInMinutes; + } + + public void setHistoricalEntitySummarizedFrequencyInMinutes( + long historicalEntitySummarizedFrequencyInMinutes) { + this.historicalEntitySummarizedFrequencyInMinutes = + historicalEntitySummarizedFrequencyInMinutes; + } + + private int syncTaskInitialDelayInMs; + + private int syncTaskFrequencyInMs; + + private int scrollContextTimeToLiveInMinutes; + + private int numScrollContextItemsToRetrievePerRequest; + + private long resolverProgressLogFrequencyInMs; + + private long resolverQueueMonitorFrequencyInMs; + + private boolean indexIntegrityValidatorEnabled; + + private long indexIntegrityValidatorFrequencyInMs; + + private int syncTaskFrequencyInDay; + + private String syncTaskStartTime; + + private int syncTaskStartTimeHr = 5; // for default sync start time + + private int syncTaskStartTimeMin; + + private int syncTaskStartTimeSec; + + private String syncTaskStartTimeTimeZone; + + private boolean displayVerboseQueueManagerStats; + + private boolean resourceNotFoundErrorsSupressed; + + private boolean nodesOnlyModifierEnabled; + + private boolean historicalEntitySummarizerEnabled; + + private boolean autosuggestSynchronizationEnabled; + + private long historicalEntitySummarizedFrequencyInMinutes; + + + private boolean configOkForStartupSync = true; + + private boolean configOkForPeriodicSync = true; + + public boolean isResourceNotFoundErrorsSupressed() { + return resourceNotFoundErrorsSupressed; + } + + public void setResourceNotFoundErrorsSupressed(boolean resourceNotFoundErrorsSupressed) { + this.resourceNotFoundErrorsSupressed = resourceNotFoundErrorsSupressed; + } + + public int getScrollContextTimeToLiveInMinutes() { + return scrollContextTimeToLiveInMinutes; + } + + public void setScrollContextTimeToLiveInMinutes(int scrollContextTimeToLiveInMinutes) { + this.scrollContextTimeToLiveInMinutes = scrollContextTimeToLiveInMinutes; + } + + public int getNumScrollContextItemsToRetrievePerRequest() { + return numScrollContextItemsToRetrievePerRequest; + } + + public void setNumScrollContextItemsToRetrievePerRequest( + int numScrollContextItemsToRetrievePerRequest) { + this.numScrollContextItemsToRetrievePerRequest = numScrollContextItemsToRetrievePerRequest; + } + + public int getSyncTaskFrequencyInDay() { + return syncTaskFrequencyInDay; + } + + public void setSyncTaskFrequencyInDay(int syncTaskFrequencyInDay) { + this.syncTaskFrequencyInDay = syncTaskFrequencyInDay; + } + + public String getSyncTaskStartTime() { + return syncTaskStartTime; + } + + public void setSyncTaskStartTime(String syncTaskStartTime) { + this.syncTaskStartTime = syncTaskStartTime; + } + + public int getSyncTaskStartTimeHr() { + return syncTaskStartTimeHr; + } + + public void setSyncTaskStartTimeHr(int syncTaskStartTimeHr) { + this.syncTaskStartTimeHr = syncTaskStartTimeHr; + } + + public int getSyncTaskStartTimeMin() { + return syncTaskStartTimeMin; + } + + public void setSyncTaskStartTimeMin(int syncTaskStartTimeMin) { + this.syncTaskStartTimeMin = syncTaskStartTimeMin; + } + + public int getSyncTaskStartTimeSec() { + return syncTaskStartTimeSec; + } + + public void setSyncTaskStartTimeSec(int syncTaskStartTimeSec) { + this.syncTaskStartTimeSec = syncTaskStartTimeSec; + } + + public String getSyncTaskStartTimeTimeZone() { + return syncTaskStartTimeTimeZone; + } + + public void setSyncTaskStartTimeTimeZone(String syncTaskStartTimeTimeZone) { + this.syncTaskStartTimeTimeZone = syncTaskStartTimeTimeZone; + } + + public int getSyncTaskFrequencyInMs() { + return syncTaskFrequencyInMs; + } + + public void setSyncTaskFrequencyInMs(int syncTaskFrequencyInMs) { + this.syncTaskFrequencyInMs = syncTaskFrequencyInMs; + } + + public long getResolverProgressLogFrequencyInMs() { + return resolverProgressLogFrequencyInMs; + } + + public void setResolverProgressLogFrequencyInMs(long resolverProgressLogFrequencyInMs) { + this.resolverProgressLogFrequencyInMs = resolverProgressLogFrequencyInMs; + } + + public long getResolverQueueMonitorFrequencyInMs() { + return resolverQueueMonitorFrequencyInMs; + } + + public void setResolverQueueMonitorFrequencyInMs(long resolverQueueMonitorFrequencyInMs) { + this.resolverQueueMonitorFrequencyInMs = resolverQueueMonitorFrequencyInMs; + } + + public boolean isIndexIntegrityValidatorEnabled() { + return indexIntegrityValidatorEnabled; + } + + public void setIndexIntegrityValidatorEnabled(boolean indexIntegrityValidatorEnabled) { + this.indexIntegrityValidatorEnabled = indexIntegrityValidatorEnabled; + } + + public long getIndexIntegrityValidatorFrequencyInMs() { + return indexIntegrityValidatorFrequencyInMs; + } + + public void setIndexIntegrityValidatorFrequencyInMs(long indexIntegrityValidatorFrequencyInMs) { + this.indexIntegrityValidatorFrequencyInMs = indexIntegrityValidatorFrequencyInMs; + } + + public boolean isConfigOkForStartupSync() { + return configOkForStartupSync; + } + + public void setConfigOkForStartupSync(boolean configOkForStartupSync) { + this.configOkForStartupSync = configOkForStartupSync; + } + + public boolean isConfigOkForPeriodicSync() { + return configOkForPeriodicSync; + } + + public void setConfigOkForPeriodicSync(boolean configOkForPeriodicSync) { + this.configOkForPeriodicSync = configOkForPeriodicSync; + } + + public boolean isConfigDisabledForInitialSync() { + return syncTaskInitialDelayInMs == SynchronizerConstants.DELAY_NO_STARTUP_SYNC_IN_MS; + } + + public boolean isAutosuggestSynchronizationEnabled() { + return autosuggestSynchronizationEnabled; + } + + public void setAutosuggestSynchronizationEnabled(boolean autosuggestSynchronizationEnabled) { + this.autosuggestSynchronizationEnabled = autosuggestSynchronizationEnabled; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "SynchronizerConfiguration [syncTaskInitialDelayInMs=" + syncTaskInitialDelayInMs + + ", syncTaskFrequencyInMs=" + syncTaskFrequencyInMs + ", scrollContextTimeToLiveInMinutes=" + + scrollContextTimeToLiveInMinutes + ", numScrollContextItemsToRetrievePerRequest=" + + numScrollContextItemsToRetrievePerRequest + ", resolverProgressLogFrequencyInMs=" + + resolverProgressLogFrequencyInMs + ", resolverQueueMonitorFrequencyInMs=" + + resolverQueueMonitorFrequencyInMs + ", indexIntegrityValidatorEnabled=" + + indexIntegrityValidatorEnabled + ", indexIntegrityValidatorFrequencyInMs=" + + indexIntegrityValidatorFrequencyInMs + ", ssyncTaskFrequencyInDay=" + + syncTaskFrequencyInDay + ", syncTaskStartTime=" + syncTaskStartTime + + ", syncTaskStartTimeHr=" + syncTaskStartTimeHr + ", syncTaskStartTimeMin=" + + syncTaskStartTimeMin + ", syncTaskStartTimeSec=" + syncTaskStartTimeSec + + ", syncTaskStartTimeTimeZone=" + syncTaskStartTimeTimeZone + + ", displayVerboseQueueManagerStats=" + displayVerboseQueueManagerStats + + ", resourceNotFoundErrorsSupressed=" + resourceNotFoundErrorsSupressed + + ", nodesOnlyModifierEnabled=" + nodesOnlyModifierEnabled + ", configOKForStartupSync=" + + configOkForStartupSync + ", configOKForPeriodicSync=" + configOkForPeriodicSync + + ", autosuggestSynchronizationEnabled=" + autosuggestSynchronizationEnabled + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/config/SynchronizerConstants.java b/src/main/java/org/openecomp/sparky/synchronizer/config/SynchronizerConstants.java new file mode 100644 index 0000000..8e22157 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/config/SynchronizerConstants.java @@ -0,0 +1,63 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.config; + +import java.util.Date; + +/** + * The Class SynchronizerConstants. + */ +public final class SynchronizerConstants { + // Error values for invalid user input + public static final int DEFAULT_CONFIG_ERROR_INT_VALUE = Integer.MAX_VALUE; + public static final Date DEFAULT_CONFIG_ERROR_DATE_VALUE = new Date(Long.MAX_VALUE); + + // constants for scheduling synchronizer + public static final int COMPONENTS_IN_TIMESTAMP = 2; + public static final String DEFAULT_INITIAL_DELAY_IN_MS = "0"; + public static final String DEFAULT_TASK_FREQUENCY_IN_DAY = "0"; + public static final String DEFAULT_START_TIMESTAMP = "05:00:00 UTC"; + public static final long DELAY_NO_STARTUP_SYNC_IN_MS = 0; + public static final long DELAY_NO_PERIODIC_SYNC_IN_MS = 0; + public static final int IDX_TIME_IN_TIMESTAMP = 0; + public static final int IDX_TIMEZONE_IN_TIMESTAMP = 1; + public static final long MILLISEC_IN_A_MIN = 60000; + public static final long MILLISEC_IN_A_DAY = 24 * 60 * 60 * 1000; + public static final String TIME_STD = "GMT"; + public static final String TIME_CONFIG_STD = "UTC"; + public static final String TIMESTAMP24HOURS_PATTERN = + "([01]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9] UTC[+|-][0-5][0-9]:[0-5][0-9]"; + + + + public static final String DEFAULT_SCROLL_CTX_TIME_TO_LIVE_IN_MIN = "5"; + public static final String DEFAULT_NUM_SCROLL_CTX_ITEMS_TO_RETRIEVE_PER_REQ = "5000"; + + /** + * Instantiates a new synchronizer constants. + */ + private SynchronizerConstants() {} +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/config/TaskProcessorConfig.java b/src/main/java/org/openecomp/sparky/synchronizer/config/TaskProcessorConfig.java new file mode 100644 index 0000000..7cbfe31 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/config/TaskProcessorConfig.java @@ -0,0 +1,328 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.config; + +import java.util.Properties; + +/** + * TODO: Fill in description. + * + * @author davea. + */ +public class TaskProcessorConfig { + /** + * Initialize from properties. + * + * @param props the props + */ + public void initializeFromProperties(Properties props) { + + if (props == null) { + return; + } + + maxConcurrentWorkers = Integer.parseInt(props.getProperty("maxConcurrentWorkers")); + transactionRateControllerEnabled = + Boolean.parseBoolean(props.getProperty("transactionRateControllerEnabled")); + numSamplesPerThreadForRunningAverage = + Integer.parseInt(props.getProperty("numSamplesPerThreadForRunningAverage")); + targetTps = Double.parseDouble(props.getProperty("targetTPS")); + bytesHistogramLabel = props.getProperty("bytesHistogramLabel"); + bytesHistogramMaxYAxis = Long.parseLong(props.getProperty("bytesHistogramMaxYAxis")); + bytesHistogramNumBins = Integer.parseInt(props.getProperty("bytesHistogramNumBins")); + bytesHistogramNumDecimalPoints = + Integer.parseInt(props.getProperty("bytesHistogramNumDecimalPoints")); + queueLengthHistogramLabel = props.getProperty("queueLengthHistogramLabel"); + queueLengthHistogramMaxYAxis = + Long.parseLong(props.getProperty("queueLengthHistogramMaxYAxis")); + queueLengthHistogramNumBins = + Integer.parseInt(props.getProperty("queueLengthHistogramNumBins")); + queueLengthHistogramNumDecimalPoints = + Integer.parseInt(props.getProperty("queueLengthHistogramNumDecimalPoints")); + + taskAgeHistogramLabel = props.getProperty("taskAgeHistogramLabel"); + taskAgeHistogramMaxYAxis = Long.parseLong(props.getProperty("taskAgeHistogramMaxYAxis")); + taskAgeHistogramNumBins = Integer.parseInt(props.getProperty("taskAgeHistogramNumBins")); + taskAgeHistogramNumDecimalPoints = + Integer.parseInt(props.getProperty("taskAgeHistogramNumDecimalPoints")); + + responseTimeHistogramLabel = props.getProperty("responseTimeHistogramLabel"); + responseTimeHistogramMaxYAxis = + Long.parseLong(props.getProperty("responseTimeHistogramMaxYAxis")); + responseTimeHistogramNumBins = + Integer.parseInt(props.getProperty("responseTimeHistogramNumBins")); + responseTimeHistogramNumDecimalPoints = + Integer.parseInt(props.getProperty("responseTimeHistogramNumDecimalPoints")); + + tpsHistogramLabel = props.getProperty("tpsHistogramLabel"); + tpsHistogramMaxYAxis = Long.parseLong(props.getProperty("tpsHistogramMaxYAxis")); + tpsHistogramNumBins = Integer.parseInt(props.getProperty("tpsHistogramNumBins")); + tpsHistogramNumDecimalPoints = + Integer.parseInt(props.getProperty("tpsHistogramNumDecimalPoints")); + + } + + private int maxConcurrentWorkers; + + private boolean transactionRateControllerEnabled; + + private int numSamplesPerThreadForRunningAverage; + + private double targetTps; + + private String bytesHistogramLabel; + + private long bytesHistogramMaxYAxis; + + private int bytesHistogramNumBins; + + private int bytesHistogramNumDecimalPoints; + + private String queueLengthHistogramLabel; + + private long queueLengthHistogramMaxYAxis; + + private int queueLengthHistogramNumBins; + + private int queueLengthHistogramNumDecimalPoints; + + private String taskAgeHistogramLabel; + + private long taskAgeHistogramMaxYAxis; + + private int taskAgeHistogramNumBins; + + private int taskAgeHistogramNumDecimalPoints; + + private String responseTimeHistogramLabel; + + private long responseTimeHistogramMaxYAxis; + + private int responseTimeHistogramNumBins; + + private int responseTimeHistogramNumDecimalPoints; + + private String tpsHistogramLabel; + + private long tpsHistogramMaxYAxis; + + private int tpsHistogramNumBins; + + private int tpsHistogramNumDecimalPoints; + + public String getBytesHistogramLabel() { + return bytesHistogramLabel; + } + + public void setBytesHistogramLabel(String bytesHistogramLabel) { + this.bytesHistogramLabel = bytesHistogramLabel; + } + + public long getBytesHistogramMaxYAxis() { + return bytesHistogramMaxYAxis; + } + + public void setBytesHistogramMaxYAxis(long bytesHistogramMaxYAxis) { + this.bytesHistogramMaxYAxis = bytesHistogramMaxYAxis; + } + + public int getBytesHistogramNumBins() { + return bytesHistogramNumBins; + } + + public void setBytesHistogramNumBins(int bytesHistogramNumBins) { + this.bytesHistogramNumBins = bytesHistogramNumBins; + } + + public int getBytesHistogramNumDecimalPoints() { + return bytesHistogramNumDecimalPoints; + } + + public void setBytesHistogramNumDecimalPoints(int bytesHistogramNumDecimalPoints) { + this.bytesHistogramNumDecimalPoints = bytesHistogramNumDecimalPoints; + } + + public String getQueueLengthHistogramLabel() { + return queueLengthHistogramLabel; + } + + public void setQueueLengthHistogramLabel(String queueLengthHistogramLabel) { + this.queueLengthHistogramLabel = queueLengthHistogramLabel; + } + + public long getQueueLengthHistogramMaxYAxis() { + return queueLengthHistogramMaxYAxis; + } + + public void setQueueLengthHistogramMaxYAxis(long queueLengthHistogramMaxYAxis) { + this.queueLengthHistogramMaxYAxis = queueLengthHistogramMaxYAxis; + } + + public int getQueueLengthHistogramNumBins() { + return queueLengthHistogramNumBins; + } + + public void setQueueLengthHistogramNumBins(int queueLengthHistogramNumBins) { + this.queueLengthHistogramNumBins = queueLengthHistogramNumBins; + } + + public int getQueueLengthHistogramNumDecimalPoints() { + return queueLengthHistogramNumDecimalPoints; + } + + public void setQueueLengthHistogramNumDecimalPoints(int queueLengthHistogramNumDecimalPoints) { + this.queueLengthHistogramNumDecimalPoints = queueLengthHistogramNumDecimalPoints; + } + + public boolean isTransactionRateControllerEnabled() { + return transactionRateControllerEnabled; + } + + public void setTransactionRateControllerEnabled(boolean transactionRateControllerEnabled) { + this.transactionRateControllerEnabled = transactionRateControllerEnabled; + } + + public int getNumSamplesPerThreadForRunningAverage() { + return numSamplesPerThreadForRunningAverage; + } + + public void setNumSamplesPerThreadForRunningAverage(int numSamplesPerThreadForRunningAverage) { + this.numSamplesPerThreadForRunningAverage = numSamplesPerThreadForRunningAverage; + } + + public double getTargetTps() { + return targetTps; + } + + public void setTargetTps(double targetTps) { + this.targetTps = targetTps; + } + + public int getMaxConcurrentWorkers() { + return maxConcurrentWorkers; + } + + public void setMaxConcurrentWorkers(int maxConcurrentWorkers) { + this.maxConcurrentWorkers = maxConcurrentWorkers; + } + + public String getTaskAgeHistogramLabel() { + return taskAgeHistogramLabel; + } + + public void setTaskAgeHistogramLabel(String taskAgeHistogramLabel) { + this.taskAgeHistogramLabel = taskAgeHistogramLabel; + } + + public long getTaskAgeHistogramMaxYAxis() { + return taskAgeHistogramMaxYAxis; + } + + public void setTaskAgeHistogramMaxYAxis(long taskAgeHistogramMaxYAxis) { + this.taskAgeHistogramMaxYAxis = taskAgeHistogramMaxYAxis; + } + + public int getTaskAgeHistogramNumBins() { + return taskAgeHistogramNumBins; + } + + public void setTaskAgeHistogramNumBins(int taskAgeHistogramNumBins) { + this.taskAgeHistogramNumBins = taskAgeHistogramNumBins; + } + + public int getTaskAgeHistogramNumDecimalPoints() { + return taskAgeHistogramNumDecimalPoints; + } + + public void setTaskAgeHistogramNumDecimalPoints(int taskAgeHistogramNumDecimalPoints) { + this.taskAgeHistogramNumDecimalPoints = taskAgeHistogramNumDecimalPoints; + } + + public String getResponseTimeHistogramLabel() { + return responseTimeHistogramLabel; + } + + public void setResponseTimeHistogramLabel(String responseTimeHistogramLabel) { + this.responseTimeHistogramLabel = responseTimeHistogramLabel; + } + + public long getResponseTimeHistogramMaxYAxis() { + return responseTimeHistogramMaxYAxis; + } + + public void setResponseTimeHistogramMaxYAxis(long responseTimeHistogramMaxYAxis) { + this.responseTimeHistogramMaxYAxis = responseTimeHistogramMaxYAxis; + } + + public int getResponseTimeHistogramNumBins() { + return responseTimeHistogramNumBins; + } + + public void setResponseTimeHistogramNumBins(int responseTimeHistogramNumBins) { + this.responseTimeHistogramNumBins = responseTimeHistogramNumBins; + } + + public int getResponseTimeHistogramNumDecimalPoints() { + return responseTimeHistogramNumDecimalPoints; + } + + public void setResponseTimeHistogramNumDecimalPoints(int responseTimeHistogramNumDecimalPoints) { + this.responseTimeHistogramNumDecimalPoints = responseTimeHistogramNumDecimalPoints; + } + + public String getTpsHistogramLabel() { + return tpsHistogramLabel; + } + + public void setTpsHistogramLabel(String tpsHistogramLabel) { + this.tpsHistogramLabel = tpsHistogramLabel; + } + + public long getTpsHistogramMaxYAxis() { + return tpsHistogramMaxYAxis; + } + + public void setTpsHistogramMaxYAxis(long tpsHistogramMaxYAxis) { + this.tpsHistogramMaxYAxis = tpsHistogramMaxYAxis; + } + + public int getTpsHistogramNumBins() { + return tpsHistogramNumBins; + } + + public void setTpsHistogramNumBins(int tpsHistogramNumBins) { + this.tpsHistogramNumBins = tpsHistogramNumBins; + } + + public int getTpsHistogramNumDecimalPoints() { + return tpsHistogramNumDecimalPoints; + } + + public void setTpsHistogramNumDecimalPoints(int tpsHistogramNumDecimalPoints) { + this.tpsHistogramNumDecimalPoints = tpsHistogramNumDecimalPoints; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/AggregationEntity.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/AggregationEntity.java new file mode 100644 index 0000000..0f817fe --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/AggregationEntity.java @@ -0,0 +1,116 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +import java.util.HashMap; +import java.util.Map; + +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.util.NodeUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The Class AggregationEntity. + */ +public class AggregationEntity extends IndexableEntity implements IndexDocument { + private Map attributes = new HashMap(); + protected ObjectMapper mapper = new ObjectMapper(); + + /** + * Instantiates a new aggregation entity. + */ + public AggregationEntity() { + super(); + } + + /** + * Instantiates a new aggregation entity. + * + * @param loader the loader + */ + public AggregationEntity(OxmModelLoader loader) { + super(loader); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.entity.IndexDocument#deriveFields() + */ + @Override + public void deriveFields() { + + /* + * We'll try and create a unique identity key that we can use for differencing the previously + * imported record sets as we won't have granular control of what is created/removed and when. + * The best we can hope for is identification of resources by generated Id until the + * Identity-Service UUID is tagged against all resources, then we can use that instead. + */ + this.id = + NodeUtils.generateUniqueShaDigest(link); + } + + public void copyAttributeKeyValuePair(Map map){ + for(String key: map.keySet()){ + if (!key.equalsIgnoreCase("relationship-list")){ // ignore relationship data which is not required in aggregation + this.attributes.put(key, map.get(key).toString()); // not sure if entity attribute can contain an object as value + } + } + } + + public void addAttributeKeyValuePair(String key, String value){ + this.attributes.put(key, value); + } + + @Override + public String getIndexDocumentJson() { + ObjectNode rootNode = mapper.createObjectNode(); + rootNode.put("link", this.getLink()); + rootNode.put("lastmodTimestamp", this.getEntityTimeStamp()); + for (String key: this.attributes.keySet()){ + rootNode.put(key, this.attributes.get(key)); + } + return rootNode.toString(); + } + + @Override + public ObjectNode getBulkImportEntity() { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "IndexDocument [" + (entityType != null ? "entityType=" + entityType + ", " : "") + + (entityPrimaryKeyValue != null ? "entityPrimaryKeyValue=" + entityPrimaryKeyValue + ", " + : "") + + (mapper != null ? "mapper=" + mapper + ", " : "") + (id != null ? "id=" + id + ", " : "") + + (lastmodTimestamp != null ? "lastmodTimestamp=" + lastmodTimestamp + ", " : "") + "]"; + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/AggregationSuggestionEntity.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/AggregationSuggestionEntity.java new file mode 100644 index 0000000..155aed1 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/AggregationSuggestionEntity.java @@ -0,0 +1,86 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.openecomp.sparky.util.NodeUtils; + +public class AggregationSuggestionEntity extends IndexableEntity implements IndexDocument { + + private List inputs = new ArrayList(); + private final String outputString = "VNFs"; + protected ObjectMapper mapper = new ObjectMapper(); + + public AggregationSuggestionEntity() { + super(); + inputs.add("VNFs"); + inputs.add("generic-vnfs"); + } + + @Override + public void deriveFields() { + this.id = NodeUtils.generateUniqueShaDigest(this.outputString); + } + + @Override + public String getIndexDocumentJson() { + + JSONArray inputArray = new JSONArray(); + for (String input: inputs) { + input = input.replace(",","" ); + input = input.replace("[","" ); + input = input.replace("]","" ); + inputArray.put(input); + } + + JSONObject entitySuggest = new JSONObject(); + entitySuggest.put("input", inputArray); + entitySuggest.put("output", this.outputString); + entitySuggest.put("weight", 100); + + JSONObject payloadNode = new JSONObject(); + entitySuggest.put("payload", payloadNode); + + JSONObject rootNode = new JSONObject(); + rootNode.put("entity_suggest", entitySuggest); + + return rootNode.toString(); + } + + @Override + public ObjectNode getBulkImportEntity() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexDocument.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexDocument.java new file mode 100644 index 0000000..a115a84 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexDocument.java @@ -0,0 +1,45 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The Interface IndexDocument. + */ +public interface IndexDocument { + + /** + * Derive fields. + */ + public void deriveFields(); + + public String getIndexDocumentJson(); + + public String getId(); + + public ObjectNode getBulkImportEntity(); +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexableCrossEntityReference.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexableCrossEntityReference.java new file mode 100644 index 0000000..d6de9c0 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexableCrossEntityReference.java @@ -0,0 +1,119 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +import java.util.ArrayList; + +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.util.NodeUtils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + + +/** + * The Class IndexableCrossEntityReference. + */ + +public class IndexableCrossEntityReference extends IndexableEntity implements IndexDocument { + + protected String crossReferenceEntityValues; + protected ArrayList crossEntityReferenceCollection = new ArrayList(); + protected ObjectMapper mapper = new ObjectMapper(); + + /** + * Instantiates a new indexable cross entity reference. + */ + public IndexableCrossEntityReference() { + super(); + } + + /** + * Instantiates a new indexable cross entity reference. + * + * @param loader the loader + */ + public IndexableCrossEntityReference(OxmModelLoader loader) { + super(loader); + } + + /** + * Adds the cross entity reference value. + * + * @param crossEntityReferenceValue the cross entity reference value + */ + public void addCrossEntityReferenceValue(String crossEntityReferenceValue) { + if (!crossEntityReferenceCollection.contains(crossEntityReferenceValue)) { + crossEntityReferenceCollection.add(crossEntityReferenceValue); + } + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.entity.IndexDocument#deriveFields() + */ + @Override + public void deriveFields() { + this.id = NodeUtils.generateUniqueShaDigest(link); + this.crossReferenceEntityValues = NodeUtils.concatArray(crossEntityReferenceCollection, ";"); + } + + @Override + public String getIndexDocumentJson() { + ObjectNode rootNode = mapper.createObjectNode(); + rootNode.put("entityType", this.getEntityType()); + rootNode.put("entityPrimaryKeyValue", this.getEntityPrimaryKeyValue()); + rootNode.put("crossEntityReferenceValues", crossReferenceEntityValues); + rootNode.put("link", link); + rootNode.put("lastmodTimestamp", this.getEntityTimeStamp()); + return rootNode.toString(); + } + + @Override + public ObjectNode getBulkImportEntity() { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "IndexableCrossEntityReference [" + + (crossReferenceEntityValues != null + ? "crossReferenceEntityValues=" + crossReferenceEntityValues + ", " : "") + + (crossEntityReferenceCollection != null + ? "crossEntityReferenceCollection=" + crossEntityReferenceCollection + ", " : "") + + (mapper != null ? "mapper=" + mapper + ", " : "") + (id != null ? "id=" + id + ", " : "") + + (entityType != null ? "entityType=" + entityType + ", " : "") + + (entityPrimaryKeyValue != null ? "entityPrimaryKeyValue=" + entityPrimaryKeyValue + ", " + : "") + + (lastmodTimestamp != null ? "lastmodTimestamp=" + lastmodTimestamp + ", " : "") + + (link != null ? "link=" + link + ", " : "") + (loader != null ? "loader=" + loader : "") + + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexableEntity.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexableEntity.java new file mode 100644 index 0000000..6159bb1 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/IndexableEntity.java @@ -0,0 +1,106 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +import java.sql.Timestamp; +import java.text.SimpleDateFormat; + +import org.openecomp.sparky.config.oxm.OxmModelLoader; + +/** + * The Class IndexableEntity. + */ +public abstract class IndexableEntity { + protected String id; // generated, SHA-256 digest + protected String entityType; + protected String entityPrimaryKeyValue; + protected String lastmodTimestamp; + protected String link; + protected OxmModelLoader loader; + + private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + + /** + * Instantiates a new indexable entity. + */ + public IndexableEntity() { + SimpleDateFormat dateFormat = new SimpleDateFormat(TIMESTAMP_FORMAT); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + String currentFormattedTimeStamp = dateFormat.format(timestamp); + this.setEntityTimeStamp(currentFormattedTimeStamp); + } + + /** + * Instantiates a new indexable entity. + * + * @param loader the loader + */ + public IndexableEntity(OxmModelLoader loader) { + this(); + this.loader = loader; + } + + public String getId() { + return id; + } + + public String getEntityType() { + return entityType; + } + + public String getEntityPrimaryKeyValue() { + return entityPrimaryKeyValue; + } + + public String getEntityTimeStamp() { + return lastmodTimestamp; + } + + public void setId(String id) { + this.id = id; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public void setEntityPrimaryKeyValue(String fieldValue) { + this.entityPrimaryKeyValue = fieldValue; + } + + public void setEntityTimeStamp(String lastmodTimestamp) { + this.lastmodTimestamp = lastmodTimestamp; + } + + public String getLink() { + return link; + } + + public void setLink(String link) { + this.link = link; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/MergableEntity.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/MergableEntity.java new file mode 100644 index 0000000..eccb52b --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/MergableEntity.java @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; + +import java.util.HashMap; +import java.util.Map; + +/** + * The Class MergableEntity. + */ +public class MergableEntity { + private Map other = new HashMap(); + + /** + * Any. + * + * @return the map + */ + @JsonAnyGetter + public Map any() { + return other; + } + + /** + * Sets the. + * + * @param name the name + * @param value the value + */ + @JsonAnySetter + public void set(String name, String value) { + other.put(name, value); + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/ObjectIdCollection.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/ObjectIdCollection.java new file mode 100644 index 0000000..0e52d2e --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/ObjectIdCollection.java @@ -0,0 +1,79 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The Class ObjectIdCollection. + */ +public class ObjectIdCollection { + + protected ConcurrentHashMap importedObjectIds = + new ConcurrentHashMap(); + + public Collection getImportedObjectIds() { + return importedObjectIds.values(); + } + + /** + * Adds the object id. + * + * @param id the id + */ + public void addObjectId(String id) { + importedObjectIds.putIfAbsent(id, id); + } + + public int getSize() { + return importedObjectIds.values().size(); + } + + /** + * Adds the all. + * + * @param items the items + */ + public void addAll(List items) { + if (items == null) { + return; + } + + items.stream().forEach((item) -> { + importedObjectIds.putIfAbsent(item, item); + }); + + } + + /** + * Clear. + */ + public void clear() { + importedObjectIds.clear(); + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/SearchableEntity.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/SearchableEntity.java new file mode 100644 index 0000000..2bccb0a --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/SearchableEntity.java @@ -0,0 +1,152 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.ArrayList; +import java.util.List; + +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.util.NodeUtils; + +/** + * The Class SearchableEntity. + */ +public class SearchableEntity extends IndexableEntity implements IndexDocument { + protected List searchTagCollection = new ArrayList(); + protected List searchTagIdCollection = new ArrayList(); + protected ObjectMapper mapper = new ObjectMapper(); + + /** + * Instantiates a new searchable entity. + */ + public SearchableEntity() { + super(); + } + + /** + * Instantiates a new searchable entity. + * + * @param loader the loader + */ + public SearchableEntity(OxmModelLoader loader) { + super(loader); + } + + /* + * Generated fields, leave the settings for junit overrides + */ + protected String searchTags; // generated based on searchTagCollection values + protected String searchTagIDs; + + /** + * Generates the sha based id. + */ + public void generateId() { + this.id = NodeUtils.generateUniqueShaDigest(link); + } + + /* (non-Javadoc) + * @see org.openecomp.sparky.synchronizer.entity.IndexDocument#deriveFields() + */ + @Override + public void deriveFields() { + + /* + * We'll try and create a unique identity key that we can use for differencing the previously + * imported record sets as we won't have granular control of what is created/removed and when. + * The best we can hope for is identification of resources by generated Id until the + * Identity-Service UUID is tagged against all resources, then we can use that instead. + */ + generateId(); + this.searchTags = NodeUtils.concatArray(searchTagCollection, ";"); + this.searchTagIDs = NodeUtils.concatArray(this.searchTagIdCollection, ";"); + } + + /** + * Adds the search tag with key. + * + * @param searchTag the search tag + * @param searchTagKey the key associated with the search tag (key:value) + */ + public void addSearchTagWithKey(String searchTag, String searchTagKey) { + searchTagIdCollection.add(searchTagKey); + searchTagCollection.add(searchTag); + } + + public List getSearchTagCollection() { + return searchTagCollection; + } + + public String getSearchTags() { + return searchTags; + } + + public String getSearchTagIDs() { + return searchTagIDs; + } + + public List getSearchTagIdCollection() { + return searchTagIdCollection; + } + + @Override + public String getIndexDocumentJson() { + ObjectNode rootNode = mapper.createObjectNode(); + rootNode.put("entityType", this.getEntityType()); + rootNode.put("entityPrimaryKeyValue", this.getEntityPrimaryKeyValue()); + rootNode.put("searchTagIDs", this.getSearchTagIDs()); + rootNode.put("searchTags", this.getSearchTags()); + rootNode.put("link", this.getLink()); + rootNode.put("lastmodTimestamp", this.getEntityTimeStamp()); + return rootNode.toString(); + } + + @Override + public ObjectNode getBulkImportEntity() { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "IndexDocument [" + (entityType != null ? "entityType=" + entityType + ", " : "") + + (entityPrimaryKeyValue != null ? "entityPrimaryKeyValue=" + entityPrimaryKeyValue + ", " + : "") + + (searchTagCollection != null ? "searchTagCollection=" + searchTagCollection + ", " : "") + + (searchTagIdCollection != null ? "searchTagIDCollection=" + searchTagIdCollection + ", " + : "") + + (mapper != null ? "mapper=" + mapper + ", " : "") + (id != null ? "id=" + id + ", " : "") + + (lastmodTimestamp != null ? "lastmodTimestamp=" + lastmodTimestamp + ", " : "") + + (searchTags != null ? "searchTags=" + searchTags + ", " : "") + + (searchTagIDs != null ? "searchTagIDs=" + searchTagIDs : "") + "]"; + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/SelfLinkDescriptor.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/SelfLinkDescriptor.java new file mode 100644 index 0000000..9a3d84d --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/SelfLinkDescriptor.java @@ -0,0 +1,91 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +/** + * The Class SelfLinkDescriptor. + */ +public class SelfLinkDescriptor { + private String selfLink; + private String entityType; + private String depthModifier; + + public String getDepthModifier() { + return depthModifier; + } + + public void setDepthModifier(String depthModifier) { + this.depthModifier = depthModifier; + } + + public String getSelfLink() { + return selfLink; + } + + public void setSelfLink(String selfLink) { + this.selfLink = selfLink; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public SelfLinkDescriptor(String selfLink) { + this(selfLink, null, null); + } + + /** + * Instantiates a new self link descriptor. + * + * @param selfLink the self link + * @param entityType the entity type + */ + public SelfLinkDescriptor(String selfLink, String entityType) { + this(selfLink, null, entityType); + } + + public SelfLinkDescriptor(String selfLink, String depthModifier, String entityType) { + this.selfLink = selfLink; + this.entityType = entityType; + this.depthModifier = depthModifier; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "SelfLinkDescriptor [" + (selfLink != null ? "selfLink=" + selfLink + ", " : "") + + (entityType != null ? "entityType=" + entityType + ", " : "") + + (depthModifier != null ? "depthModifier=" + depthModifier : "") + "]"; + } + +} + diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/SuggestionSearchEntity.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/SuggestionSearchEntity.java new file mode 100644 index 0000000..38558a1 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/SuggestionSearchEntity.java @@ -0,0 +1,279 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.synchronizer.entity; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.util.NodeUtils; + +public class SuggestionSearchEntity extends IndexableEntity implements IndexDocument { + + private String entityType; + private List suggestionConnectorWords = new ArrayList(); + private List suggestionAttributeTypes = new ArrayList(); + private List suggestionAttributeValues = new ArrayList(); + private List suggestionTypeAliases = new ArrayList(); + private List suggestionInputPermutations = new ArrayList(); + private List suggestableAttr = new ArrayList(); + private Map payload = new HashMap(); + private JSONObject payloadJsonNode = new JSONObject(); + private StringBuffer outputString = new StringBuffer(); + private String aliasToUse; + + public Map getPayload() { + return payload; + } + + public void setPayload(Map payload) { + this.payload = payload; + } + + + public JSONObject getPayloadJsonNode() { + return payloadJsonNode; + } + + public void setPayloadJsonNode(JSONObject payloadJsonNode) { + this.payloadJsonNode = payloadJsonNode; + } + + + protected ObjectMapper mapper = new ObjectMapper(); + + public SuggestionSearchEntity() { + super(); + } + + public void setSuggestableAttr(ArrayList attributes) { + for (String attribute : attributes) { + this.suggestableAttr.add(attribute); + } + } + + public void setPayloadFromResponse(JsonNode node) { + Map nodePayload = new HashMap(); + if (suggestableAttr != null) { + for (String attribute : suggestableAttr) { + if (node.get(attribute) != null) { + nodePayload.put(attribute, node.get(attribute).asText()); + } + } + this.setPayload(nodePayload); + } + } + + + public SuggestionSearchEntity(OxmModelLoader loader) { + super(loader); + } + + @Override + public String getEntityType() { + return entityType; + } + + @Override + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public List getSuggestionConnectorWords() { + return suggestionConnectorWords; + } + + public void setSuggestionConnectorWords(List suggestionConnectorWords) { + this.suggestionConnectorWords = suggestionConnectorWords; + } + + public List getSuggestionPropertyTypes() { + return this.suggestionAttributeTypes; + } + + public void setSuggestionPropertyTypes(List suggestionPropertyTypes) { + this.suggestionAttributeTypes = suggestionPropertyTypes; + } + + public List getSuggestionAttributeValues() { + return this.suggestionAttributeValues; + } + + public void setSuggestionAttributeValues(List suggestionAttributeValues) { + this.suggestionAttributeValues = suggestionAttributeValues; + } + + public List getSuggestionAliases() { + return this.suggestionTypeAliases; + } + + public void setSuggestionAliases(List suggestionAliases) { + this.suggestionTypeAliases = suggestionAliases; + } + + public List getSuggestionInputPermutations() { + return this.suggestionInputPermutations; + } + + public void setSuggestionInputPermutations(List permutations) { + this.suggestionInputPermutations = permutations; + } + + public void generateSuggestionInputPermutations() { + + + List entityNames = new ArrayList<>(); + entityNames.add(entityType); + HashMap desc = loader.getOxmModel().get(this.entityType); + String attr = desc.get("suggestionAliases"); + String[] suggestionAliasesArray = attr.split(","); + suggestionTypeAliases = Arrays.asList(suggestionAliasesArray); + this.setAliasToUse(suggestionAliasesArray[suggestionAliasesArray.length - 1]); + for (String alias : suggestionTypeAliases) { + entityNames.add(alias); + } + ArrayList listOfSearchSuggestionPermutations = new ArrayList<>(); + + ArrayList listToPermutate = new ArrayList<>(payload.values()); + + for (String entityName : entityNames) { + listToPermutate.add(entityName); + permutateList(listToPermutate, new ArrayList(), listToPermutate.size(), + listOfSearchSuggestionPermutations); + listToPermutate.remove(entityName); + } + suggestionInputPermutations = listOfSearchSuggestionPermutations; + } + + /** + * Generate all permutations of a list of Strings + * + * @param list + * @param permutation + * @param size + */ + private void permutateList(List list, List permutation, int size, + List listOfSearchSuggestionPermutationList) { + if (permutation.size() == size) { + StringBuilder newPermutation = new StringBuilder(); + + for (int i = 0; i < permutation.size(); i++) { + newPermutation.append(permutation.get(i)).append(" "); + } + + listOfSearchSuggestionPermutationList.add(newPermutation.toString().trim()); + + return; + } + + String[] availableItems = list.toArray(new String[0]); + + for (String i : availableItems) { + permutation.add(i); + list.remove(i); + permutateList(list, permutation, size, listOfSearchSuggestionPermutationList); + list.add(i); + permutation.remove(i); + } + } + + public boolean isSuggestableDoc() { + return this.getPayload().size() != 0; + } + + + @Override + public void deriveFields() { + + int payloadEntryCounter = 1; + for (Map.Entry payload : getPayload().entrySet()) { + // Add the payload(status) only if a valid value is present + if (payload.getValue() != null &&payload.getValue().length() > 0) { + this.getPayloadJsonNode().put(payload.getKey(), payload.getValue()); + this.outputString.append(payload.getValue()); + if (payloadEntryCounter < getPayload().entrySet().size()) { + this.outputString.append(" and "); + } else{ + this.outputString.append(" "); + } + } + payloadEntryCounter++; + } + + this.outputString.append(this.getAliasToUse()); + this.id = NodeUtils.generateUniqueShaDigest(outputString.toString()); + } + + @Override + public String getIndexDocumentJson() { + // TODO Auto-generated method stub + JSONObject rootNode = new JSONObject(); + + JSONArray suggestionsArray = new JSONArray(); + for (String suggestion : suggestionInputPermutations) { + suggestionsArray.put(suggestion); + } + + JSONObject entitySuggest = new JSONObject(); + + entitySuggest.put("input", suggestionsArray); + entitySuggest.put("output", this.outputString); + entitySuggest.put("payload", this.payloadJsonNode); + rootNode.put("entity_suggest", entitySuggest); + + return rootNode.toString(); + } + + @Override + public ObjectNode getBulkImportEntity() { + // TODO Auto-generated method stub + return null; + } + + public String getAliasToUse() { + return aliasToUse; + } + + public void setAliasToUse(String aliasToUse) { + this.aliasToUse = aliasToUse; + } + + @Override + public String toString() { + return "SuggestionSearchEntity [entityType=" + entityType + ", suggestionConnectorWords=" + + suggestionConnectorWords + ", suggestionAttributeTypes=" + suggestionAttributeTypes + + ", suggestionAttributeValues=" + suggestionAttributeValues + ", suggestionTypeAliases=" + + suggestionTypeAliases + ", mapper=" + mapper + "]"; + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/entity/TransactionStorageType.java b/src/main/java/org/openecomp/sparky/synchronizer/entity/TransactionStorageType.java new file mode 100644 index 0000000..4c15e30 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/entity/TransactionStorageType.java @@ -0,0 +1,57 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.entity; + +/** + * The Enum TransactionStorageType. + */ +public enum TransactionStorageType { + EDGE_TAG_QUERY(0, "aaiOffline/edge-tag-query"), ACTIVE_INVENTORY_QUERY(1, + "aaiOffline/active-inventory-query"); + + private Integer index; + private String outputFolder; + + /** + * Instantiates a new transaction storage type. + * + * @param index the index + * @param outputFolder the output folder + */ + TransactionStorageType(Integer index, String outputFolder) { + this.index = index; + this.outputFolder = outputFolder; + } + + public Integer getIndex() { + return index; + } + + public String getOutputFolder() { + return outputFolder; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/enumeration/OperationState.java b/src/main/java/org/openecomp/sparky/synchronizer/enumeration/OperationState.java new file mode 100644 index 0000000..65b350c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/enumeration/OperationState.java @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.enumeration; + +/** + * The Enum OperationState. + */ +public enum OperationState { + INIT, OK, ERROR, ABORT, PENDING +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/enumeration/SynchronizerState.java b/src/main/java/org/openecomp/sparky/synchronizer/enumeration/SynchronizerState.java new file mode 100644 index 0000000..67f8eb6 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/enumeration/SynchronizerState.java @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.enumeration; + +/** + * The Enum SynchronizerState. + */ +public enum SynchronizerState { + IDLE, PERFORMING_SYNCHRONIZATION +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/filter/ElasticSearchSynchronizerFilter.java b/src/main/java/org/openecomp/sparky/synchronizer/filter/ElasticSearchSynchronizerFilter.java new file mode 100644 index 0000000..8f82371 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/filter/ElasticSearchSynchronizerFilter.java @@ -0,0 +1,111 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.filter; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.SyncHelper; +import org.openecomp.sparky.util.NodeUtils; + +import org.openecomp.cl.mdc.MdcContext; + +/* + * This is a wire-frame for an experiment to get the jetty filter-lifecyle initialization method to + * setup a scheduled thread executor with an ElasticSearchSynchronization task, which (I'm hoping) + * will allow us to do periodic ES <=> AAI synchronization. + * + * Alternatively, if the embedded java approach doesn't work we could try instead to do a + * System.exec( "perl refreshElasticSearchInstance.pl"). We have two options, I'm hoping the + * embedded options will work for us. + */ + +/** + * The Class ElasticSearchSynchronizerFilter. + */ +public class ElasticSearchSynchronizerFilter implements Filter { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(ElasticSearchSynchronizerFilter.class); + + private SyncHelper syncHelper; + + /* (non-Javadoc) + * @see javax.servlet.Filter#destroy() + */ + @Override + public void destroy() { + + if (syncHelper != null) { + syncHelper.shutdown(); + } + } + + /* (non-Javadoc) + * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + /* + * However, we will setup the filtermap with a url that should never get it, so we shouldn't + * ever be in here. + */ + + chain.doFilter(request, response); + } + + /* (non-Javadoc) + * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) + */ + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String txnID = NodeUtils.getRandomTxnId(); + MdcContext.initialize(txnID, "ElasticSearchSynchronizerFilter", "", "Init", ""); + + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "init()"); + + try { + new SyncHelper(OxmModelLoader.getInstance()); + } catch (Exception exc) { + throw new ServletException("Caught an exception while initializing filter", exc); + } + + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/CollectEntitySelfLinkTask.java b/src/main/java/org/openecomp/sparky/synchronizer/task/CollectEntitySelfLinkTask.java new file mode 100644 index 0000000..6550551 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/CollectEntitySelfLinkTask.java @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class CollectEntitySelfLinkTask. + */ +public class CollectEntitySelfLinkTask implements Supplier { + + private NetworkTransaction txn; + + private ActiveInventoryDataProvider provider; + + /** + * Instantiates a new collect entity self link task. + * + * @param txn the txn + * @param provider the provider + */ + public CollectEntitySelfLinkTask(NetworkTransaction txn, ActiveInventoryDataProvider provider) { + this.txn = txn; + this.provider = provider; + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + + txn.setTaskAgeInMs(); + + long startTimeInMs = System.currentTimeMillis(); + OperationResult result = null; + try { + result = provider.queryActiveInventoryWithRetries(txn.getLink(), "application/json", 5); + } catch (Exception exc) { + result = new OperationResult(500, + "Caught an exception while trying to resolve link = " + exc.getMessage()); + } finally { + result.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + txn.setOperationResult(result); + } + + return txn; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/CollectEntityTypeSelfLinksTask.java b/src/main/java/org/openecomp/sparky/synchronizer/task/CollectEntityTypeSelfLinksTask.java new file mode 100644 index 0000000..1ce8fdc --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/CollectEntityTypeSelfLinksTask.java @@ -0,0 +1,78 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class CollectEntityTypeSelfLinksTask. + */ +public class CollectEntityTypeSelfLinksTask implements Supplier { + + private ActiveInventoryDataProvider aaiProvider; + + private NetworkTransaction txn; + + /** + * Instantiates a new collect entity type self links task. + * + * @param txn the txn + * @param provider the provider + */ + public CollectEntityTypeSelfLinksTask(NetworkTransaction txn, + ActiveInventoryDataProvider provider) { + this.aaiProvider = provider; + this.txn = txn; + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + + txn.setTaskAgeInMs(); + + long startTimeInMs = System.currentTimeMillis(); + OperationResult result = null; + try { + result = aaiProvider.queryActiveInventoryWithRetries(txn.getLink(), "application/json", 5); + } catch (Exception exc) { + result = new OperationResult(500, + "Caught an exception while trying to resolve link = " + exc.getMessage()); + } finally { + result.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + txn.setOperationResult(result); + } + + return txn; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/GetCrossEntityReferenceEntityTask.java b/src/main/java/org/openecomp/sparky/synchronizer/task/GetCrossEntityReferenceEntityTask.java new file mode 100644 index 0000000..c19c501 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/GetCrossEntityReferenceEntityTask.java @@ -0,0 +1,78 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class GetCrossEntityReferenceEntityTask. + */ +public class GetCrossEntityReferenceEntityTask implements Supplier { + + private NetworkTransaction txn; + + private ActiveInventoryDataProvider provider; + + /** + * Instantiates a new gets the cross entity reference entity task. + * + * @param txn the txn + * @param provider the provider + */ + public GetCrossEntityReferenceEntityTask(NetworkTransaction txn, + ActiveInventoryDataProvider provider) { + this.txn = txn; + this.provider = provider; + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + + txn.setTaskAgeInMs(); + + long startTimeInMs = System.currentTimeMillis(); + OperationResult result = null; + try { + result = provider.queryActiveInventoryWithRetries(txn.getLink(), "application/json", 5); + } catch (Exception exc) { + result = new OperationResult(500, + "Caught an exception while trying to resolve link = " + exc.getMessage()); + } finally { + result.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + txn.setOperationResult(result); + } + + return txn; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/PerformActiveInventoryRetrieval.java b/src/main/java/org/openecomp/sparky/synchronizer/task/PerformActiveInventoryRetrieval.java new file mode 100644 index 0000000..3bfbabd --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/PerformActiveInventoryRetrieval.java @@ -0,0 +1,93 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +/* + * Consider abstraction the tasks into common elemnts, because most of them repeat a generic call + * flow pattern + */ + +/** + * The Class PerformActiveInventoryRetrieval. + */ +public class PerformActiveInventoryRetrieval implements Supplier { + + private static Logger logger = LoggerFactory.getLogger(PerformActiveInventoryRetrieval.class); + + private NetworkTransaction txn; + private ActiveInventoryDataProvider aaiProvider; + private Map contextMap; + + /** + * Instantiates a new perform active inventory retrieval. + * + * @param txn the txn + * @param aaiProvider the aai provider + */ + public PerformActiveInventoryRetrieval(NetworkTransaction txn, + ActiveInventoryDataProvider aaiProvider) { + this.txn = txn; + this.aaiProvider = aaiProvider; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + + txn.setTaskAgeInMs(); + + long startTimeInMs = System.currentTimeMillis(); + MDC.setContextMap(contextMap); + OperationResult result = null; + try { + // todo: use proper config instead of hard-coding parameters + result = aaiProvider.queryActiveInventoryWithRetries(txn.getLink(), "application/json", 5); + } catch (Exception exc) { + logger.error("Failure to resolve self link from AAI. Error = ", exc); + result = new OperationResult(500, + "Caught an exception while trying to resolve link = " + exc.getMessage()); + } finally { + result.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + txn.setOperationResult(result); + } + + return txn; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchPut.java b/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchPut.java new file mode 100644 index 0000000..b6fe489 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchPut.java @@ -0,0 +1,85 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestDataProvider; +import org.slf4j.MDC; + +/** + * The Class PerformElasticSearchPut. + */ +public class PerformElasticSearchPut implements Supplier { + + private RestDataProvider restDataProvider; + private String jsonPayload; + private NetworkTransaction txn; + private Map contextMap; + + /** + * Instantiates a new perform elastic search put. + * + * @param jsonPayload the json payload + * @param txn the txn + * @param restDataProvider the rest data provider + */ + public PerformElasticSearchPut(String jsonPayload, NetworkTransaction txn, + RestDataProvider restDataProvider) { + this.jsonPayload = jsonPayload; + this.txn = txn; + this.restDataProvider = restDataProvider; + this.contextMap = MDC.getCopyOfContextMap(); + } + + public PerformElasticSearchPut(String jsonPayload, NetworkTransaction txn, + RestDataProvider restDataProvider, Map contextMap) { + this.jsonPayload = jsonPayload; + this.txn = txn; + this.restDataProvider = restDataProvider; + this.contextMap = contextMap; + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + txn.setTaskAgeInMs(); + long startTimeInMs = System.currentTimeMillis(); + MDC.setContextMap(contextMap); + + OperationResult or = restDataProvider.doPut(txn.getLink(), jsonPayload, "application/json"); + + or.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + txn.setOperationResult(or); + + return txn; + } +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchRetrieval.java b/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchRetrieval.java new file mode 100644 index 0000000..a144f1c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchRetrieval.java @@ -0,0 +1,69 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestDataProvider; +import org.slf4j.MDC; + +/** + * The Class PerformElasticSearchRetrieval. + */ +public class PerformElasticSearchRetrieval implements Supplier { + + private NetworkTransaction txn; + private RestDataProvider restDataProvider; + private Map contextMap; + + /** + * Instantiates a new perform elastic search retrieval. + * + * @param elasticSearchTxn the elastic search txn + * @param restDataProvider the rest data provider + */ + public PerformElasticSearchRetrieval(NetworkTransaction elasticSearchTxn, + RestDataProvider restDataProvider) { + this.txn = elasticSearchTxn; + this.restDataProvider = restDataProvider; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + MDC.setContextMap(contextMap); + OperationResult or = restDataProvider.doGet(txn.getLink(), "application/json"); + txn.setOperationResult(or); + return txn; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchUpdate.java b/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchUpdate.java new file mode 100644 index 0000000..d5cafc1 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/PerformElasticSearchUpdate.java @@ -0,0 +1,83 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.elasticsearch.ElasticSearchDataProvider; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.slf4j.MDC; + +/** + * The Class PerformElasticSearchUpdate. + */ +public class PerformElasticSearchUpdate implements Supplier { + + private ElasticSearchDataProvider esDataProvider; + private NetworkTransaction operationTracker; + private String updatePayload; + private String updateUrl; + private Map contextMap; + + /** + * Instantiates a new perform elastic search update. + * + * @param updateUrl the update url + * @param updatePayload the update payload + * @param esDataProvider the es data provider + * @param transactionTracker the transaction tracker + */ + public PerformElasticSearchUpdate(String updateUrl, String updatePayload, + ElasticSearchDataProvider esDataProvider, NetworkTransaction transactionTracker) { + this.updateUrl = updateUrl; + this.updatePayload = updatePayload; + this.esDataProvider = esDataProvider; + this.contextMap = MDC.getCopyOfContextMap(); + this.operationTracker = new NetworkTransaction(); + operationTracker.setEntityType(transactionTracker.getEntityType()); + operationTracker.setDescriptor(transactionTracker.getDescriptor()); + operationTracker.setOperationType(transactionTracker.getOperationType()); + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + operationTracker.setTaskAgeInMs(); + long startTimeInMs = System.currentTimeMillis(); + MDC.setContextMap(contextMap); + OperationResult or = esDataProvider.doBulkOperation(updateUrl, updatePayload); + + or.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + operationTracker.setOperationResult(or); + + return operationTracker; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/PersistOperationResultToDisk.java b/src/main/java/org/openecomp/sparky/synchronizer/task/PersistOperationResultToDisk.java new file mode 100644 index 0000000..894faa5 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/PersistOperationResultToDisk.java @@ -0,0 +1,88 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +import java.io.File; +import java.util.Map; +import java.util.function.Supplier; + +import org.openecomp.cl.api.Logger; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.slf4j.MDC; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The Class PersistOperationResultToDisk. + */ +public class PersistOperationResultToDisk implements Supplier { + + private String fullPath; + private OperationResult dataToStore; + private ObjectMapper mapper; + private Logger logger; + private Map contextMap; + + /** + * Instantiates a new persist operation result to disk. + * + * @param fullPath the full path + * @param dataToStore the data to store + * @param mapper the mapper + * @param logger the logger + */ + public PersistOperationResultToDisk(String fullPath, OperationResult dataToStore, + ObjectMapper mapper, Logger logger) { + + this.fullPath = fullPath; + this.mapper = mapper; + this.dataToStore = dataToStore; + this.logger = logger; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public Void get() { + MDC.setContextMap(contextMap); + File file = new File(fullPath); + if (!file.exists()) { + try { + mapper.writeValue(new File(fullPath), dataToStore); + } catch (Exception exc) { + logger.error(AaiUiMsgs.DISK_DATA_WRITE_IO_ERROR, exc.toString()); + } + } + + return null; + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/RetrieveOperationResultFromDisk.java b/src/main/java/org/openecomp/sparky/synchronizer/task/RetrieveOperationResultFromDisk.java new file mode 100644 index 0000000..f69ce38 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/RetrieveOperationResultFromDisk.java @@ -0,0 +1,92 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.synchronizer.task; + +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.function.Supplier; + +import org.openecomp.cl.api.Logger; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The Class RetrieveOperationResultFromDisk. + */ +public class RetrieveOperationResultFromDisk implements Supplier { + + private String fullPath; + private ObjectMapper mapper; + private Logger logger; + + /** + * Instantiates a new retrieve operation result from disk. + * + * @param fullPath the full path + * @param mapper the mapper + * @param logger the logger + */ + public RetrieveOperationResultFromDisk(String fullPath, ObjectMapper mapper, Logger logger) { + + this.fullPath = fullPath; + this.mapper = mapper; + this.logger = logger; + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public OperationResult get() { + + try { + File file = new File(fullPath); + if (file.exists()) { + if (logger.isDebugEnabled()) { + logger.debug(AaiUiMsgs.WILL_RETRIEVE_TXN, fullPath); + } + + Path path = Paths.get(fullPath); + byte[] byteBuffer = Files.readAllBytes(path); + + OperationResult opResult = mapper.readValue(byteBuffer, OperationResult.class); + + return opResult; + } else { + logger.debug(AaiUiMsgs.FAILED_TO_RESTORE_TXN_FILE_MISSING, fullPath); + } + } catch (IOException exc) { + logger.error(AaiUiMsgs.DISK_CACHE_READ_IO_ERROR, exc.getLocalizedMessage()); + } + return null; + } + +} diff --git a/src/main/java/org/openecomp/sparky/synchronizer/task/StoreDocumentTask.java b/src/main/java/org/openecomp/sparky/synchronizer/task/StoreDocumentTask.java new file mode 100644 index 0000000..0134b0d --- /dev/null +++ b/src/main/java/org/openecomp/sparky/synchronizer/task/StoreDocumentTask.java @@ -0,0 +1,81 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.synchronizer.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.NetworkTransaction; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestDataProvider; +import org.openecomp.sparky.synchronizer.entity.IndexDocument; +import org.slf4j.MDC; + +/** + * The Class StoreDocumentTask. + */ +public class StoreDocumentTask implements Supplier { + + private IndexDocument doc; + + private NetworkTransaction txn; + + private RestDataProvider esDataProvider; + private Map contextMap; + + /** + * Instantiates a new store document task. + * + * @param doc the doc + * @param txn the txn + * @param esDataProvider the es data provider + */ + public StoreDocumentTask(IndexDocument doc, NetworkTransaction txn, + RestDataProvider esDataProvider) { + this.doc = doc; + this.txn = txn; + this.esDataProvider = esDataProvider; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NetworkTransaction get() { + txn.setTaskAgeInMs(); + + long startTimeInMs = System.currentTimeMillis(); + MDC.setContextMap(contextMap); + OperationResult or = + esDataProvider.doPut(txn.getLink(), doc.getIndexDocumentJson(), "application/json"); + or.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + + txn.setOperationResult(or); + + return txn; + } + +} diff --git a/src/main/java/org/openecomp/sparky/util/ConfigHelper.java b/src/main/java/org/openecomp/sparky/util/ConfigHelper.java new file mode 100644 index 0000000..5d660ff --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/ConfigHelper.java @@ -0,0 +1,194 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.Set; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.logging.AaiUiMsgs; + +/** + * The Class ConfigHelper. + */ +public class ConfigHelper { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(ConfigHelper.class); + + /** + * Gets the config with prefix. + * + * @param configPrefix the config prefix + * @param properties the properties + * @return the config with prefix + */ + public static Properties getConfigWithPrefix(String configPrefix, Properties properties) { + + /* + * The idea here is collect properties groups prefixed with the same origin + */ + + Set set = properties.keySet(); + Properties newProps = new Properties(); + + for (Object k : set) { + String ks = (String) k; + if (ks.startsWith(configPrefix)) { + + String temp = ks.replaceFirst(configPrefix + ".", ""); + newProps.setProperty(temp, properties.getProperty(ks)); + } + } + + return newProps; + } + + /** + * Load config. + * + * @param fileName the file name + * @return the properties + * @throws Exception the exception + */ + public static Properties loadConfig(String fileName) throws Exception { + + String basePath = System.getProperty("user.dir"); + InputStream fileInputStream = new FileInputStream(basePath + "//" + fileName); + + Properties props = new Properties(); + props.load(fileInputStream); + + return props; + } + + /** + * Load config from explicit path. + * + * @param fileName the file name + * @return the properties + */ + public static Properties loadConfigFromExplicitPath(String fileName) { + + Properties props = new Properties(); + + try { + InputStream fileInputStream = new FileInputStream(fileName); + props.load(fileInputStream); + } catch (Exception exc) { + LOG.warn(AaiUiMsgs.CONFIG_NOT_FOUND_VERBOSE, fileName, exc.getLocalizedMessage()); + } + + return props; + } + + /** + * Property fetch. + * + * @param config the config + * @param propName the prop name + * @param defaultValue the default value + * @return the string + */ + public static String propertyFetch(Properties config, String propName, String defaultValue) { + return config.getProperty(propName, defaultValue); + } + + public static boolean isEssDevModeEnabled() { + return Boolean.parseBoolean(System.getProperty("isEssDevMode", "false")); + } + + /** + * Gets the filepath. + * + * @param fileName the file name + * @param isRelativePath the is relative path + * @return the filepath + */ + public static String getFilepath(String fileName, boolean isRelativePath) { + + String filepath = null; + + if (isRelativePath) { + filepath = System.getProperty("user.dir") + "/" + fileName; + + } else { + filepath = fileName; + } + + return filepath; + + } + + /** + * Gets the file contents. + * + * @param fileName the file name + * @return the file contents + * @throws IOException Signals that an I/O exception has occurred. + */ + public static String getFileContents(String fileName) throws IOException { + + LOG.debug(AaiUiMsgs.FILE_READ_IN_PROGRESS, fileName); + + File file = new File(fileName); + + if (!file.exists()) { + throw new FileNotFoundException("Failed to load file = " + fileName); + } + + if (file.exists() && !file.isDirectory()) { + BufferedReader br = new BufferedReader(new FileReader(file)); + try { + StringBuilder sb = new StringBuilder(); + String line = br.readLine(); + + while (line != null) { + sb.append(line); + sb.append(System.lineSeparator()); + line = br.readLine(); + } + + return sb.toString(); + } finally { + br.close(); + } + } else { + LOG.warn(AaiUiMsgs.FILE_NOT_FOUND, fileName); + } + + return null; + + } + +} diff --git a/src/main/java/org/openecomp/sparky/util/EncryptConvertor.java b/src/main/java/org/openecomp/sparky/util/EncryptConvertor.java new file mode 100644 index 0000000..6b03302 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/EncryptConvertor.java @@ -0,0 +1,150 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +/** + * The Class EncryptConvertor. + */ +public class EncryptConvertor { + + private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); + + /** + * toHexString(String) - convert a string into its hex equivalent. + * + * @param buf the buf + * @return the string + */ + public static final String toHexString(String buf) { + if (buf == null) { + return ""; + } + return toHexString(buf.getBytes()); + } + + /** + * toHexString(byte[]) - convert a byte-string into its hex equivalent. + * + * @param buf the buf + * @return the string + */ + public static final String toHexString(byte[] buf) { + + if (buf == null) { + return ""; + } + char[] chars = new char[2 * buf.length]; + for (int i = 0; i < buf.length; ++i) { + chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4]; + chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F]; + } + return new String(chars); + } + + /** + * Convert a hex string to its equivalent value. + * + * @param hexString the hex string + * @return the string + * @throws Exception the exception + */ + public static final String stringFromHex(String hexString) throws Exception { + if (hexString == null) { + return ""; + } + return stringFromHex(hexString.toCharArray()); + } + + /** + * String from hex. + * + * @param hexCharArray the hex char array + * @return the string + * @throws Exception the exception + */ + public static final String stringFromHex(char[] hexCharArray) throws Exception { + if (hexCharArray == null) { + return ""; + } + return new String(bytesFromHex(hexCharArray)); + } + + /** + * Bytes from hex. + * + * @param hexString the hex string + * @return the byte[] + * @throws Exception the exception + */ + public static final byte[] bytesFromHex(String hexString) throws Exception { + if (hexString == null) { + return new byte[0]; + } + return bytesFromHex(hexString.toCharArray()); + } + + /** + * Bytes from hex. + * + * @param hexCharArray the hex char array + * @return the byte[] + * @throws Exception the exception + */ + public static final byte[] bytesFromHex(char[] hexCharArray) throws Exception { + if (hexCharArray == null) { + return new byte[0]; + } + int len = hexCharArray.length; + if ((len % 2) != 0) { + throw new Exception("Odd number of characters: '" + String.valueOf(hexCharArray) + "'"); + } + byte[] txtInByte = new byte[len / 2]; + int counter = 0; + for (int i = 0; i < len; i += 2) { + txtInByte[counter++] = + (byte) (((fromHexDigit(hexCharArray[i], i) << 4) | fromHexDigit(hexCharArray[i + 1], i)) + & 0xFF); + } + return txtInByte; + } + + /** + * From hex digit. + * + * @param ch the ch + * @param index the index + * @return the int + * @throws Exception the exception + */ + protected static final int fromHexDigit(char ch, int index) throws Exception { + int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new Exception("Illegal hex character '" + ch + "' at index " + index); + } + return digit; + } + +} diff --git a/src/main/java/org/openecomp/sparky/util/Encryptor.java b/src/main/java/org/openecomp/sparky/util/Encryptor.java new file mode 100644 index 0000000..87abe16 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/Encryptor.java @@ -0,0 +1,137 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +import org.apache.commons.cli.BasicParser; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.eclipse.jetty.util.security.Password; + +/** + * The Class Encryptor. + */ +public class Encryptor { + + /** + * Instantiates a new encryptor. + */ + public Encryptor() { + } + + /** + * Decrypt value. + * + * @param value the value + * @return the string + */ + public String decryptValue(String value) { + String decyptedValue = ""; + + try { + decyptedValue = Password.deobfuscate(value); + } catch (Exception exc) { + System.err.println("Cannot decrypt '" + value + "': " + exc.toString()); + } + + return decyptedValue; + } + + /** + * Usage. + */ + public static void usage() { + usage(null); + } + + /** + * Usage. + * + * @param msg the msg + */ + public static void usage(String msg) { + if (msg != null) { + System.err.println(msg); + } + System.err.println("Usage: java Encryptor -e value"); + System.err.println("\tEncrypt the given value"); + System.err.println("Usage: java Encryptor -d value"); + System.err.println("\tDecrypt the given value"); + System.exit(1); + } + + /** + * The main method. + * + * @param args the arguments + */ + public static void main(String[] args) { + + Options options = new Options(); + options.addOption("d", true, "value to decrypt"); + options.addOption("h", false, "show help"); + options.addOption("?", false, "show help"); + + String value = null; + boolean encrypt = false; + boolean decrypt = false; + + CommandLineParser parser = new BasicParser(); + CommandLine cmd = null; + + try { + cmd = parser.parse(options, args); + + if (cmd.hasOption("d")) { + value = cmd.getOptionValue("d"); + decrypt = true; + } + + if (cmd.hasOption("?") || cmd.hasOption("h")) { + usage(); + System.exit(0); + } + + if ((encrypt && decrypt) || (!encrypt && !decrypt)) { + usage("Must specify one (and only one) of the -e or -d options"); + } + + Encryptor encryptor = new Encryptor(); + + if (decrypt) { + String out = encryptor.decryptValue(value); + System.out.println(out); + } + } catch (ParseException exc) { + System.out.println("Failed to parse command line properties: " + exc.toString()); + } catch (Exception exc) { + System.out.println("Failure: " + exc.toString()); + } + + System.exit(0); + } +} diff --git a/src/main/java/org/openecomp/sparky/util/ErrorUtil.java b/src/main/java/org/openecomp/sparky/util/ErrorUtil.java new file mode 100644 index 0000000..9cea8b3 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/ErrorUtil.java @@ -0,0 +1,63 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + + +package org.openecomp.sparky.util; + +/** + * The Class ErrorUtil. + */ +public class ErrorUtil { + + /** + * Extract stack trace elements. + * + * @param maxNumberOfElementsToCapture the max number of elements to capture + * @param exc the exc + * @return the string + */ + public static String extractStackTraceElements(int maxNumberOfElementsToCapture, Exception exc) { + StringBuilder sb = new StringBuilder(128); + + StackTraceElement[] stackTraceElements = exc.getStackTrace(); + + if (stackTraceElements != null) { + + /* + * We want to avoid an index out-of-bounds error, so we will make sure to only extract the + * number of frames from the stack trace that actually exist. + */ + + int numFramesToExtract = Math.min(maxNumberOfElementsToCapture, stackTraceElements.length); + + for (int x = 0; x < numFramesToExtract; x++) { + sb.append(stackTraceElements[x]).append("\n"); + } + + } + + return sb.toString(); + } +} diff --git a/src/main/java/org/openecomp/sparky/util/JsonXmlConverter.java b/src/main/java/org/openecomp/sparky/util/JsonXmlConverter.java new file mode 100644 index 0000000..845e0af --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/JsonXmlConverter.java @@ -0,0 +1,80 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.XML; + +/** + * The Class JsonXmlConverter. + */ +public class JsonXmlConverter { + + /** + * Checks if is valid json. + * + * @param text the text + * @return true, if is valid json + */ + public static boolean isValidJson(String text) { + try { + new JSONObject(text); + } catch (JSONException ex) { + try { + new JSONArray(text); + } catch (JSONException ex1) { + return false; + } + } + + return true; + } + + /** + * Convert jsonto xml. + * + * @param jsonText the json text + * @return the string + */ + public static String convertJsontoXml(String jsonText) { + JSONObject jsonObj = new JSONObject(jsonText); + String xmlText = XML.toString(jsonObj); + return xmlText; + } + + /** + * Convert xmlto json. + * + * @param xmlText the xml text + * @return the string + */ + public static String convertXmltoJson(String xmlText) { + JSONObject jsonObj = XML.toJSONObject(xmlText); + return jsonObj.toString(); + } +} diff --git a/src/main/java/org/openecomp/sparky/util/KeystoreBuilder.java b/src/main/java/org/openecomp/sparky/util/KeystoreBuilder.java new file mode 100644 index 0000000..6361e95 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/KeystoreBuilder.java @@ -0,0 +1,525 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +/** + * The Class KeystoreBuilder. + */ +public class KeystoreBuilder { + + /** + * The Class EndPoint. + */ + private class EndPoint { + private String hostname; + private int port; + + /** + * Instantiates a new end point. + */ + @SuppressWarnings("unused") + public EndPoint() {} + + /** + * Instantiates a new end point. + * + * @param host the host + * @param port the port + */ + public EndPoint(String host, int port) { + this.hostname = host; + this.port = port; + } + + public String getHostname() { + return hostname; + } + + @SuppressWarnings("unused") + public void setHostname(String hostname) { + this.hostname = hostname; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "EndPoint [hostname=" + hostname + ", port=" + port + "]"; + } + + } + + private List endpoints = new ArrayList(); + + /** + * Initialize end points list. + * + * @param endpointList the endpoint list + */ + private void initializeEndPointsList(String endpointList) { + String[] endpointUris = endpointList.split(";"); + + for (String endpointUri : endpointUris) { + + String ipAndPort = endpointUri.replaceAll("http://", ""); + ipAndPort = endpointUri.replaceAll("https://", ""); + + // System.out.println("ipAndPortUrl = " + ipAndPort); + + String[] hostAndPort = ipAndPort.split(":"); + + String hostname = hostAndPort[0]; + int port = Integer.parseInt(hostAndPort[1]); + + EndPoint ep = new EndPoint(hostname, port); + endpoints.add(ep); + } + + } + + /** + * Instantiates a new keystore builder. + * + * @param endpointList the endpoint list + * @throws NoSuchAlgorithmException the no such algorithm exception + */ + public KeystoreBuilder(String endpointList) throws NoSuchAlgorithmException { + initializeEndPointsList(endpointList); + sha1 = MessageDigest.getInstance("SHA1"); + md5 = MessageDigest.getInstance("MD5"); + } + + private static final String SEP = File.separator; + private SavingTrustManager savingTrustManager; + private SSLSocketFactory sslSocketFactory; + private MessageDigest sha1; + private MessageDigest md5; + private KeyStore ks; + private String keystoreFileName; + private String keystorePassword; + private boolean dumpCertDetails = false; + + public void setDumpCertDetails(boolean shouldSet) { + dumpCertDetails = shouldSet; + } + + /** + * Update keystore. + * + * @param keystoreFileName the keystore file name + * @param keystorePassword the keystore password + * @throws KeyStoreException the key store exception + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws CertificateException the certificate exception + * @throws IOException Signals that an I/O exception has occurred. + * @throws KeyManagementException the key management exception + */ + public void updateKeystore(String keystoreFileName, String keystorePassword) + throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, + KeyManagementException { + + this.keystoreFileName = keystoreFileName; + this.keystorePassword = keystorePassword; + + File file = new File(keystoreFileName); + String password = keystorePassword; + + if (file.isFile() == false) { + + File dir = new File(System.getProperty("java.home") + SEP + "lib" + SEP + "security"); + file = new File(dir, "jssecacerts"); + if (file.isFile() == false) { + + file = new File(dir, "cacerts"); + System.out.println("keystore file doesn't exist, preloading new file with cacerts"); + + } else { + System.out.println("keystore file doesn't exist, preloading new file with jssecacerts"); + } + password = "changeit"; + + } + + InputStream in = new FileInputStream(file); + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + ks.load(in, password.toCharArray()); + in.close(); + + SSLContext context = SSLContext.getInstance("TLS"); + TrustManagerFactory tmf = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0]; + savingTrustManager = new SavingTrustManager(defaultTrustManager); + context.init(null, new TrustManager[] {savingTrustManager}, null); + sslSocketFactory = context.getSocketFactory(); + + System.out.println("About to add the following endpoint server certificates to the keystore:"); + for (EndPoint ep : endpoints) { + System.out.println("\t--------------------------"); + System.out.println("\t" + ep.toString()); + + X509Certificate[] certChain = + getCertificateChainForRemoteEndpoint(ep.getHostname(), ep.getPort()); + + if (certChain == null) { + System.out.println("Could not obtain server certificate chain"); + return; + } + + dumpCertChainInfo(certChain); + + updateKeyStoreWithCertChain(certChain); + + } + + } + + /** + * Gets the certificate chain for remote endpoint. + * + * @param hostname the hostname + * @param port the port + * @return the certificate chain for remote endpoint + * @throws UnknownHostException the unknown host exception + * @throws IOException Signals that an I/O exception has occurred. + */ + private X509Certificate[] getCertificateChainForRemoteEndpoint(String hostname, int port) + throws UnknownHostException, IOException { + + System.out.println("Opening connection to localhost:8442.."); + SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket("aai-int1.dev.att.com", 8440); + socket.setSoTimeout(10000); + + try { + System.out.println("Starting SSL handshake..."); + socket.startHandshake(); + socket.close(); + System.out.println("\nNo errors, certificate is already trusted"); + System.exit(0); + } catch (SSLException exc) { + System.out.println("\nCaught SSL exception, we are not authorized to access this server yet"); + // e.printStackTrace(System.out); + } + + return savingTrustManager.chain; + + } + + /** + * Dump cert chain info. + * + * @param chain the chain + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws CertificateEncodingException the certificate encoding exception + * @throws CertificateParsingException the certificate parsing exception + */ + private void dumpCertChainInfo(X509Certificate[] chain) + throws NoSuchAlgorithmException, CertificateEncodingException, CertificateParsingException { + + System.out.println(); + System.out.println("Server sent " + chain.length + " certificate(s):"); + System.out.println(); + + for (int i = 0; i < chain.length; i++) { + X509Certificate cert = chain[i]; + + if (dumpCertDetails) { + System.out.println("Full cert details @ index = " + i + " \n" + cert.toString()); + } + + System.out.println("Subject: " + cert.getSubjectDN()); + System.out.println("Issuer: " + cert.getIssuerDN()); + System.out.println("SubjectAlternativeNames: "); + + /* + * RFC-5280, pg. 38, section 4.2.1.6 ( Subject Alternative Names ) + * + * Finally, the semantics of subject alternative names that include wildcard characters (e.g., + * as a placeholder for a set of names) are not addressed by this specification. Applications + * with specific requirements MAY use such names, but they must define the semantics. + * + * id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 } + * + * SubjectAltName ::= GeneralNames + * + * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName + * + * GeneralName ::= CHOICE { otherName [0] OtherName, rfc822Name [1] IA5String, dNSName [2] + * IA5String, <-- the 2 in the output is a type operand x400Address [3] ORAddress, + * directoryName [4] Name, ediPartyName [5] EDIPartyName, uniformResourceIdentifier [6] + * IA5String, iPAddress [7] OCTET STRING, registeredID [8] OBJECT IDENTIFIER } + * + * OtherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY DEFINED BY + * type-id } + * + * EDIPartyName ::= SEQUENCE { nameAssigner [0] DirectoryString OPTIONAL, partyName [1] + * DirectoryString } + * + */ + + Collection> sans = cert.getSubjectAlternativeNames(); + + for (List san : sans) { + + /* + * It seems the structure of the array elements contained within the SAN is: [, + * ]* + * + */ + + int type = ((Integer) san.get(0)).intValue(); + String typeStr = getSanType(type); + String value = (String) san.get(1); + + System.out.println(String.format("\tType:'%s', Value: '%s'.", typeStr, value)); + + } + + } + + } + + /** + * Gets the subject alternative names. + * + * @param cert the cert + * @return the subject alternative names + * @throws CertificateParsingException the certificate parsing exception + */ + private List getSubjectAlternativeNames(X509Certificate cert) + throws CertificateParsingException { + + Collection> sans = cert.getSubjectAlternativeNames(); + List subjectAlternativeNames = new ArrayList(); + + for (List san : sans) { + + /* + * It seems the structure of the array elements contained within the SAN is: [, + * ]* + * + */ + + String value = (String) san.get(1); + subjectAlternativeNames.add(value); + } + + return subjectAlternativeNames; + } + + /** + * Update key store with cert chain. + * + * @param chain the chain + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws KeyStoreException the key store exception + * @throws CertificateException the certificate exception + * @throws IOException Signals that an I/O exception has occurred. + */ + private void updateKeyStoreWithCertChain(X509Certificate[] chain) + throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { + + for (X509Certificate cert : chain) { + + List sans = getSubjectAlternativeNames(cert); + + for (String san : sans) { + ks.setCertificateEntry(san, cert); + System.out.println( + "Added certificate to keystore '" + keystoreFileName + "' using alias '" + san + "'"); + } + } + + OutputStream out = new FileOutputStream(keystoreFileName); + ks.store(out, keystorePassword.toCharArray()); + out.close(); + + } + + + /** + * The Class SavingTrustManager. + */ + private static class SavingTrustManager implements X509TrustManager { + + private final X509TrustManager tm; + private X509Certificate[] chain; + + /** + * Instantiates a new saving trust manager. + * + * @param tm the tm + */ + SavingTrustManager(X509TrustManager tm) { + this.tm = tm; + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String) + */ + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + throw new UnsupportedOperationException(); + } + + /* (non-Javadoc) + * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String) + */ + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + this.chain = chain; + tm.checkServerTrusted(chain, authType); + } + } + + private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray(); + + /** + * Gets the san type. + * + * @param type the type + * @return the san type + */ + // TODO: convert to enum(int,string) + private String getSanType(int type) { + switch (type) { + case 0: + return "otherName"; + case 1: + return "rfc822Name"; + case 2: + return "dNSName"; + case 3: + return "x400Address"; + case 4: + return "directoryName"; + case 5: + return "ediPartyName"; + case 6: + return "uniformResourceIdentifier"; + case 7: + return "iPAddress"; + case 8: + return "registeredID"; + default: + return "unknownSanType"; + } + } + + + /** + * To hex string. + * + * @param bytes the bytes + * @return the string + */ + private static String toHexString(byte[] bytes) { + StringBuilder sb = new StringBuilder(bytes.length * 3); + for (int b : bytes) { + b &= 0xff; + sb.append(HEXDIGITS[b >> 4]); + sb.append(HEXDIGITS[b & 15]); + sb.append(' '); + } + return sb.toString(); + } + + + + /** + * The main method. + * + * @param args the arguments + * @throws Exception the exception + */ + public static void main(String[] args) throws Exception { + + // String endpointList = "aai-int1.test.att.com:8440;aai-int1.dev.att.com:8442"; + + /* + * Examples: localhost:8440;localhost:8442 d:\1\adhoc_keystore.jks aaiDomain2 false + * localhost:8440;localhost:8442 d:\1\adhoc_keystore.jks aaiDomain2 true + */ + + if (args.length != 4) { + System.out.println( + "Usage: KeyBuilder <[ip:port];*> " + + " "); + System.exit(1); + } + KeystoreBuilder kb = new KeystoreBuilder(args[0]); + kb.setDumpCertDetails(Boolean.parseBoolean(args[3])); + kb.updateKeystore(args[1], args[2]); + + } +} + + diff --git a/src/main/java/org/openecomp/sparky/util/NodeUtils.java b/src/main/java/org/openecomp/sparky/util/NodeUtils.java new file mode 100644 index 0000000..1789fcf --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/NodeUtils.java @@ -0,0 +1,714 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.Thread.UncaughtExceptionHandler; +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.http.HttpServletRequest; +import javax.xml.stream.XMLStreamConstants; + +import org.openecomp.cl.api.Logger; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + + +/** + * The Class NodeUtils. + */ +public class NodeUtils { + private static SecureRandom sRandom = new SecureRandom(); + + public static synchronized String getRandomTxnId(){ + byte bytes[] = new byte[6]; + sRandom.nextBytes(bytes); + return Integer.toUnsignedString(ByteBuffer.wrap(bytes).getInt()); + } + + /** + * Builds the depth padding. + * + * @param depth the depth + * @return the string + */ + public static String buildDepthPadding(int depth) { + StringBuilder sb = new StringBuilder(32); + + for (int x = 0; x < depth; x++) { + sb.append(" "); + } + + return sb.toString(); + } + + /** + * Checks if is numeric. + * + * @param numberStr the number str + * @return true, if is numeric + */ + public static boolean isNumeric(String numberStr) { + + try { + Double.parseDouble(numberStr); + } catch (Exception exc) { + return false; + } + + return true; + + } + + /** + * Creates the named executor. + * + * @param name the name + * @param numWorkers the num workers + * @param logger the logger + * @return the executor service + */ + public static ExecutorService createNamedExecutor(String name, int numWorkers, final Logger logger) { + UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread thread, Throwable exc) { + + logger.error(AaiUiMsgs.ERROR_GENERIC, thread.getName() + ": " + exc); + + } + }; + + ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(name + "-%d") + .setUncaughtExceptionHandler(uncaughtExceptionHandler).build(); + + return Executors.newScheduledThreadPool(numWorkers + 1, namedThreadFactory); + } + + /** + * Calculate edit attribute uri. + * + * @param link the link + * @return the string + */ + public static String calculateEditAttributeUri(String link) { + String uri = null; + + if (link != null) { + + Pattern pattern = Pattern.compile(TierSupportUiConstants.URI_VERSION_REGEX_PATTERN); + Matcher matcher = pattern.matcher(link); + if (matcher.find()) { + uri = link.substring(matcher.end()); + } + } + return uri; + } + + /** + * Generate unique sha digest. + * + * @param keys the keys + * @return the string + */ + public static String generateUniqueShaDigest(String... keys) { + + if ((keys == null) || keys.length == 0) { + return null; + } + + final String keysStr = Arrays.asList(keys).toString(); + final String hashedId = org.apache.commons.codec.digest.DigestUtils.sha256Hex(keysStr); + + return hashedId; + } + + /** + * Gets the node field as text. + * + * @param node the node + * @param fieldName the field name + * @return the node field as text + */ + public static String getNodeFieldAsText(JsonNode node, String fieldName) { + + String fieldValue = null; + + JsonNode valueNode = node.get(fieldName); + + if (valueNode != null) { + fieldValue = valueNode.asText(); + } + + return fieldValue; + } + + private static final String ENTITY_RESOURCE_KEY_FORMAT = "%s.%s"; + + /** + * Convert a millisecond duration to a string format + * + * @param millis A duration to convert to a string form + * @return A string of the form "X Days Y Hours Z Minutes A Seconds". + */ + + private static final String TIME_BREAK_DOWN_FORMAT = + "[ %d days, %d hours, %d minutes, %d seconds ]"; + + /** + * Gets the duration breakdown. + * + * @param millis the millis + * @return the duration breakdown + */ + public static String getDurationBreakdown(long millis) { + + if (millis < 0) { + return String.format(TIME_BREAK_DOWN_FORMAT, 0, 0, 0, 0); + } + + long days = TimeUnit.MILLISECONDS.toDays(millis); + millis -= TimeUnit.DAYS.toMillis(days); + long hours = TimeUnit.MILLISECONDS.toHours(millis); + millis -= TimeUnit.HOURS.toMillis(hours); + long minutes = TimeUnit.MILLISECONDS.toMinutes(millis); + millis -= TimeUnit.MINUTES.toMillis(minutes); + long seconds = TimeUnit.MILLISECONDS.toSeconds(millis); + + return String.format(TIME_BREAK_DOWN_FORMAT, days, hours, minutes, seconds); + + } + + /** + * Checks if is equal. + * + * @param n1 the n 1 + * @param n2 the n 2 + * @return true, if is equal + */ + public static boolean isEqual(JsonNode n1, JsonNode n2) { + + /* + * due to the inherent nature of json being unordered, comparing object representations of the + * same keys and values but different order makes comparison challenging. Let's try an + * experiment where we compare the structure of the json, and then simply compare the sorted + * order of that structure which should be good enough for what we are trying to accomplish. + */ + + TreeWalker walker = new TreeWalker(); + List n1Paths = new ArrayList(); + List n2Paths = new ArrayList(); + + walker.walkTree(n1Paths, n1); + walker.walkTree(n2Paths, n2); + + Collections.sort(n1Paths); + Collections.sort(n2Paths); + + return n1Paths.equals(n2Paths); + + } + + /** + * Concat array. + * + * @param list the list + * @return the string + */ + public static String concatArray(List list) { + return concatArray(list, " "); + } + + /** + * Concat array. + * + * @param list the list + * @param delimiter the delimiter + * @return the string + */ + public static String concatArray(List list, String delimiter) { + + if (list == null || list.size() == 0) { + return ""; + } + + StringBuilder result = new StringBuilder(64); + + boolean firstValue = true; + + for (String item : list) { + + if (firstValue) { + result.append(item); + firstValue = false; + } else { + result.append(delimiter).append(item); + } + + } + + return result.toString(); + + } + + /** + * Concat array. + * + * @param values the values + * @return the string + */ + public static String concatArray(String[] values) { + + if (values == null || values.length == 0) { + return ""; + } + + StringBuilder result = new StringBuilder(64); + + boolean firstValue = true; + + for (String item : values) { + + if (firstValue) { + result.append(item); + firstValue = false; + } else { + result.append(".").append(item); + } + + } + + return result.toString(); + + } + + /** + * Builds the entity resource key. + * + * @param entityType the entity type + * @param resourceId the resource id + * @return the string + */ + public static String buildEntityResourceKey(String entityType, String resourceId) { + return String.format(ENTITY_RESOURCE_KEY_FORMAT, entityType, resourceId); + } + + /** + * Extract resource id from link. + * + * @param link the link + * @return the string + */ + public static String extractResourceIdFromLink(String link) { + + if (link == null) { + return null; + } + + int linkLength = link.length(); + if (linkLength == 0) { + return null; + } + + /* + * if the last character != / then we need to change the lastIndex position + */ + + int startIndex = 0; + String resourceId = null; + if ("/".equals(link.substring(linkLength - 1))) { + // Use-case: + // https://aai-ext1.test.att.com:9292/aai/v7/business/customers/customer/1607_20160524Func_Ak1_01/service-subscriptions/service-subscription/uCPE-VMS/ + startIndex = link.lastIndexOf("/", linkLength - 2); + resourceId = link.substring(startIndex + 1, linkLength - 1); + } else { + // Use-case: + // https://aai-ext1.test.att.com:9292/aai/v7/business/customers/customer/1607_20160524Func_Ak1_01/service-subscriptions/service-subscription/uCPE-VMS + startIndex = link.lastIndexOf("/"); + resourceId = link.substring(startIndex + 1, linkLength); + } + + String result = null; + + if (resourceId != null) { + try { + result = java.net.URLDecoder.decode(resourceId, "UTF-8"); + } catch (Exception exc) { + /* + * if there is a failure decoding the parameter we will just return the original value. + */ + result = resourceId; + } + } + + return result; + + } + + /** + * Gets the xml stream constant as str. + * + * @param value the value + * @return the xml stream constant as str + */ + public static String getXmlStreamConstantAsStr(int value) { + switch (value) { + case XMLStreamConstants.ATTRIBUTE: + return "ATTRIBUTE"; + case XMLStreamConstants.CDATA: + return "CDATA"; + case XMLStreamConstants.CHARACTERS: + return "CHARACTERS"; + case XMLStreamConstants.COMMENT: + return "COMMENT"; + case XMLStreamConstants.DTD: + return "DTD"; + case XMLStreamConstants.END_DOCUMENT: + return "END_DOCUMENT"; + case XMLStreamConstants.END_ELEMENT: + return "END_ELEMENT"; + case XMLStreamConstants.ENTITY_DECLARATION: + return "ENTITY_DECLARATION"; + case XMLStreamConstants.ENTITY_REFERENCE: + return "ENTITY_REFERENCE"; + case XMLStreamConstants.NAMESPACE: + return "NAMESPACE"; + case XMLStreamConstants.NOTATION_DECLARATION: + return "NOTATION_DECLARATION"; + case XMLStreamConstants.PROCESSING_INSTRUCTION: + return "PROCESSING_INSTRUCTION"; + case XMLStreamConstants.SPACE: + return "SPACE"; + case XMLStreamConstants.START_DOCUMENT: + return "START_DOCUMENT"; + case XMLStreamConstants.START_ELEMENT: + return "START_ELEMENT"; + + default: + return "Unknown(" + value + ")"; + } + } + + /** + * Convert object to json. + * + * @param object the object + * @param pretty the pretty + * @return the string + * @throws JsonProcessingException the json processing exception + */ + public static String convertObjectToJson(Object object, boolean pretty) + throws JsonProcessingException { + ObjectWriter ow = null; + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + + if (pretty) { + ow = mapper.writer().withDefaultPrettyPrinter(); + + } else { + ow = mapper.writer(); + } + + return ow.writeValueAsString(object); + } + + /** + * Convert json str to json node. + * + * @param jsonStr the json str + * @return the json node + * @throws IOException Signals that an I/O exception has occurred. + */ + public static JsonNode convertJsonStrToJsonNode(String jsonStr) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + if (jsonStr == null || jsonStr.length() == 0) { + return null; + } + + return mapper.readTree(jsonStr); + } + + /** + * Convert object to xml. + * + * @param object the object + * @return the string + * @throws JsonProcessingException the json processing exception + */ + public static String convertObjectToXml(Object object) throws JsonProcessingException { + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + String jsonOutput = ow.writeValueAsString(object); + + if (jsonOutput == null) { + return null; + } + + return JsonXmlConverter.convertJsontoXml(jsonOutput); + + } + + /** + * Extract objects by key. + * + * @param node the node + * @param searchKey the search key + * @param foundObjects the found objects + */ + public static void extractObjectsByKey(JsonNode node, String searchKey, + Collection foundObjects) { + + if ( node == null ) { + return; + } + + if (node.isObject()) { + Iterator> nodeIterator = node.fields(); + + while (nodeIterator.hasNext()) { + Map.Entry entry = nodeIterator.next(); + if (!entry.getValue().isValueNode()) { + extractObjectsByKey(entry.getValue(), searchKey, foundObjects); + } + + String name = entry.getKey(); + if (name.equalsIgnoreCase(searchKey)) { + + JsonNode entryNode = entry.getValue(); + + if (entryNode.isArray()) { + + Iterator arrayItemsIterator = entryNode.elements(); + while (arrayItemsIterator.hasNext()) { + foundObjects.add(arrayItemsIterator.next()); + } + + } else { + foundObjects.add(entry.getValue()); + } + + + } + } + } else if (node.isArray()) { + Iterator arrayItemsIterator = node.elements(); + while (arrayItemsIterator.hasNext()) { + extractObjectsByKey(arrayItemsIterator.next(), searchKey, foundObjects); + } + + } + + } + + /** + * Convert array into list. + * + * @param node the node + * @param instances the instances + */ + public static void convertArrayIntoList(JsonNode node, Collection instances) { + + if (node.isArray()) { + Iterator arrayItemsIterator = node.elements(); + while (arrayItemsIterator.hasNext()) { + instances.add(arrayItemsIterator.next()); + } + + } else { + instances.add(node); + } + + } + + /** + * Extract field values from object. + * + * @param node the node + * @param attributesToExtract the attributes to extract + * @param fieldValues the field values + */ + public static void extractFieldValuesFromObject(JsonNode node, + Collection attributesToExtract, Collection fieldValues) { + + if (node == null) { + return; + } + + if (node.isObject()) { + + JsonNode valueNode = null; + + for (String attrToExtract : attributesToExtract) { + + valueNode = node.get(attrToExtract); + + if (valueNode != null) { + + if (valueNode.isValueNode()) { + fieldValues.add(valueNode.asText()); + } + } + } + } + } + + /** + * Extract field value from object. + * + * @param node the node + * @param fieldName the field name + * @return the string + */ + public static String extractFieldValueFromObject(JsonNode node, String fieldName) { + + if (node == null) { + return null; + } + + if (node.isObject()) { + + JsonNode valueNode = node.get(fieldName); + + if (valueNode != null) { + + if (valueNode.isValueNode()) { + return valueNode.asText(); + } + } + + } + return null; + + } + + /** + * Format timestamp. + * + * @param timestamp the timestamp + * @return the string + */ + public static String formatTimestamp(String timestamp) { + try { + SimpleDateFormat originalFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'"); + originalFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + Date toDate = originalFormat.parse(timestamp); + SimpleDateFormat newFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + newFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + return newFormat.format(toDate); + + } catch (ParseException pe) { + return timestamp; + } + } + + /** + * Gets the HttpRequest payload. + * + * @param request the request + * @return the body + * @throws IOException Signals that an I/O exception has occurred. + */ + public static String getBody(HttpServletRequest request) throws IOException { + + String body = null; + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = null; + + try { + InputStream inputStream = request.getInputStream(); + if (inputStream != null) { + bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + char[] charBuffer = new char[128]; + int bytesRead = -1; + while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { + stringBuilder.append(charBuffer, 0, bytesRead); + } + } else { + stringBuilder.append(""); + } + } catch (IOException ex) { + throw ex; + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException ex) { + throw ex; + } + } + } + + body = stringBuilder.toString(); + return body; + } + + /** + * The main method. + * + * @param args the arguments + * @throws ParseException the parse exception + */ + public static void main(String[] args) throws ParseException { + String date = "20170110T112312Z"; + SimpleDateFormat originalFormat = new SimpleDateFormat("yyyyMMdd'T'hhmmss'Z'"); + Date toDate = originalFormat.parse(date); + SimpleDateFormat newFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss'Z'"); + System.out.println(newFormat.format(toDate)); + + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/util/RawByteHelper.java b/src/main/java/org/openecomp/sparky/util/RawByteHelper.java new file mode 100644 index 0000000..f929acf --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/RawByteHelper.java @@ -0,0 +1,177 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +/** + * The Class RawByteHelper. + */ +public class RawByteHelper { + private static final byte[] HEX_CHAR = + new byte[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + /** + * Dump bytes. + * + * @param buffer the buffer + * @return the string + */ + /* + * TODO -> DOCUMENT ME! + * + * @param buffer DOCUMENT ME! + * + * @return DOCUMENT ME! + */ + public static String dumpBytes(byte[] buffer) { + if (buffer == null) { + return ""; + } + String newLine = System.getProperty("line.separator"); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < buffer.length; i++) { + if (i != 0 && i % 16 == 0) { + sb.append(newLine); + } + // sb.append("0x").append((char) (HEX_CHAR[(buffer[i] & 0x00F0) >> 4])).append((char) + // (HEX_CHAR[buffer[i] & 0x000F])).append(" "); + sb.append((char) (HEX_CHAR[(buffer[i] & 0x00F0) >> 4])) + .append((char) (HEX_CHAR[buffer[i] & 0x000F])).append(" "); + } + + return sb.toString(); + } + + // if you're trying to figure out why or's w/ FF's see: + /** + * Bytes to int. + * + * @param one the one + * @param two the two + * @param three the three + * @param four the four + * @return the int + */ + // http://www.darksleep.com/player/JavaAndUnsignedTypes.html + public static int bytesToInt(byte one, byte two, byte three, byte four) { + return (((0xFF & one) << 24) | ((0xFF & two) << 16) | ((0xFF & three) << 8) | ((0xFF & four))); + } + + /** + * Bytes to short. + * + * @param one the one + * @param two the two + * @return the short + */ + public static short bytesToShort(byte one, byte two) { + return (short) (((0xFF & one) << 8) | (0xFF & two)); + } + + /** + * First byte. + * + * @param num the num + * @return the byte + */ + // short helper functions + static byte firstByte(short num) { + return (byte) ((num >> 8) & 0xFF); + } + + /** + * First byte. + * + * @param num the num + * @return the byte + */ + // Int helper functions + static byte firstByte(int num) { + return (byte) ((num >> 24) & 0xFF); + } + + /** + * Second byte. + * + * @param num the num + * @return the byte + */ + static byte secondByte(short num) { + return (byte) (num & 0xFF); + } + + /** + * Second byte. + * + * @param num the num + * @return the byte + */ + static byte secondByte(int num) { + return (byte) ((num >> 16) & 0xFF); + } + + /** + * Third byte. + * + * @param num the num + * @return the byte + */ + static byte thirdByte(int num) { + return (byte) ((num >> 8) & 0xFF); + } + + /** + * Fourth byte. + * + * @param num the num + * @return the byte + */ + static byte fourthByte(int num) { + return (byte) (num & 0xFF); + } + + /** + * Int to byte. + * + * @param value the value + * @return the byte + */ + public static byte intToByte(int value) { + return fourthByte(value); + } + + /** + * Int to short. + * + * @param value the value + * @return the short + */ + public static short intToShort(int value) { + return (short) ((value & 0xFF00) | (value & 0xFF)); + } + +} + diff --git a/src/main/java/org/openecomp/sparky/util/ServletUtils.java b/src/main/java/org/openecomp/sparky/util/ServletUtils.java new file mode 100644 index 0000000..e56a98a --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/ServletUtils.java @@ -0,0 +1,164 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.http.HttpServletResponse; + +import org.openecomp.cl.api.Logger; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.dal.elasticsearch.SearchAdapter; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class ServletUtils. + */ +public class ServletUtils { + + /** + * Execute get query. + * + * @param logger the logger + * @param search the search + * @param response the response + * @param requestUrl the request url + * @return the operation result + * @throws Exception the exception + */ + public static OperationResult executeGetQuery(Logger logger, SearchAdapter search, + HttpServletResponse response, String requestUrl) throws Exception { + + OperationResult opResult = search.doGet(requestUrl, "application/json"); + + if (opResult.getResultCode() > 300) { + setServletResponse(logger, true, opResult.getResultCode(), response, opResult.getResult()); + } else { + response.setStatus(opResult.getResultCode()); + } + + return opResult; + + } + + /** + * Execute post query. + * + * @param logger the logger + * @param search the search + * @param response the response + * @param requestUrl the request url + * @param requestJsonPayload the request json payload + * @return the operation result + * @throws Exception the exception + */ + public static OperationResult executePostQuery(Logger logger, SearchAdapter search, + HttpServletResponse response, String requestUrl, String requestJsonPayload) throws Exception { + + OperationResult opResult = search.doPost(requestUrl, requestJsonPayload, "application/json"); + + if (opResult.getResultCode() > 300) { + setServletResponse(logger, true, opResult.getResultCode(), response, opResult.getResult()); + + } else { + response.setStatus(opResult.getResultCode()); + } + + return opResult; + } + + /** + * Handle search servlet errors. + * + * @param logger the logger + * @param errorMsg the error msg + * @param exc the exc + * @param response the response + * @throws IOException Signals that an I/O exception has occurred. + */ + public static void handleSearchServletErrors(Logger logger, String errorMsg, Exception exc, + HttpServletResponse response) throws IOException { + String errorLogMsg = (exc == null ? errorMsg : errorMsg + ". Error:" + + exc.getLocalizedMessage()); + logger.error(AaiUiMsgs.ERROR_GENERIC, errorLogMsg); + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(generateJsonErrorResponse(errorMsg)); + out.close(); + } + + /** + * Generate json error response. + * + * @param message the message + * @return the string + */ + public static String generateJsonErrorResponse(String message) { + return String.format("{ \"errorMessage\" : %s }", message); + } + + /** + * Sets the servlet response. + * + * @param logger the logger + * @param isError the is error + * @param responseCode the response code + * @param response the response + * @param postPayload the post payload + * @throws IOException Signals that an I/O exception has occurred. + */ + public static void setServletResponse(Logger logger, boolean isError, int responseCode, + HttpServletResponse response, String postPayload) throws IOException { + + if (isError) { + logger.error(AaiUiMsgs.ERROR_GENERIC, postPayload); + } + + response.setStatus(responseCode); + + if (postPayload != null) { + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(postPayload); + out.close(); + } + } + + /** + * Gets the full url. + * + * @param elasticConfig the elastic config + * @param resourceUrl the resource url + * @return the full url + */ + public static String getFullUrl(ElasticSearchConfig elasticConfig, String resourceUrl) { + final String host = elasticConfig.getIpAddress(); + final String port = elasticConfig.getHttpPort(); + return String.format("http://%s:%s%s", host, port, resourceUrl); + } +} diff --git a/src/main/java/org/openecomp/sparky/util/SuggestionsPermutation.java b/src/main/java/org/openecomp/sparky/util/SuggestionsPermutation.java new file mode 100644 index 0000000..876b2f4 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/SuggestionsPermutation.java @@ -0,0 +1,82 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.util; + +import java.util.ArrayList; +import java.util.List; + +public class SuggestionsPermutation { + + /* + * Will return all the unique combinations of the suggestions provided. + * The order of the permutation is not taken into account when computing + * the uniqueness. + * eg: A list of A,B,C,D will return + * [[A], [A, B, C, D], [A, C, D], [A, D], [B], [B, C, D], [B, D], [C], [C, D], [D]] + * + * @param list The list to create the unique permutations + * @return A Arraylist which contains a array list of all possible combinations + */ + @SuppressWarnings("serial") + public ArrayList> getSuggestionsPermutation(List list) { + List statusList = new ArrayList<>(list); + List dupStatusList; + ArrayList> uniqueList = new ArrayList>(); + int mainLoopIndexCounter = 0; + for (String status : statusList) { + // Add the single entity subset + uniqueList.add(new ArrayList() { + { + add(status); + } + }); + // Remove all the elements to left till the current index + dupStatusList = truncateListUntill(statusList, mainLoopIndexCounter); + + while (dupStatusList.size() > 0) { + ArrayList suggListInIterate= new ArrayList<>(); + suggListInIterate.add(status); + for (String dupStatus : dupStatusList) { + suggListInIterate.add(dupStatus); + } + uniqueList.add(suggListInIterate); + dupStatusList.remove(0); + } + mainLoopIndexCounter++; + } + return uniqueList; + + } + + private List truncateListUntill(List lists, int index) { + List truncatedList = new ArrayList<>(lists); + int counter = 0; + while (counter <= index) { + truncatedList.remove(0); + counter++; + } + return truncatedList; + } +} diff --git a/src/main/java/org/openecomp/sparky/util/TreeWalker.java b/src/main/java/org/openecomp/sparky/util/TreeWalker.java new file mode 100644 index 0000000..c9a804d --- /dev/null +++ b/src/main/java/org/openecomp/sparky/util/TreeWalker.java @@ -0,0 +1,137 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.util; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * The Class TreeWalker. + */ +public class TreeWalker { + + /** + * Convert json to node. + * + * @param json the json + * @return the json node + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + public JsonNode convertJsonToNode(String json) throws JsonProcessingException, IOException { + ObjectMapper mapper = new ObjectMapper(); + + if (json == null) { + return null; + } + + return mapper.readTree(json); + + } + + /** + * Walk tree. + * + * @param paths the paths + * @param root the root + */ + public void walkTree(List paths, JsonNode root) { + walker(paths, null, root); + } + + /** + * Walker. + * + * @param paths the paths + * @param nodename the nodename + * @param node the node + */ + private void walker(List paths, String nodename, JsonNode node) { + + if (node == null) { + return; + } + + /* + * if ( nodename != null ) { paths.add(nodename); } + */ + + // System.out.println("path: " + nameToPrint); + if (node.isObject()) { + Iterator> iterator = node.fields(); + + ArrayList> nodesList = Lists.newArrayList(iterator); + // System.out.println("Walk Tree - root:" + node + ", elements + // keys:" + nodesList); + + if (nodesList.isEmpty()) { + + if (nodename != null) { + paths.add(nodename); + } + + } else { + + for (Map.Entry nodEntry : nodesList) { + String name = nodEntry.getKey(); + JsonNode newNode = nodEntry.getValue(); + + if (newNode.isValueNode()) { + if (nodename == null) { + paths.add(name + "=" + newNode.asText()); + } else { + paths.add(nodename + "." + name + "=" + newNode.asText()); + } + } else { + + if (nodename == null) { + walker(paths, name, newNode); + } else { + walker(paths, nodename + "." + name, newNode); + } + } + + } + } + } else if (node.isArray()) { + Iterator arrayItemsIterator = node.elements(); + ArrayList arrayItemsList = Lists.newArrayList(arrayItemsIterator); + for (JsonNode arrayNode : arrayItemsList) { + walker(paths, nodename, arrayNode); + } + } else if (node.isValueNode()) { + paths.add(nodename + "=" + node.asText()); + } + } +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/EntityTypeAggregation.java b/src/main/java/org/openecomp/sparky/viewandinspect/EntityTypeAggregation.java new file mode 100644 index 0000000..a99ebeb --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/EntityTypeAggregation.java @@ -0,0 +1,94 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.openecomp.sparky.util.NodeUtils; + +/** + * The Class EntityTypeAggregation. + */ +public class EntityTypeAggregation { + + private ConcurrentHashMap counters; + + /** + * Instantiates a new entity type aggregation. + */ + public EntityTypeAggregation() { + counters = new ConcurrentHashMap(); + } + + /** + * Peg counter. + * + * @param counterName the counter name + */ + public void pegCounter(String counterName) { + counters.putIfAbsent(counterName, new AtomicInteger(0)); + counters.get(counterName).incrementAndGet(); + } + + public ConcurrentHashMap getCounters() { + return counters; + } + + /** + * The main method. + * + * @param args the arguments + * @throws JsonProcessingException the json processing exception + */ + public static void main(String[] args) throws JsonProcessingException { + // TODO Auto-generated method stub + + EntityTypeAggregation eta = new EntityTypeAggregation(); + + eta.pegCounter("c1"); + eta.pegCounter("c1"); + eta.pegCounter("c1"); + + eta.pegCounter("x2"); + eta.pegCounter("x2"); + eta.pegCounter("x2"); + eta.pegCounter("x2"); + + eta.pegCounter("z2"); + eta.pegCounter("z2"); + eta.pegCounter("z2"); + eta.pegCounter("z2"); + eta.pegCounter("z2"); + eta.pegCounter("z2"); + + System.out.println(NodeUtils.convertObjectToJson(eta, true)); + + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/config/TierSupportUiConstants.java b/src/main/java/org/openecomp/sparky/viewandinspect/config/TierSupportUiConstants.java new file mode 100644 index 0000000..4da07b9 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/config/TierSupportUiConstants.java @@ -0,0 +1,90 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.config; + +/** + * The Class TierSupportUiConstants. + */ +public class TierSupportUiConstants { + + public static String APP_NAME = "AAIUI"; + + /** Default to unix file separator if system property file.separator is null */ + public static final String FILESEP = + (System.getProperty("file.separator") == null) ? "/" : System.getProperty("file.separator"); + + public static String CONFIG_HOME = System.getProperty("CONFIG_HOME") + FILESEP; + public static String AJSC_HOME = System.getProperty("AJSC_HOME") + FILESEP; + public static String CONFIG_ROOT_LOCATION = AJSC_HOME + "bundleconfig" + FILESEP + "etc" + FILESEP; + public static String STATIC_CONFIG_APP_LOCATION = CONFIG_ROOT_LOCATION + "appprops" + FILESEP; + public static String DYNAMIC_CONFIG_APP_LOCATION = CONFIG_HOME; + + public static String CONFIG_OXM_LOCATION = CONFIG_HOME + "model" + FILESEP; + + public static String CONFIG_AUTH_LOCATION = CONFIG_HOME + "auth" + FILESEP; + + public static String HOST = "host"; + public static String PORT = "port"; + public static String RETRIES = "numRequestRetries"; + public static String RESOURCE_VERSION = "resource-version"; + public static String URI = "URI"; + + public static String USERS_FILE_LOCATION = DYNAMIC_CONFIG_APP_LOCATION + "users.config"; + public static String ROLES_FILE_LOCATION = DYNAMIC_CONFIG_APP_LOCATION + "roles.config"; + public static String PORTAL_AUTHENTICATION_FILE_LOCATION = DYNAMIC_CONFIG_APP_LOCATION + "portal" + FILESEP + "portal-authentication.properties"; + + public static final String ES_SUGGEST_API = "_suggest"; + public static final String ES_COUNT_API = "_count"; + public static final String ES_SEARCH_API = "_search"; + + public static final String ENTITY_AUTO_SUGGEST_INDEX_NAME_DEFAULT = + "entityautosuggestindex-localhost"; + public static final String ENTITY_AUTO_SUGGEST_SETTINGS_FILE_DEFAULT = + "/etc/autoSuggestSettings.json"; + public static final String ENTITY_AUTO_SUGGEST_MAPPINGS_FILE_DEFAULT = + "/etc/autoSuggestMappings.json"; + public static final String ENTITY_DYNAMIC_MAPPINGS_FILE_DEFAULT = + "/etc/dynamicMappings.json"; + + // JUnit testing synchronizer.properties file + public static String TEST_CONFIG_FILE = + System.getProperty("user.dir") + FILESEP + "bundleconfig-local" + FILESEP + "etc" + FILESEP + + "appprops" + FILESEP + "synchronizer.properties"; + + // Injected Attributes + public static String URI_ATTR_NAME = "uri"; + + public static final String URI_VERSION_REGEX_PATTERN = "aai/v[\\d]+/"; + + public static final String getConfigPath(String configFile){ + return AJSC_HOME + FILESEP + configFile; + } + + public static final String getAggregationIndexName(String entityType){ + return "aggregate_" + entityType + "_index"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/config/VisualizationConfig.java b/src/main/java/org/openecomp/sparky/viewandinspect/config/VisualizationConfig.java new file mode 100644 index 0000000..3f0a5b5 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/config/VisualizationConfig.java @@ -0,0 +1,199 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.config; + +import java.util.Properties; + +import org.openecomp.sparky.util.ConfigHelper; + +/** + * The Class VisualizationConfig. + */ +public class VisualizationConfig { + + private int maxSelfLinkTraversalDepth; + + private boolean visualizationDebugEnabled; + + private String aaiEntityNodeDescriptors; + + private String generalNodeClassName; + + private String searchNodeClassName; + + private String selectedSearchedNodeClassName; + + private String entityTypesToSummarize; + private String vnfEntityTypes; + + private boolean makeAllNeighborsBidirectional; + + private static VisualizationConfig instance; + + public static VisualizationConfig getConfig() { + + if (instance == null) { + instance = new VisualizationConfig(); + } + + return instance; + + } + + /** + * Instantiates a new visualization config. + */ + public VisualizationConfig() { + + Properties visualizationProps = + ConfigHelper.loadConfigFromExplicitPath(TierSupportUiConstants.STATIC_CONFIG_APP_LOCATION + + TierSupportUiConstants.FILESEP + "visualization.properties"); + + maxSelfLinkTraversalDepth = + Integer.parseInt(visualizationProps.getProperty("maxSelfLinkTraversalDepth", "2")); + visualizationDebugEnabled = + Boolean.parseBoolean(visualizationProps.getProperty("visualizationDebugEnabled", "false")); + aaiEntityNodeDescriptors = visualizationProps.getProperty("aaiEntityNodeDescriptors", null); + generalNodeClassName = + visualizationProps.getProperty("generalNodeClassName", "unknownClassName"); + searchNodeClassName = + visualizationProps.getProperty("searchedNodeClassName", "unknownClassName"); + selectedSearchedNodeClassName = + visualizationProps.getProperty("selectedSearchedNodeClassName", "unknownClassName"); + + entityTypesToSummarize = visualizationProps.getProperty("entityTypesToSummarize", + "customer,service-instance,complex,pserver,vserver,vnf"); + + vnfEntityTypes = visualizationProps.getProperty("vnfEntityTypes", "generic-vnf,newvce,vce,vpe"); + + makeAllNeighborsBidirectional = Boolean + .parseBoolean(visualizationProps.getProperty("makeAllNeighborsBidirectional", "false")); + + } + + + + /** + * Make all neighbors bidirectional. + * + * @return true, if successful + */ + public boolean makeAllNeighborsBidirectional() { + return makeAllNeighborsBidirectional; + } + + public void setMakeAllNeighborsBidirectional(boolean makeAllNeighborsBidirectional) { + this.makeAllNeighborsBidirectional = makeAllNeighborsBidirectional; + } + + public String getSelectedSearchedNodeClassName() { + return selectedSearchedNodeClassName; + } + + public void setSelectedSearchedNodeClassName(String selectedSearchedNodeClassName) { + this.selectedSearchedNodeClassName = selectedSearchedNodeClassName; + } + + public String getGeneralNodeClassName() { + return generalNodeClassName; + } + + public void setGeneralNodeClassName(String generalNodeClassName) { + this.generalNodeClassName = generalNodeClassName; + } + + public String getSearchNodeClassName() { + return searchNodeClassName; + } + + public void setSearchNodeClassName(String searchNodeClassName) { + this.searchNodeClassName = searchNodeClassName; + } + + public String getAaiEntityNodeDescriptors() { + return aaiEntityNodeDescriptors; + } + + public void setAaiEntityNodeDescriptors(String aaiEntityNodeDescriptors) { + this.aaiEntityNodeDescriptors = aaiEntityNodeDescriptors; + } + + public boolean isVisualizationDebugEnabled() { + return visualizationDebugEnabled; + } + + public void setVisualizationDebugEnabled(boolean visualizationDebugEnabled) { + this.visualizationDebugEnabled = visualizationDebugEnabled; + } + + public void setMaxSelfLinkTraversalDepth(int maxSelfLinkTraversalDepth) { + this.maxSelfLinkTraversalDepth = maxSelfLinkTraversalDepth; + } + + public int getMaxSelfLinkTraversalDepth() { + return maxSelfLinkTraversalDepth; + } + + public String getEntityTypesToSummarize() { + return entityTypesToSummarize; + } + + public void setEntityTypesToSummarize(String entityTypesToSummarize) { + this.entityTypesToSummarize = entityTypesToSummarize; + } + + public String getVnfEntityTypes() { + return vnfEntityTypes; + } + + public void setVnfEntityTypes(String vnfEntityTypes) { + this.vnfEntityTypes = vnfEntityTypes; + } + + @Override + public String toString() { + return "VisualizationConfig [maxSelfLinkTraversalDepth=" + maxSelfLinkTraversalDepth + + ", visualizationDebugEnabled=" + visualizationDebugEnabled + ", " + + (aaiEntityNodeDescriptors != null + ? "aaiEntityNodeDescriptors=" + aaiEntityNodeDescriptors + ", " : "") + + (generalNodeClassName != null ? "generalNodeClassName=" + generalNodeClassName + ", " + : "") + + (searchNodeClassName != null ? "searchNodeClassName=" + searchNodeClassName + ", " : "") + + (selectedSearchedNodeClassName != null + ? "selectedSearchedNodeClassName=" + selectedSearchedNodeClassName + ", " : "") + + (entityTypesToSummarize != null + ? "entityTypesToSummarize=" + entityTypesToSummarize + ", " : "") + + (vnfEntityTypes != null ? "vnfEntityTypes=" + vnfEntityTypes + ", " : "") + + "makeAllNeighborsBidirectional=" + makeAllNeighborsBidirectional + "]"; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + + + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/ActiveInventoryNode.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/ActiveInventoryNode.java new file mode 100644 index 0000000..db79ef5 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/ActiveInventoryNode.java @@ -0,0 +1,778 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.viewandinspect.config.VisualizationConfig; +import org.openecomp.sparky.viewandinspect.enumeration.NodeProcessingAction; +import org.openecomp.sparky.viewandinspect.enumeration.NodeProcessingState; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The Class ActiveInventoryNode. + */ +public class ActiveInventoryNode { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger( + ActiveInventoryNode.class); + private static final String URIRegexPattern = "aai/v[\\d]/"; + + public static final int DEFAULT_INIT_NODE_DEPTH = 1000; + + private String nodeId; + private String selfLink; + + private boolean isRootNode; + private ConcurrentLinkedDeque inboundNeighbors; + private ConcurrentLinkedDeque outboundNeighbors; + private List complexGroups; + private List relationshipLists; + private int nodeDepth; + private OperationResult opResult; + + + private boolean processingErrorOccurred; + private List errorCauses; + private boolean selflinkRetrievalFailure; + private NodeProcessingState state; + + private boolean processedNeighbors; + + private boolean selfLinkPendingResolve; + + /* + * I think we shouldn't be using this crutch flags. If these things are meant + * to represent the current state of the node, then they should be legitimate + * state transitions. + */ + + private boolean selfLinkDeterminationPending; + + private AtomicBoolean selfLinkProcessed; + + private OxmModelLoader oxmModelLoader; + private VisualizationConfig visualizationConfig; + + private String entityType; + private String primaryKeyName; + private String primaryKeyValue; + + private boolean nodeIssue; + private boolean ignoredByFilter; + + private boolean resolvedSelfLink; + + private Map properties; + private ArrayList queryParams; + + private ObjectMapper mapper; + + /** + * Instantiates a new active inventory node. + */ + public ActiveInventoryNode() { + this(null); + } + + /** + * Instantiates a new active inventory node. + * + * @param key the key + */ + public ActiveInventoryNode(String key) { + this.nodeId = null; + this.entityType = null; + this.selfLink = null; + this.properties = new HashMap(); + this.processingErrorOccurred = false; + this.errorCauses = new ArrayList(); + this.selflinkRetrievalFailure = false; + this.nodeIssue = false; + this.state = NodeProcessingState.INIT; + this.selfLinkPendingResolve = false; + this.selfLinkDeterminationPending = false; + + selfLinkProcessed = new AtomicBoolean(Boolean.FALSE); + oxmModelLoader = null; + visualizationConfig = null; + + isRootNode = false; + inboundNeighbors = new ConcurrentLinkedDeque(); + outboundNeighbors = new ConcurrentLinkedDeque(); + complexGroups = new ArrayList(); + relationshipLists = new ArrayList(); + nodeDepth = DEFAULT_INIT_NODE_DEPTH; + queryParams = new ArrayList(); + + mapper = new ObjectMapper(); + + processedNeighbors = false; + resolvedSelfLink = false; + + + } + + public void clearQueryParams() { + queryParams.clear(); + } + + public void addQueryParam(String queryParam) { + if ( queryParam!= null) { + if( !queryParams.contains(queryParam)) { + queryParams.add(queryParam); + } + } + } + + public void addQueryParams(Collection params) { + + if (params != null & params.size() > 0) { + + for (String param : params) { + addQueryParam(param); + } + } + } + + + public List getQueryParams() { + return queryParams; + } + + public void setSelfLinkDeterminationPending(boolean selfLinkDeterminationPending) { + this.selfLinkDeterminationPending = selfLinkDeterminationPending; + } + + public boolean isSelfLinkDeterminationPending() { + return selfLinkDeterminationPending; + } + + public NodeProcessingState getState() { + return state; + } + + public List getComplexGroups() { + return complexGroups; + } + + public List getRelationshipLists() { + return relationshipLists; + } + + public OperationResult getOpResult() { + return opResult; + } + + public void setOpResult(OperationResult opResult) { + this.opResult = opResult; + } + + public String getPrimaryKeyName() { + return primaryKeyName; + } + + /** + * Gets the visualization config. + * + * @return the visualization config + */ + public VisualizationConfig getvisualizationConfig() { + return visualizationConfig; + } + + public int getNodeDepth() { + return nodeDepth; + } + + public void setNodeDepth(int nodeDepth) { + this.nodeDepth = nodeDepth; + } + + /** + * Sets the visualization config. + * + * @param visualizationConfig the new visualization config + */ + public void setvisualizationConfig(VisualizationConfig visualizationConfig) { + this.visualizationConfig = visualizationConfig; + } + + public OxmModelLoader getOxmModelLoader() { + return oxmModelLoader; + } + + public void setPrimaryKeyName(String primaryKeyName) { + this.primaryKeyName = primaryKeyName; + } + + public String getPrimaryKeyValue() { + return primaryKeyValue; + } + + public void setPrimaryKeyValue(String primaryKeyValue) { + this.primaryKeyValue = primaryKeyValue; + } + + public boolean isNodeIssue() { + return nodeIssue; + } + + public boolean isIgnoredByFilter() { + return ignoredByFilter; + } + + public void setIgnoredByFilter(boolean ignoredByFilter) { + this.ignoredByFilter = ignoredByFilter; + } + + public void setNodeIssue(boolean nodeIssue) { + this.nodeIssue = nodeIssue; + } + + /** + * Checks for processed neighbors. + * + * @return true, if successful + */ + public boolean hasProcessedNeighbors() { + return processedNeighbors; + } + + public void setProcessedNeighbors(boolean processedNeighbors) { + this.processedNeighbors = processedNeighbors; + } + + /** + * Checks for resolved self link. + * + * @return true, if successful + */ + public boolean hasResolvedSelfLink() { + return resolvedSelfLink; + } + + public void setResolvedSelfLink(boolean resolvedSelfLink) { + this.resolvedSelfLink = resolvedSelfLink; + } + + /** + * Checks for neighbors. + * + * @return true, if successful + */ + public boolean hasNeighbors() { + return (inboundNeighbors.size() > 0 || outboundNeighbors.size() > 0); + } + + /** + * Adds the inbound neighbor. + * + * @param nodeId the node id + */ + public void addInboundNeighbor(String nodeId) { + + if (nodeId == null) { + return; + } + + if (!inboundNeighbors.contains(nodeId)) { + inboundNeighbors.add(nodeId); + } + + } + + /** + * Adds the outbound neighbor. + * + * @param nodeId the node id + */ + public void addOutboundNeighbor(String nodeId) { + + if (nodeId == null) { + return; + } + + if (!outboundNeighbors.contains(nodeId)) { + outboundNeighbors.add(nodeId); + } + + } + + public boolean isAtMaxDepth() { + return (nodeDepth >= VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()); + } + + public ConcurrentLinkedDeque getInboundNeighbors() { + return inboundNeighbors; + } + + public void setInboundNeighbors(ConcurrentLinkedDeque inboundNeighbors) { + this.inboundNeighbors = inboundNeighbors; + } + + public Collection getOutboundNeighbors() { + List result = new ArrayList(); + + Iterator neighborIterator = outboundNeighbors.iterator(); + + while (neighborIterator.hasNext()) { + result.add(neighborIterator.next()); + } + + return result; + } + + /** + * Change depth. + * + * @param newDepth the new depth + * @return true, if successful + */ + public boolean changeDepth(int newDepth) { + + boolean nodeDepthWasChanged = false; + + if (newDepth < nodeDepth) { + LOG.info(AaiUiMsgs.ACTIVE_INV_NODE_CHANGE_DEPTH, nodeId, + String.valueOf(this.nodeDepth), String.valueOf(newDepth)); + this.nodeDepth = newDepth; + nodeDepthWasChanged = true; + } + + return nodeDepthWasChanged; + + } + + public void setOutboundNeighbors(ConcurrentLinkedDeque outboundNeighbors) { + this.outboundNeighbors = outboundNeighbors; + } + + public boolean isRootNode() { + return isRootNode; + } + + public void setRootNode(boolean isRootNode) { + this.isRootNode = isRootNode; + } + + /** + * Change state. + * + * @param newState the new state + * @param action the action + */ + public void changeState(NodeProcessingState newState, NodeProcessingAction action) { + /* + * NodeId may be null depending on the current node life-cycle state + */ + + if (getNodeId() != null) { + LOG.info(AaiUiMsgs.ACTIVE_INV_NODE_CHANGE_STATE, state.toString(), newState.toString(), action.toString()); + } else { + LOG.info(AaiUiMsgs.ACTIVE_INV_NODE_CHANGE_STATE_NO_NODE_ID, state.toString(), newState.toString(), action.toString()); + } + this.state = newState; + } + + public boolean isSelfLinkPendingResolve() { + return selfLinkPendingResolve; + } + + public void setSelfLinkPendingResolve(boolean selfLinkPendingResolve) { + this.selfLinkPendingResolve = selfLinkPendingResolve; + } + + public boolean isSelflinkRetrievalFailure() { + return selflinkRetrievalFailure; + } + + public void setSelflinkRetrievalFailure(boolean selflinkRetrievalFailure) { + this.selflinkRetrievalFailure = selflinkRetrievalFailure; + } + + public void setOxmModelLoader(OxmModelLoader loader) { + this.oxmModelLoader = loader; + } + + public boolean getSelfLinkProcessed() { + return selfLinkProcessed.get(); + } + + public void setSelfLinkProcessed(boolean selfLinkProcessed) { + this.selfLinkProcessed.set(selfLinkProcessed); + } + + public boolean isDirectSelfLink() { + // https://aai-int1.test.att.com:8443/aai/v8/resources/id/2458124400 + return isDirectSelfLink(this.selfLink); + } + + /** + * Checks if is direct self link. + * + * @param link the link + * @return true, if is direct self link + */ + public static boolean isDirectSelfLink(String link) { + // https://aai-int1.test.att.com:8443/aai/v8/resources/id/2458124400 + + if (link == null) { + return false; + } + + return link.contains("/resources/id/"); + + } + + public Map getProperties() { + return properties; + } + + /** + * Adds the error cause. + * + * @param error the error + */ + public void addErrorCause(String error) { + if (!errorCauses.contains(error)) { + errorCauses.add(error); + } + } + + /** + * Adds the property. + * + * @param key the key + * @param value the value + */ + public void addProperty(String key, String value) { + properties.put(key, value); + } + + public boolean isProcessingErrorOccurred() { + return processingErrorOccurred; + } + + public void setProcessingErrorOccurred(boolean processingErrorOccurred) { + this.processingErrorOccurred = processingErrorOccurred; + } + + public String getNodeId() { + return nodeId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public String getSelfLink() { + return selfLink; + } + + /** + * Calculate edit attribute uri. + * + * @param link the link + * @return the string + */ + public String calculateEditAttributeUri(String link) { + String uri = null; + Pattern pattern = Pattern.compile(URIRegexPattern); + Matcher matcher = pattern.matcher(link); + if (matcher.find()) { + uri = link.substring(matcher.end()); + } + return uri; + } + + /** + * Analyze self link relationship list. + * + * @param jsonResult the json result + * @return the relationship list + */ + private RelationshipList analyzeSelfLinkRelationshipList(String jsonResult) { + + + RelationshipList relationshipList = null; + + try { + relationshipList = mapper.readValue(jsonResult, RelationshipList.class); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SELF_LINK_RELATIONSHIP_LIST_ERROR, exc.toString()); + } + + return relationshipList; + } + + /** + * Adds the relationship list. + * + * @param relationshipList the relationship list + */ + public void addRelationshipList(RelationshipList relationshipList) { + + if (!relationshipLists.contains(relationshipList)) { + relationshipLists.add(relationshipList); + } + + } + + /** + * Process pathed self link response. + * + * @param selfLinkJsonResponse the self link json response + * @param startNodeType the start node type + * @param startNodeResourceKey the start node resource key + */ + public void processPathedSelfLinkResponse(String selfLinkJsonResponse, String startNodeType, + String startNodeResourceKey) { + + if (selfLinkJsonResponse == null || selfLinkJsonResponse.length() == 0) { + LOG.error(AaiUiMsgs.SELF_LINK_NULL_EMPTY_RESPONSE); + return; + } + + try { + JsonNode jsonNode = mapper.readValue(selfLinkJsonResponse, JsonNode.class); + + Iterator> fieldNames = jsonNode.fields(); + Entry field = null; + + while (fieldNames.hasNext()) { + + field = fieldNames.next(); + + /* + * Is there a way to tell if the field is an aggregate or an atomic value? This is where our + * flattening code needs to live + */ + + String fieldName = field.getKey(); + + if ("relationship-list".equals(fieldName)) { + + /* + * Parse the relationship list like we were doing before, so we can determine whether or + * not to keep it or traverse it after we have performed the evaluative node depth logic. + */ + RelationshipList relationshipList = + analyzeSelfLinkRelationshipList(field.getValue().toString()); + + if (relationshipList != null) { + this.relationshipLists.add(relationshipList); + } else { + LOG.info(AaiUiMsgs.NO_RELATIONSHIP_DISCOVERED, nodeId); + } + } else { + JsonNode nodeValue = field.getValue(); + + if (nodeValue != null && nodeValue.isValueNode()) { + + /* + * before we blindly add the fieldName and value to our property set, let's do one more + * check to see if the field name is an entity type. If it is, then our complex + * attribute processing code will pick it up and process it instead, but this is + * probably more likely just for array node types, but we'll see. + */ + + if (oxmModelLoader.getEntityDescriptor(fieldName) == null) { + /* + * this is no an entity type as far as we can tell, so we can add it to our property + * set. + */ + + addProperty(fieldName, nodeValue.asText()); + + } + + } else { + + if (nodeValue.isArray()) { + + /* + * make sure array entity-type collection is not an entityType before adding it to the + * property set. The expetation is that it will be added the visualization through a + * complex group or relationship. + */ + + if (oxmModelLoader.getEntityDescriptor(field.getKey()) == null) { + /* + * this is no an entity type as far as we can tell, so we can add it to our property + * set. + */ + + addProperty(field.getKey(), nodeValue.toString()); + + } + + } else { + + complexGroups.add(nodeValue); + + } + + } + + } + + } + + } catch (IOException exc) { + LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, "POJO", exc.getLocalizedMessage()); + this.setProcessingErrorOccurred(true); + this.addErrorCause( + "An error occurred while converting JSON into POJO = " + exc.getLocalizedMessage()); + } + + } + + public void setSelfLink(String selfLink) { + this.selfLink = selfLink; + } + + /** + * Adds the complex group. + * + * @param complexGroup the complex group + */ + public void addComplexGroup(JsonNode complexGroup) { + + if (!complexGroups.contains(complexGroup)) { + complexGroups.add(complexGroup); + } + + } + + /** + * Gets the padding. + * + * @param level the level + * @param paddingString the padding string + * @return the padding + */ + private static String getPadding(int level, String paddingString) { + StringBuilder sb = new StringBuilder(32); + for (int x = 0; x < level; x++) { + sb.append(paddingString); + } + return sb.toString(); + } + + /** + * Dump node tree. + * + * @param showProperties the show properties + * @return the string + */ + public String dumpNodeTree(boolean showProperties) { + return dumpNodeTree(0, showProperties); + } + + /** + * Dump node tree. + * + * @param level the level + * @param showProperties the show properties + * @return the string + */ + private String dumpNodeTree(int level, boolean showProperties) { + StringBuilder sb = new StringBuilder(128); + String padding = getPadding(level, " "); + + sb.append(padding + " -> " + getNodeId() + "]").append("\n"); + sb.append(padding + " -> primaryKeyName = " + primaryKeyName + "]").append("\n"); + sb.append(padding + " -> primaryKeyValue = " + primaryKeyValue + "]").append("\n"); + sb.append(padding + " -> entityType = " + entityType + "]").append("\n"); + + if (showProperties) { + Set> entries = properties.entrySet(); + for (Entry entry : entries) { + sb.append( + padding + " ----> " + String.format("[ %s => %s ]", entry.getKey(), entry.getValue())) + .append("\n"); + } + } + + sb.append(padding + " ----> " + String.format("[ selfLink => %s ]", getSelfLink())) + .append("\n"); + + sb.append("\n").append(padding + " ----> Inbound Neighbors:").append("\n"); + + for (String inboundNeighbor : inboundNeighbors) { + sb.append("\n").append(inboundNeighbor.toString()); + } + + sb.append(padding + " ----> Outbound Neighbors:").append("\n"); + sb.append("\n").append(padding + " ----> Outbound Neighbors:").append("\n"); + + for (String outboundNeighbor : outboundNeighbors) { + sb.append("\n").append(outboundNeighbor.toString()); + } + + return sb.toString(); + + } + + public String getProcessingErrorCauses() { + + StringBuilder sb = new StringBuilder(128); + + for (String c : this.errorCauses) { + sb.append(c).append("\n"); + } + + return sb.toString(); + } +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/D3VisualizationOutput.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/D3VisualizationOutput.java new file mode 100644 index 0000000..c201408 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/D3VisualizationOutput.java @@ -0,0 +1,132 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +import java.util.ArrayList; +import java.util.List; + +/** + * The Class D3VisualizationOutput. + */ +public class D3VisualizationOutput { + + public GraphMeta graphMeta; + public List nodes; + public List links; + public InlineMessage inlineMessage; + + /** + * Instantiates a new d 3 visualization output. + */ + public D3VisualizationOutput() { + nodes = new ArrayList(); + links = new ArrayList(); + inlineMessage = null; + } + + public GraphMeta getGraphMeta() { + return graphMeta; + } + + /** + * Peg counter. + * + * @param counterName the counter name + */ + public void pegCounter(String counterName) { + graphMeta.pegCounter(counterName); + } + + public void setGraphMeta(GraphMeta graphMeta) { + this.graphMeta = graphMeta; + } + + /** + * Adds the nodes. + * + * @param nodes the nodes + */ + public void addNodes(List nodes) { + this.nodes.addAll(nodes); + } + + /** + * Adds the links. + * + * @param links the links + */ + public void addLinks(List links) { + this.links.addAll(links); + } + + public InlineMessage getInlineMessage() { + return inlineMessage; + } + + public void setInlineMessage(InlineMessage inlineMessage) { + this.inlineMessage = inlineMessage; + } + + /** + * The main method. + * + * @param args the arguments + * @throws JsonProcessingException the json processing exception + */ + public static final void main(String[] args) throws JsonProcessingException { + + ActiveInventoryNode pserverAin = new ActiveInventoryNode(); + pserverAin.setNodeId("pserver.76786asd87asgd"); + JsonNode pserver = new JsonNode(pserverAin); + + List nodes = new ArrayList(); + nodes.add(pserver); + + JsonNodeLink l1 = new JsonNodeLink(); + l1.setSource(pserverAin.getNodeId()); + l1.setTarget(pserverAin.getNodeId()); + l1.setId(l1.getSource() + "_" + l1.getTarget()); + + List links = new ArrayList(); + links.add(l1); + + D3VisualizationOutput output = new D3VisualizationOutput(); + output.addNodes(nodes); + output.addLinks(links); + + + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + String json = ow.writeValueAsString(output); + + System.out.println(json); + + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/EntityEntry.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/EntityEntry.java new file mode 100644 index 0000000..0d5699d --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/EntityEntry.java @@ -0,0 +1,82 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +/** + * The Class EntityEntry. + */ +public class EntityEntry { + + private String entityType; + + private String entityPrimaryKeyValue; + + private String searchTags; + + private String entityId; + + public String getEntityId() { + return entityId; + } + + public void setEntityId(String entityId) { + this.entityId = entityId; + } + + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + public String getEntityPrimaryKeyValue() { + return entityPrimaryKeyValue; + } + + public void setEntityPrimaryKeyValue(String entityPrimaryKeyValue) { + this.entityPrimaryKeyValue = entityPrimaryKeyValue; + } + + public String getSearchTags() { + return searchTags; + } + + public void setSearchTags(String searchTags) { + this.searchTags = searchTags; + } + + @Override + public String toString() { + return "EntityEntry [" + (entityType != null ? "entityType=" + entityType + ", " : "") + + (entityPrimaryKeyValue != null ? "entityPrimaryKeyValue=" + entityPrimaryKeyValue + ", " + : "") + + (searchTags != null ? "searchTags=" + searchTags + ", " : "") + + (entityId != null ? "entityId=" + entityId : "") + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/GraphMeta.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/GraphMeta.java new file mode 100644 index 0000000..1409501 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/GraphMeta.java @@ -0,0 +1,148 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import org.openecomp.sparky.viewandinspect.EntityTypeAggregation; + +/** + * The Class GraphMeta. + */ +public class GraphMeta { + + private com.fasterxml.jackson.databind.JsonNode aaiEntityNodeDescriptors; + + private int numNodes; + + private int numLinks; + + private long renderTimeInMs; + + private int numLinksResolvedSuccessfullyFromCache; + + private int numLinksResolvedSuccessfullyFromServer; + + private int numLinkResolveFailed; + + private EntityTypeAggregation entitySummary; + + /** + * Instantiates a new graph meta. + */ + public GraphMeta() { + entitySummary = new EntityTypeAggregation(); + } + + public EntityTypeAggregation getEntitySummary() { + return entitySummary; + } + + public void setEntitySummary(EntityTypeAggregation entitySummary) { + this.entitySummary = entitySummary; + } + + public com.fasterxml.jackson.databind.JsonNode getAaiEntityNodeDescriptors() { + return aaiEntityNodeDescriptors; + } + + public void setAaiEntityNodeDescriptors( + com.fasterxml.jackson.databind.JsonNode aaiEntityNodeDefinitions) { + this.aaiEntityNodeDescriptors = aaiEntityNodeDefinitions; + } + + public int getNumLinksResolvedSuccessfullyFromCache() { + return numLinksResolvedSuccessfullyFromCache; + } + + public void setNumLinksResolvedSuccessfullyFromCache(int numLinksResolvedSuccessfullyFromCache) { + this.numLinksResolvedSuccessfullyFromCache = numLinksResolvedSuccessfullyFromCache; + } + + public int getNumLinksResolvedSuccessfullyFromServer() { + return numLinksResolvedSuccessfullyFromServer; + } + + public void setNumLinksResolvedSuccessfullyFromServer( + int numLinksResolvedSuccessfullyFromServer) { + this.numLinksResolvedSuccessfullyFromServer = numLinksResolvedSuccessfullyFromServer; + } + + public int getNumLinkResolveFailed() { + return numLinkResolveFailed; + } + + public void setNumLinkResolveFailed(int numLinkResolveFailed) { + this.numLinkResolveFailed = numLinkResolveFailed; + } + + public int getNumNodes() { + return numNodes; + } + + public void setNumNodes(int numNodes) { + this.numNodes = numNodes; + } + + public int getNumLinks() { + return numLinks; + } + + public void setNumLinks(int numLinks) { + this.numLinks = numLinks; + } + + public long getRenderTimeInMs() { + return renderTimeInMs; + } + + public void setRenderTimeInMs(long renderTimeInMs) { + this.renderTimeInMs = renderTimeInMs; + } + + /** + * Peg counter. + * + * @param counterName the counter name + */ + public void pegCounter(String counterName) { + entitySummary.pegCounter(counterName); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "GraphMeta [" + + (aaiEntityNodeDescriptors != null + ? "aaiEntityNodeDescriptors=" + aaiEntityNodeDescriptors + ", " : "") + + "numNodes=" + numNodes + ", numLinks=" + numLinks + ", renderTimeInMs=" + renderTimeInMs + + ", numLinksResolvedSuccessfullyFromCache=" + numLinksResolvedSuccessfullyFromCache + + ", numLinksResolvedSuccessfullyFromServer=" + numLinksResolvedSuccessfullyFromServer + + ", numLinkResolveFailed=" + numLinkResolveFailed + ", " + + (entitySummary != null ? "entitySummary=" + entitySummary : "") + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/InlineMessage.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/InlineMessage.java new file mode 100644 index 0000000..1a4dd58 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/InlineMessage.java @@ -0,0 +1,71 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +/** + * The Class InlineMessage. + */ +public class InlineMessage { + + private String level; + private String message; + + /** + * Instantiates a new inline message. + * + * @param level the level + * @param message the message + */ + public InlineMessage(String level, String message) { + this.level = level; + this.message = message; + } + + public String getLevel() { + return level; + } + + public void setLevel(String level) { + this.level = level; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return level + " : " + message; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/JsonNode.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/JsonNode.java new file mode 100644 index 0000000..9db06f0 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/JsonNode.java @@ -0,0 +1,197 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; + +/* + * We can use annotations to differentiate between intermediate data we use to build the node, and + * the data that we actually want to appear in the exported JSON. + */ + +/* + * This is our current ( 14-June-2016 ) working schema that will remain organic until we get it just + * right. + * + * { "item-type": "customer", "item-name-key": "subscriber-name", “item-name-value” : + * “subscriber-name-123456789-aai847-data-01”, "item-properties": [{ "property-name": + * "subscriber-name", "property-value": "subscriber-name-123456789-aai847-data-01" }, { + * "property-name": "global-customer-id", "property-value": + * "global-customer-id-123456789-aai847-data-01" } ], "node-meta": { “color” : “#f2d2d2”, + * "isSearchTarget" : false, "nodeGroups" : "1,2,3,4" }, } + * + */ + + +/** + * The Class JsonNode. + */ +public class JsonNode { + + private String id; + private String itemType; + private String itemNameKey; + private String itemNameValue; + private Map itemProperties; + private NodeMeta nodeMeta; + + @JsonIgnore + private boolean isRootNode; + + + @JsonIgnore + private String resourceKey; + @JsonIgnore + private Collection inboundNeighbors; + + @JsonIgnore + private Collection outboundNeighbors; + + + @JsonIgnore + private static final Logger LOG = Logger.getLogger(JsonNode.class); + + /** + * Instantiates a new json node. + * + * @param ain the ain + */ + public JsonNode(ActiveInventoryNode ain) { + this.resourceKey = ain.getNodeId(); + this.itemProperties = ain.getProperties(); + this.setItemType(ain.getEntityType()); + this.setItemNameKey(ain.getPrimaryKeyName()); + this.setItemNameValue(ain.getPrimaryKeyValue()); + this.setId(ain.getNodeId()); + this.isRootNode = ain.isRootNode(); + + if (LOG.isDebugEnabled()) { + LOG.debug("---"); + LOG.debug("JsonNode constructor using AIN = " + ain.dumpNodeTree(true)); + LOG.debug("---"); + } + + inboundNeighbors = ain.getInboundNeighbors(); + outboundNeighbors = ain.getOutboundNeighbors(); + + nodeMeta = new NodeMeta(); + + nodeMeta.setNodeIssue(ain.isNodeIssue()); + nodeMeta.setNodeDepth(ain.getNodeDepth()); + + nodeMeta.setNumInboundNeighbors(ain.getInboundNeighbors().size()); + nodeMeta.setNumOutboundNeighbors(ain.getOutboundNeighbors().size()); + + nodeMeta.setAtMaxDepth(ain.isAtMaxDepth()); + nodeMeta.setSelfLinkResolved(!ain.isSelflinkRetrievalFailure()); + nodeMeta.setProcessingErrorOccurred(ain.isProcessingErrorOccurred()); + nodeMeta.setHasNeighbors( + ain.getOutboundNeighbors().size() > 0 || ain.getInboundNeighbors().size() > 0); + nodeMeta.setProcessingState(ain.getState()); + + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getItemNameKey() { + return itemNameKey; + } + + public String getItemNameValue() { + return itemNameValue; + } + + public Map getItemProperties() { + return itemProperties; + } + + public String getItemType() { + return itemType; + } + + public String getResourceKey() { + return resourceKey; + } + + public void setItemNameKey(String itemNameKey) { + this.itemNameKey = itemNameKey; + } + + public void setItemNameValue(String itemNameValue) { + this.itemNameValue = itemNameValue; + } + + public void setItemProperties(HashMap itemProperties) { + this.itemProperties = itemProperties; + } + + public void setItemType(String itemType) { + this.itemType = itemType; + } + + public void setResourceKey(String resourceKey) { + this.resourceKey = resourceKey; + } + + public NodeMeta getNodeMeta() { + return nodeMeta; + } + + public void setNodeMeta(NodeMeta nodeMeta) { + this.nodeMeta = nodeMeta; + } + + public boolean isRootNode() { + return isRootNode; + } + + @Override + public String toString() { + return "JsonNode [" + (id != null ? "id=" + id + ", " : "") + + (itemType != null ? "itemType=" + itemType + ", " : "") + + (itemNameKey != null ? "itemNameKey=" + itemNameKey + ", " : "") + + (itemNameValue != null ? "itemNameValue=" + itemNameValue + ", " : "") + + (itemProperties != null ? "itemProperties=" + itemProperties + ", " : "") + + (nodeMeta != null ? "nodeMeta=" + nodeMeta + ", " : "") + "isRootNode=" + isRootNode + + ", " + (resourceKey != null ? "resourceKey=" + resourceKey + ", " : "") + + (inboundNeighbors != null ? "inboundNeighbors=" + inboundNeighbors + ", " : "") + + (outboundNeighbors != null ? "outboundNeighbors=" + outboundNeighbors : "") + "]"; + } + + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/JsonNodeLink.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/JsonNodeLink.java new file mode 100644 index 0000000..f6be171 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/JsonNodeLink.java @@ -0,0 +1,76 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +/* + * Expected JSON Output: + * + * { JsonNodeLink : { id : , source : , target : } } + * + */ + +/** + * The Class JsonNodeLink. + */ +public class JsonNodeLink { + + protected String id; + protected String source; + protected String target; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "JsonNodeLink [id=" + id + ", source=" + source + ", target=" + target + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeDebug.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeDebug.java new file mode 100644 index 0000000..64f5333 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeDebug.java @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +/** + * The Class NodeDebug. + */ +public class NodeDebug { + private boolean maxTraversalDepthReached; + private boolean processingError; + private String processingErrorCauses; + + public boolean isMaxTraversalDepthReached() { + return maxTraversalDepthReached; + } + + public void setMaxTraversalDepthReached(boolean maxTraversalDepthReached) { + this.maxTraversalDepthReached = maxTraversalDepthReached; + } + + public boolean isProcessingError() { + return processingError; + } + + public void setProcessingError(boolean processingError) { + this.processingError = processingError; + } + + public String getProcessingErrorCauses() { + return processingErrorCauses; + } + + public void setProcessingErrorCauses(String processingErrorCauses) { + this.processingErrorCauses = processingErrorCauses; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeMeta.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeMeta.java new file mode 100644 index 0000000..6df5e73 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeMeta.java @@ -0,0 +1,212 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import org.openecomp.sparky.viewandinspect.config.VisualizationConfig; +import org.openecomp.sparky.viewandinspect.enumeration.NodeProcessingState; + +/** + * The Class NodeMeta. + */ +public class NodeMeta { + + private String className; + + private boolean isEnrichableNode; + private boolean isSearchTarget; + + private NodeDebug nodeDebug; + private boolean nodeIssue; + private boolean nodeValidated; + private long selfLinkResponseTimeInMs; + private long numInboundNeighbors; + private long numOutboundNeighbors; + + private boolean atMaxDepth; + private boolean selfLinkResolved; + private boolean processingErrorOccurred; + private boolean neighborsProcessed; + private int nodeDepth; + private boolean hasNeighbors; + + private NodeProcessingState processingState; + + /** + * Instantiates a new node meta. + */ + public NodeMeta() { + this.isSearchTarget = false; + this.isEnrichableNode = false; + + if (VisualizationConfig.getConfig().isVisualizationDebugEnabled()) { + nodeDebug = new NodeDebug(); + } + this.numInboundNeighbors = 0; + this.numOutboundNeighbors = 0; + + this.selfLinkResponseTimeInMs = 0; + + this.atMaxDepth = false; + this.selfLinkResolved = false; + this.processingErrorOccurred = false; + this.hasNeighbors = false; + this.neighborsProcessed = false; + this.nodeDepth = ActiveInventoryNode.DEFAULT_INIT_NODE_DEPTH; + this.processingState = NodeProcessingState.INIT; + + } + + public boolean isAtMaxDepth() { + return atMaxDepth; + } + + public void setAtMaxDepth(boolean atMaxDepth) { + this.atMaxDepth = atMaxDepth; + } + + public boolean isSelfLinkResolved() { + return selfLinkResolved; + } + + + + public NodeProcessingState getProcessingState() { + return processingState; + } + + public void setProcessingState(NodeProcessingState processingState) { + this.processingState = processingState; + } + + public void setSelfLinkResolved(boolean selfLinkResolved) { + this.selfLinkResolved = selfLinkResolved; + } + + public boolean isProcessingErrorOccurred() { + return processingErrorOccurred; + } + + public void setProcessingErrorOccurred(boolean processingErrorOccurred) { + this.processingErrorOccurred = processingErrorOccurred; + } + + public boolean isHasNeighbors() { + return hasNeighbors; + } + + public void setHasNeighbors(boolean hasNeighbors) { + this.hasNeighbors = hasNeighbors; + } + + public boolean isNeighborsProcessed() { + return neighborsProcessed; + } + + public void setNeighborsProcessed(boolean neighborsProcessed) { + this.neighborsProcessed = neighborsProcessed; + } + + public int getNodeDepth() { + return nodeDepth; + } + + public void setNodeDepth(int nodeDepth) { + this.nodeDepth = nodeDepth; + } + + public void setNodeDebug(NodeDebug nodeDebug) { + this.nodeDebug = nodeDebug; + } + + public String getClassName() { + return className; + } + + public long getNumInboundNeighbors() { + return numInboundNeighbors; + } + + public void setNumInboundNeighbors(long numInboundNeighbors) { + this.numInboundNeighbors = numInboundNeighbors; + } + + public long getNumOutboundNeighbors() { + return numOutboundNeighbors; + } + + public void setNumOutboundNeighbors(long numOutboundNeighbors) { + this.numOutboundNeighbors = numOutboundNeighbors; + } + + public NodeDebug getNodeDebug() { + return nodeDebug; + } + + public long getSelfLinkResponseTimeInMs() { + return selfLinkResponseTimeInMs; + } + + public boolean isEnrichableNode() { + return isEnrichableNode; + } + + public boolean isNodeIssue() { + return nodeIssue; + } + + public boolean isNodeValidated() { + return nodeValidated; + } + + public boolean isSearchTarget() { + return isSearchTarget; + } + + public void setClassName(String className) { + this.className = className; + } + + public void setEnrichableNode(boolean isEnrichableNode) { + this.isEnrichableNode = isEnrichableNode; + } + + public void setNodeIssue(boolean nodeIssue) { + this.nodeIssue = nodeIssue; + } + + public void setNodeValidated(boolean nodeValidated) { + this.nodeValidated = nodeValidated; + } + + public void setSearchTarget(boolean isSearchTarget) { + this.isSearchTarget = isSearchTarget; + } + + public void setSelfLinkResponseTimeInMs(long selfLinkResponseTimeInMs) { + this.selfLinkResponseTimeInMs = selfLinkResponseTimeInMs; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeProcessingTransaction.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeProcessingTransaction.java new file mode 100644 index 0000000..f881f06 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/NodeProcessingTransaction.java @@ -0,0 +1,103 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class NodeProcessingTransaction. + */ +public class NodeProcessingTransaction { + + private ActiveInventoryNode processingNode; + private OperationResult opResult; + private String selfLinkWithModifiers; + private String requestParameters; + + /** + * Instantiates a new node processing transaction. + */ + public NodeProcessingTransaction() {} + + public String getRequestParameters() { + return requestParameters; + } + + public void setRequestParameters(String requestParameters) { + this.requestParameters = requestParameters; + } + + public String getSelfLinkWithModifiers() { + + if (processingNode == null) { + return null; + } + + return processingNode.getSelfLink() + requestParameters; + } + + public ActiveInventoryNode getProcessingNode() { + return processingNode; + } + + public void setProcessingNode(ActiveInventoryNode processingNode) { + this.processingNode = processingNode; + } + + public OperationResult getOpResult() { + return opResult; + } + + public void setOpResult(OperationResult opResult) { + this.opResult = opResult; + } + + /** + * Processing error occurred. + * + * @return true, if successful + */ + public boolean processingErrorOccurred() { + if (opResult == null) { + return true; + } + + return !opResult.wasSuccessful(); + + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "NodeProcessingTransaction [" + + (processingNode != null ? "processingNode=" + processingNode + ", " : "") + + (opResult != null ? "opResult=" + opResult + ", " : "") + "processorErrorOccurred=" + + processingErrorOccurred() + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/QueryParams.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/QueryParams.java new file mode 100644 index 0000000..b3592c3 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/QueryParams.java @@ -0,0 +1,58 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +/** + * The Class QueryParams. + */ +public class QueryParams { + + private String searchTargetPrimaryKeyValues; + private String searchTargetNodeId; + + /** + * Instantiates a new query params. + */ + public QueryParams() { + + } + + public String getSearchTargetPrimaryKeyValues() { + return searchTargetPrimaryKeyValues; + } + + public void setSearchTargetPrimaryKeyValues(String searchTargetPrimaryKeyValues) { + this.searchTargetPrimaryKeyValues = searchTargetPrimaryKeyValues; + } + + public String getSearchTargetNodeId() { + return searchTargetNodeId; + } + + public void setSearchTargetNodeId(String searchTargetNodeId) { + this.searchTargetNodeId = searchTargetNodeId; + } +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/QueryRequest.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/QueryRequest.java new file mode 100644 index 0000000..34c34ef --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/QueryRequest.java @@ -0,0 +1,48 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +/** + * The Class QueryRequest. + */ +public class QueryRequest { + + private String hashId; + + public String getHashId() { + return hashId; + } + + public void setHashId(String hashId) { + this.hashId = hashId; + } + + @Override + public String toString() { + return "QueryRequest [hashId=" + hashId + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/QuerySearchEntity.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/QuerySearchEntity.java new file mode 100644 index 0000000..71b775c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/QuerySearchEntity.java @@ -0,0 +1,75 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * The Class ViewAndInspectSearchRequest. + */ +public class QuerySearchEntity { + + private static final String DEFAULT_MAX_RESULTS = "10"; + public String maxResults; + + public String queryStr; + + /** + * Instantiates a new view and inspect search request. + */ + public QuerySearchEntity() { + maxResults = DEFAULT_MAX_RESULTS; + queryStr = null; + } + + public String getMaxResults() { + return maxResults; + } + + public void setMaxResults(String maxResults) { + this.maxResults = maxResults; + } + + public String getQueryStr() { + return queryStr; + } + + public void setQueryStr(String queryStr) { + this.queryStr = queryStr; + } + + @JsonIgnore + public String[] getSearchTerms() { + + if (queryStr == null) { + return null; + } + + return queryStr.split(" "); + + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelatedToProperty.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelatedToProperty.java new file mode 100644 index 0000000..a4c72b0 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelatedToProperty.java @@ -0,0 +1,65 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The Class RelatedToProperty. + */ +public class RelatedToProperty { + protected String propertyKey; + protected String propertyValue; + + @JsonProperty("property-key") + public String getPropertyKey() { + return propertyKey; + } + + public void setPropertyKey(String propertyKey) { + this.propertyKey = propertyKey; + } + + @JsonProperty("property-value") + public String getPropertyValue() { + return propertyValue; + } + + public void setPropertyValue(String propertyValue) { + this.propertyValue = propertyValue; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "RelatedToProperty [propertyKey=" + propertyKey + ", propertyValue=" + propertyValue + + "]"; + } + + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/Relationship.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/Relationship.java new file mode 100644 index 0000000..e82ef3a --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/Relationship.java @@ -0,0 +1,92 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Arrays; + +/** + * The Class Relationship. + */ +public class Relationship { + + protected String relatedTo; + protected String relatedLink; + protected RelationshipData[] relationshipData; + protected RelatedToProperty[] relatedToProperty; + + public String getRelatedTo() { + return relatedTo; + } + + @JsonProperty("related-to") + public void setRelatedTo(String relatedTo) { + this.relatedTo = relatedTo; + } + + public String getRelatedLink() { + return relatedLink; + } + + @JsonProperty("related-link") + public void setRelatedLink(String relatedLink) { + this.relatedLink = relatedLink; + } + + public RelationshipData[] getRelationshipData() { + return relationshipData; + } + + @JsonProperty("relationship-data") + public void setRelationshipData(RelationshipData[] relationshipData) { + this.relationshipData = relationshipData; + } + + + + public RelatedToProperty[] getRelatedToProperty() { + return relatedToProperty; + } + + @JsonProperty("related-to-property") + public void setRelatedToProperty(RelatedToProperty[] relatedToProperty) { + this.relatedToProperty = relatedToProperty; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Relationship [relatedTo=" + relatedTo + ", relatedLink=" + relatedLink + + ", relationshipData=" + Arrays.toString(relationshipData) + ", relatedToProperty=" + + Arrays.toString(relatedToProperty) + "]"; + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipData.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipData.java new file mode 100644 index 0000000..d290fef --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipData.java @@ -0,0 +1,64 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The Class RelationshipData. + */ +public class RelationshipData { + protected String relationshipKey; + protected String relationshipValue; + + @JsonProperty("relationship-key") + public String getRelationshipKey() { + return relationshipKey; + } + + public void setRelationshipKey(String relationshipKey) { + this.relationshipKey = relationshipKey; + } + + @JsonProperty("relationship-value") + public String getRelationshipValue() { + return relationshipValue; + } + + public void setRelationshipValue(String relationshipValue) { + this.relationshipValue = relationshipValue; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "RelationshipData [relationshipKey=" + relationshipKey + ", relationshipValue=" + + relationshipValue + "]"; + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipDirectionality.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipDirectionality.java new file mode 100644 index 0000000..3c273dc --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipDirectionality.java @@ -0,0 +1,43 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +/** + * This enumeration is intended to be used to help us discriminate neighbor relationships for the + * purpose of visualization and conceptualization to model in/out relationships between + * ActiveInventoryNodes. + * Possible visualization behaviors could be the following: - IN ( draw a line with 1 arrow ) - OUT + * ( draw a line with 1 arrow ) - BOTH ( draw a line with 2 arrows, or 2 lines with 1 arrow each ) - + * UNKNOWN ( draw a line with no arrows ) + * The UNKNOWN case is what we have at the moment where we have a collection neighbors with no + * knowledge of relationship directionality. + * + * @author davea + * + */ +public enum RelationshipDirectionality { + IN, OUT, BOTH, UNKNOWN +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipList.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipList.java new file mode 100644 index 0000000..257b68c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/RelationshipList.java @@ -0,0 +1,58 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Arrays; + +/** + * The Class RelationshipList. + */ +public class RelationshipList { + + protected Relationship[] relationship; + + public Relationship[] getRelationshipList() { + return relationship; + } + + @JsonProperty("relationship") + public void setRelationshipList(Relationship[] relationship) { + this.relationship = relationship; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "RelationshipList [relationshipList=" + Arrays.toString(relationship) + "]"; + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/SearchResponse.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/SearchResponse.java new file mode 100644 index 0000000..8cc8d9b --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/SearchResponse.java @@ -0,0 +1,93 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.openecomp.sparky.suggestivesearch.SuggestionEntity; + +/** + * The Class SearchResponse. + */ +public class SearchResponse { + + private long processingTimeInMs; + private int totalFound; + + private List suggestions; + + /** + * Instantiates a new search response. + */ + public SearchResponse() { + this.suggestions = new ArrayList(); + this.processingTimeInMs = 0; + this.totalFound = 0; + } + + public long getProcessingTimeInMs() { + return processingTimeInMs; + } + + public void setProcessingTimeInMs(long processingTimeInMs) { + this.processingTimeInMs = processingTimeInMs; + } + + public int getTotalFound() { + return totalFound; + } + + public void setTotalFound(int totalFound) { + this.totalFound = totalFound; + } + + public List getSuggestions() { + return suggestions; + } + + public void setSuggestions(List suggestions) { + this.suggestions = suggestions; + } + /** + * Adds the entity entry. + * + * @param suggestionEntry that will be converted to JSON + */ + public void addSuggestion(SuggestionEntity suggestionEntity){ + suggestions.add(suggestionEntity); + } + + /** + * Increments the total number of hits for this SearchResponse by + * the value passed in. + * + * @param additionalCount - Count to increment the total found + */ + public void addToTotalFound(int additionalCount) { + totalFound += additionalCount; + } +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/SelfLinkDeterminationTransaction.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/SelfLinkDeterminationTransaction.java new file mode 100644 index 0000000..fbe6325 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/SelfLinkDeterminationTransaction.java @@ -0,0 +1,82 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import org.openecomp.sparky.dal.rest.OperationResult; + +public class SelfLinkDeterminationTransaction { + + private String parentNodeId; + private ActiveInventoryNode newNode; + private String queryString; + private String entityUrl; + private OperationResult opResult; + + + + public String getParentNodeId() { + return parentNodeId; + } + + public void setParentNodeId(String parentNodeId) { + this.parentNodeId = parentNodeId; + } + + public ActiveInventoryNode getNewNode() { + return newNode; + } + + public void setNewNode(ActiveInventoryNode newNode) { + this.newNode = newNode; + } + + public OperationResult getOpResult() { + return opResult; + } + + public void setOpResult(OperationResult opResult) { + this.opResult = opResult; + } + + public String getQueryString() { + return queryString; + } + + public void setQueryString(String queryString) { + this.queryString = queryString; + } + + public String getEntityUrl() { + return entityUrl; + } + + public void setEntityUrl(String entityUrl) { + this.entityUrl = entityUrl; + } + + + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/entity/Violations.java b/src/main/java/org/openecomp/sparky/viewandinspect/entity/Violations.java new file mode 100644 index 0000000..a921782 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/entity/Violations.java @@ -0,0 +1,114 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.entity; + +import com.att.aft.dme2.internal.jackson.annotate.JsonProperty; + +/** + * The Class Violations. + */ +public class Violations { + + private String severity; + + private String category; + + private String type; + + private String timestamp; + + private String details; + + @JsonProperty("error-message") + private String errorMessage; + + /** + * Instantiates a new violations. + * + * @param severity the severity + * @param category the category + * @param type the type + * @param timestamp the timestamp + * @param errorMessage the error message + */ + public Violations(String severity, String category, String type, String timestamp, + String errorMessage) { + this.severity = severity; + this.category = category; + this.type = type; + this.timestamp = timestamp; + this.errorMessage = errorMessage; + } + + public String getSeverity() { + return severity; + } + + public void setSeverity(String severity) { + this.severity = severity; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + /* + * public Map getDetails() { return details; } + * + * public void setDetails(Map details) { this.details = details; } + */ + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/enumeration/NodeProcessingAction.java b/src/main/java/org/openecomp/sparky/viewandinspect/enumeration/NodeProcessingAction.java new file mode 100644 index 0000000..7c9befa --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/enumeration/NodeProcessingAction.java @@ -0,0 +1,36 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.enumeration; + +/** + * The Enum NodeProcessingAction. + */ +public enum NodeProcessingAction { + SELF_LINK_SET, NEW_NODE_PROCESSED, SELF_LINK_RESOLVE_ERROR, SELF_LINK_DETERMINATION_ERROR, + SELF_LINK_RESOLVE_OK, SELF_LINK_RESPONSE_PARSE_ERROR, SELF_LINK_RESPONSE_PARSE_OK, + NEIGHBORS_PROCESSED_ERROR, NEIGHBORS_PROCESSED_OK, COMPLEX_ATTRIBUTE_GROUP_PARSE_ERROR, + COMPLEX_ATTRIBUTE_GROUP_PARSE_OK, NODE_IDENTITY_ERROR, UNEXPECTED_STATE_TRANSITION +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/enumeration/NodeProcessingState.java b/src/main/java/org/openecomp/sparky/viewandinspect/enumeration/NodeProcessingState.java new file mode 100644 index 0000000..344f8df --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/enumeration/NodeProcessingState.java @@ -0,0 +1,33 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.enumeration; + +/** + * The Enum NodeProcessingState. + */ +public enum NodeProcessingState { + INIT, SELF_LINK_UNRESOLVED, SELF_LINK_RESPONSE_UNPROCESSED, NEIGHBORS_UNPROCESSED, READY, ERROR +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/services/SearchServiceWrapper.java b/src/main/java/org/openecomp/sparky/viewandinspect/services/SearchServiceWrapper.java new file mode 100644 index 0000000..41f0eff --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/services/SearchServiceWrapper.java @@ -0,0 +1,809 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.viewandinspect.services; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONException; +import org.json.JSONObject; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.elasticsearch.HashQueryResponse; +import org.openecomp.sparky.dal.elasticsearch.SearchAdapter; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.sas.config.SearchServiceConfig; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.search.VnfSearchService; +import org.openecomp.sparky.search.config.SuggestionConfig; +import org.openecomp.sparky.suggestivesearch.SuggestionEntity; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.viewandinspect.entity.QuerySearchEntity; +import org.openecomp.sparky.viewandinspect.entity.SearchResponse; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * The Class SearchServlet. + */ + +public class SearchServiceWrapper { + + private static final long serialVersionUID = 1L; + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(SearchServiceWrapper.class); + + private SearchServiceConfig sasConfig = null; + private SuggestionConfig suggestionConfig = null; + private SearchAdapter search = null; + private ObjectMapper mapper; + private OxmModelLoader oxmModelLoader; + private VnfSearchService vnfSearch = null; + + private static final String SEARCH_STRING = "search"; + private static final String COUNT_STRING = "count"; + private static final String QUERY_SEARCH = SEARCH_STRING + "/querysearch"; + private static final String SUMMARY_BY_ENTITY_TYPE_API = SEARCH_STRING + "/summarybyentitytype"; + private static final String SUMMARY_BY_ENTITY_TYPE_COUNT_API = + SUMMARY_BY_ENTITY_TYPE_API + "/" + COUNT_STRING; + + private static final String VALUE_ANYKEY = "anyKey"; + private static final String VALUE_QUERY = "query"; + + private static final String KEY_HASH_ID = "hashId"; + private static final String KEY_GROUP_BY = "groupby"; + private static final String KEY_SEARCH_RESULT = "searchResult"; + private static final String KEY_HITS = "hits"; + private static final String KEY_PAYLOAD = "payload"; + private static final String KEY_DOCUMENT = "document"; + private static final String KEY_CONTENT = "content"; + private static final String KEY_SEARCH_TAG_IDS = "searchTagIDs"; + private static final String KEY_SEARCH_TAGS = "searchTags"; + private static final String KEY_LINK = "link"; + private static final String KEY_ENTITY_TYPE = "entityType"; + + private static final String VI_SUGGESTION_ROUTE = "viewInspect"; // TODO -> Read route from + // suggestive-search.properties + // instead of hard coding + + private static final String VIUI_SEARCH_TEMPLATE = + "{ " + "\"results-start\": 0," + "\"results-size\": %d," + "\"queries\": [{" + "\"must\": {" + + "\"match\": {" + "\"field\": \"entityType searchTags crossEntityReferenceValues\"," + + "\"value\": \"%s\"," + "\"operator\": \"and\", " + + "\"analyzer\": \"whitespace_analyzer\"" + "}" + "}" + "}]" + "}"; + + /** + * Instantiates a new search service wrapper + */ + public SearchServiceWrapper() { + this.mapper = new ObjectMapper(); + vnfSearch = new VnfSearchService(); + + try { + if (sasConfig == null) { + sasConfig = SearchServiceConfig.getConfig(); + } + + if (suggestionConfig == null) { + suggestionConfig = SuggestionConfig.getConfig(); + } + + if (search == null) { + search = new SearchAdapter(); + } + + if (oxmModelLoader == null) { + oxmModelLoader = OxmModelLoader.getInstance(); + + if (OxmModelLoader.getInstance().getSearchableEntityDescriptors().isEmpty()) { + LOG.error(AaiUiMsgs.ENTITY_NOT_FOUND_IN_OXM, "searchable entity"); + } + } + } catch (Exception exc) { + new ServletException( + "Caught an exception while getting an instance of servlet configuration from SearchServlet.", exc); + } + } + + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doPost(request, response); + } + + public void setSasConfig(SearchServiceConfig sasConfig) { + this.sasConfig = sasConfig; + } + + public SearchServiceConfig getSasConfig() { + return sasConfig; + } + + public void setSuggestionConfig(SuggestionConfig suggestionConfig) { + this.suggestionConfig = suggestionConfig; + } + + public void setSearch(SearchAdapter search) { + this.search = search; + } + + public SuggestionConfig getSuggestionConfig() { + return suggestionConfig; + } + + public SearchAdapter getSearch() { + return search; + } + + public void setOxmModelLoader(OxmModelLoader oxmModelLoader) { + this.oxmModelLoader = oxmModelLoader; + } + + public OxmModelLoader getOxmModelLoader() { + return oxmModelLoader; + } + + public VnfSearchService getVnfSearch() { + return vnfSearch; + } + + public void setVnfSearch(VnfSearchService vnfSearch) { + this.vnfSearch = vnfSearch; + } + + /** + * Get Full URL for search + * + * @param api the api + * @param indexName + * @return the full url + */ + private String getSasFullUrl(String indexName, String type, String ipAddress, String port, + String version) { + + return String.format("https://%s:%s/services/search-data-service/%s/search/indexes/%s/%s", + ipAddress, port, version, indexName, type); + } + + /** + * Handle search service do query. + * + * @param app the app + * @param request the request + * @param response the response + * @throws Exception the exception + */ + + protected JSONObject getRequestParamsFromHeader(HttpServletRequest request) { + StringBuffer br = new StringBuffer(); + String line = null; + try { + BufferedReader reader = request.getReader(); + while ((line = reader.readLine()) != null) { + br.append(line); + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_READING_HTTP_REQ_PARAMS); + } + + String output = br.toString(); + + return new JSONObject(output); + } + + protected void handleSummaryByEntityTypeCount(HttpServletRequest request, + HttpServletResponse response) throws Exception { + JSONObject parameters = getRequestParamsFromHeader(request); + String hashId = null; + if (parameters.has(KEY_HASH_ID)){ + hashId = parameters.get(KEY_HASH_ID).toString(); + } else { + vnfSearch.setZeroCountResponse(response); + LOG.error(AaiUiMsgs.ERROR_HASH_NOT_FOUND); + return; + } + HashQueryResponse hashQueryResponse = getResponseForQueryByHash(hashId, response); + Map hashQueryResponsePayloadParams = new HashMap(); + if (hashQueryResponse.getJsonPayload() != null) { + hashQueryResponsePayloadParams = getPayloadParams(hashQueryResponse.getJsonPayload()); + vnfSearch.getEntityCountResults(response, hashQueryResponsePayloadParams); + } else { + vnfSearch.setZeroCountResponse(response); + LOG.error(AaiUiMsgs.ERROR_INVALID_HASH, hashId); + } + } + + protected Map getPayloadParams(String parameters) { + Map payloadParams = new HashMap(); + try { + JSONObject json = new JSONObject(parameters); + JSONObject payload = json.getJSONObject(KEY_PAYLOAD); + if (payload.length() > 0) { + for (String key : JSONObject.getNames(payload)) { + payloadParams.put(key, payload.getString(key)); + } + } + } catch (JSONException exc) { + LOG.error(AaiUiMsgs.ERROR_PARSING_PARAMS, exc); + } + return payloadParams; + } + + protected HashQueryResponse getResponseForQueryByHash(String hashId, HttpServletResponse response){ + return vnfSearch.getJSONPayloadFromHash(hashId); + } + + protected void handleSummaryByEntityType(HttpServletRequest request, HttpServletResponse response) + throws Exception { + JSONObject parameters = getRequestParamsFromHeader(request); + String hashId = null; + if (parameters.has(KEY_HASH_ID)){ + hashId = parameters.get(KEY_HASH_ID).toString(); + } else { + vnfSearch.setZeroCountResponse(response); + LOG.error(AaiUiMsgs.ERROR_HASH_NOT_FOUND); + return; + } + HashQueryResponse hashQueryResponse = getResponseForQueryByHash(hashId, response); + Map hashQueryResponsePayloadParams = new HashMap(); + if (hashQueryResponse.getJsonPayload() != null) { + hashQueryResponsePayloadParams = getPayloadParams(hashQueryResponse.getJsonPayload()); + if (parameters.has(KEY_GROUP_BY)){ + String groupByKey = parameters.getString(KEY_GROUP_BY); + vnfSearch.getSummaryByEntityType(response, hashQueryResponsePayloadParams, groupByKey); + } + } else { + LOG.error(AaiUiMsgs.ERROR_INVALID_HASH, hashId); + vnfSearch.setEmptyAggResponse(response); + } + } + + /** + * Gets the value from node. + * + * @param node the node + * @param fieldName the field name + * @return the value from node + */ + private String getValueFromNode(JsonNode node, String fieldName) { + + if (node == null || fieldName == null) { + return null; + } + + JsonNode valueNode = node.get(fieldName); + + if (valueNode != null) { + return valueNode.asText(); + } + + return null; + + } + + /** + * Builds the search response. + * + * @param operationResult the operation result + * @param queryStr the query str + * @return TODO + * @return the search response + */ + private List generateSuggestionsForSearchResponse(String operationResult, + String queryStr) { + + + if (operationResult == null || operationResult.length() == 0) { + return null; + } + + ObjectMapper mapper = new ObjectMapper(); + JsonNode rootNode = null; + List suggestionEntityList = new ArrayList(); + try { + rootNode = mapper.readTree(operationResult); + + JsonNode hitsNode = rootNode.get(KEY_SEARCH_RESULT); + + + // Check if there are hits that are coming back + if (hitsNode.has(KEY_HITS)) { + ArrayNode hitsArray = (ArrayNode) hitsNode.get(KEY_HITS); + + /* + * next we iterate over the values in the hit array elements + */ + + Iterator nodeIterator = hitsArray.elements(); + JsonNode entityNode = null; + SuggestionEntity suggestionEntity = null; + JsonNode sourceNode = null; + while (nodeIterator.hasNext()) { + entityNode = nodeIterator.next(); + sourceNode = entityNode.get(KEY_DOCUMENT).get(KEY_CONTENT); + + // do the point transformation as we build the response? + suggestionEntity = new SuggestionEntity(); + suggestionEntity.setRoute(VI_SUGGESTION_ROUTE); + + /* + * This is where we probably want to annotate the search tags because we also have access + * to the seachTagIds + */ + + String searchTagIds = getValueFromNode(sourceNode, KEY_SEARCH_TAG_IDS); + String searchTags = getValueFromNode(sourceNode, KEY_SEARCH_TAGS); + String link = getValueFromNode(sourceNode, KEY_LINK); + String entityType = getValueFromNode(sourceNode, KEY_ENTITY_TYPE); + if (link != null) { + suggestionEntity.setHashId(NodeUtils.generateUniqueShaDigest(link)); + } + + try { + suggestionEntity + .setText(annotateSearchTags(searchTags, searchTagIds, entityType, queryStr)); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SEARCH_TAG_ANNOTATION_ERROR, searchTags.toString(), + exc.getLocalizedMessage()); + // at least send back the un-annotated search tags + suggestionEntity.setText(searchTags); + } + + if (searchTags != null) { + suggestionEntityList.add(suggestionEntity); + } + + } + } + } catch (IOException exc) { + LOG.warn(AaiUiMsgs.SEARCH_RESPONSE_BUILDING_EXCEPTION, exc.getLocalizedMessage()); + } + return suggestionEntityList; + } + + /* + */ + + /** + * Query terms match search tag. + * + * @param queryTerms the query terms + * @param searchTag the search tag + * @return true, if successful @return. + */ + private boolean queryTermsMatchSearchTag(String[] queryTerms, String searchTag) { + + if (queryTerms == null || queryTerms.length == 0 || searchTag == null) { + return false; + } + + for (String queryTerm : queryTerms) { + if (searchTag.toLowerCase().contains(queryTerm.toLowerCase())) { + return true; + } + } + + return false; + + } + + /** + * The current format of an UI-dropdown-item is like: "search-terms entityType att1=attr1_val". + * Example, for pserver: search-terms pserver hostname=djmAG-72060, + * pserver-name2=example-pserver-name2-val-17254, pserver-id=example-pserver-id-val-17254, + * ipv4-oam-address=example-ipv4-oam-address-val-17254 SearchController.js parses the above + * format. So if you are modifying the parsing below, please update SearchController.js as well. + * + * @param searchTags the search tags + * @param searchTagIds the search tag ids + * @param entityType the entity type + * @param queryStr the query str + * @return the string + */ + + private String annotateSearchTags(String searchTags, String searchTagIds, String entityType, + String queryStr) { + + if (searchTags == null || searchTagIds == null) { + String valueOfSearchTags = String.valueOf(searchTags); + String valueOfSearchTagIds = String.valueOf(searchTagIds); + + LOG.error(AaiUiMsgs.SEARCH_TAG_ANNOTATION_ERROR, "See error", + "Search tags = " + valueOfSearchTags + " and Seach tag IDs = " + valueOfSearchTagIds); + return searchTags; + } + + if (entityType == null) { + LOG.error(AaiUiMsgs.SEARCH_TAG_ANNOTATION_ERROR, searchTags.toString(), "EntityType is null"); + return searchTags; + } + + if (queryStr == null) { + LOG.error(AaiUiMsgs.SEARCH_TAG_ANNOTATION_ERROR, searchTags.toString(), + "Query string is null"); + return searchTags; + } + + /* + * The ElasticSearch analyzer has already applied the lowercase filter, so we don't have to + * covert them again + */ + String[] searchTagsArray = searchTags.split(";"); + String[] searchTagIdsArray = searchTagIds.split(";"); + + // specifically apply lower case to the the query terms to make matching + // simpler + String[] queryTerms = queryStr.toLowerCase().split(" "); + + OxmEntityDescriptor desc = oxmModelLoader.getSearchableEntityDescriptors().get(entityType); + + if (desc == null) { + LOG.error(AaiUiMsgs.ENTITY_NOT_FOUND_IN_OXM, entityType.toString()); + return searchTags; + } + + String primaryKeyName = NodeUtils.concatArray(desc.getPrimaryKeyAttributeName(), "/"); + String primaryKeyValue = null; + + /* + * For each used attribute, get the fieldName for the attribute index and transform the search + * tag into t1,t2,t3 => h1=t1, h2=t2, h3=t3; + */ + StringBuilder searchTagsBuilder = new StringBuilder(128); + searchTagsBuilder.append(entityType); + + String primaryKeyConjunctionValue = null; + boolean queryTermsMatchedSearchTags = false; + + if (searchTagsArray.length == searchTagIdsArray.length) { + for (int i = 0; i < searchTagsArray.length; i++) { + String searchTagAttributeId = searchTagIdsArray[i]; + String searchTagAttributeValue = searchTagsArray[i]; + + // Find the concat conjunction + Map pairConjunctionList = suggestionConfig.getPairingList(); + + String suggConjunction = null; + if (pairConjunctionList.get(searchTagAttributeId) != null) { + suggConjunction = pairConjunctionList.get(searchTagAttributeId); + } else { + suggConjunction = suggestionConfig.getDefaultPairingValue(); + } + + if (primaryKeyName.equals(searchTagAttributeId)) { + primaryKeyValue = searchTagAttributeValue; + primaryKeyConjunctionValue = suggConjunction; + } + + if (queryTermsMatchSearchTag(queryTerms, searchTagAttributeValue)) { + searchTagsBuilder.append(" " + suggConjunction + " " + searchTagAttributeValue); + queryTermsMatchedSearchTags = true; + } + } + } else { + String errorMessage = "Search tags length did not match search tag ID length for entity type " + entityType; + LOG.error(AaiUiMsgs.ENTITY_SYNC_SEARCH_TAG_ANNOTATION_FAILED, errorMessage); + } + + /* + * if none of the user query terms matched the index entity search tags then we should still tag + * the matched entity with a conjunction set to at least it's entity primary key value to + * discriminate between the entities of the same type in the search results displayed in the UI + * search bar results + */ + + if (!queryTermsMatchedSearchTags) { + + if (primaryKeyValue != null && primaryKeyConjunctionValue != null) { + searchTagsBuilder.append(" " + primaryKeyConjunctionValue + " " + primaryKeyValue); + } else { + LOG.error(AaiUiMsgs.SEARCH_TAG_ANNOTATION_ERROR, "See error", + "Could not annotate user query terms " + queryStr + + " from available entity search tags = " + searchTags); + return searchTags; + } + + } + + return searchTagsBuilder.toString(); + + } + + + /** + * @param queryStr - space separate query search terms + * @return - query string with stop-words removed + */ + private String stripStopWordsFromQuery(String queryStr) { + + if (queryStr == null) { + return queryStr; + } + + Collection stopWords = suggestionConfig.getStopWords(); + ArrayList queryTerms = new ArrayList(Arrays.asList(queryStr.toLowerCase().split(" "))); + + queryTerms.removeAll(stopWords); + + return String.join(" ", queryTerms); + } + + /* + * Expected query: + * + * POST /search/viuiSearch/ + * + * { "maxResults" : "10", "searchStr" : "" } + */ + + /** + * Handle view and inspect search. + * + * @param request the request + * @param maxResults Max number of results to return + * @param response the response + * @return + * @throws IOException Signals that an I/O exception has occurred. + */ + protected List performViewAndInspectQuerySearch( + QuerySearchEntity querySearchEntity, int maxResults) throws IOException { + List suggestionEntityList = new ArrayList(); + + /* + * Based on the configured stop words, we need to strip any matched stop-words ( case + * insensitively ) from the query string, before hitting elastic to prevent the words from being + * used against the elastic view-and-inspect index. Another alternative to this approach would + * be to define stop words on the elastic search index configuration for the + * entity-search-index, but but that may be more complicated / more risky than just a simple bug + * fix, but it's something we should think about for the future. + */ + + try { + final String queryStringWithoutStopWords = + stripStopWordsFromQuery(querySearchEntity.getQueryStr()); + + final String fullUrlStr = getSasFullUrl(sasConfig.getIndexName(), VALUE_QUERY, + sasConfig.getIpAddress(), sasConfig.getHttpPort(), sasConfig.getVersion()); + + String postBody = + String.format(VIUI_SEARCH_TEMPLATE, maxResults, queryStringWithoutStopWords); + + OperationResult opResult = search.doPost(fullUrlStr, postBody, "application/json"); + if (opResult.getResultCode() == 200) { + suggestionEntityList = generateSuggestionsForSearchResponse(opResult.getResult(), + querySearchEntity.getQueryStr()); + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, + "View and inspect query failed with error = " + exc.getMessage()); + } + return suggestionEntityList; + } + + protected List performVnfQuerySearch(QuerySearchEntity querySearchEntity, + int resultCountLimit) throws Exception { + return vnfSearch.getSuggestionsResults(querySearchEntity, resultCountLimit); + } + + protected void handleQuerySearch(HttpServletRequest request, HttpServletResponse response) + throws IOException { + String payload = NodeUtils.getBody(request); + if (payload == null || payload.isEmpty()) { + handleSearchServletErrors("Unable to parse payload", null, response); + } else { + QuerySearchEntity querySearchEntity = mapper.readValue(payload, QuerySearchEntity.class); + int maxResultsPerSearch = Integer.valueOf(querySearchEntity.getMaxResults()); + try { + SearchResponse searchResponse = new SearchResponse(); + List viewAndInspectsuggestionEntityList = + new ArrayList(); + List vnfSuggestionEntityList = new ArrayList(); + long processTime = System.currentTimeMillis(); + for (String searchService : suggestionConfig.getSearchIndexToSearchService().values()) { + if (searchService.equals(SearchServiceWrapper.class.getSimpleName())) { + viewAndInspectsuggestionEntityList = + performViewAndInspectQuerySearch(querySearchEntity, maxResultsPerSearch); + } else if (searchService.equals(VnfSearchService.class.getSimpleName())) { + vnfSuggestionEntityList = performVnfQuerySearch(querySearchEntity, maxResultsPerSearch); + } + } + + int totalAdded = 0; + for (int i = 0; i < maxResultsPerSearch; i++) { + if (i < viewAndInspectsuggestionEntityList.size() && totalAdded < maxResultsPerSearch) { + searchResponse.addSuggestion(viewAndInspectsuggestionEntityList.get(i)); + totalAdded++; + } + if (i < vnfSuggestionEntityList.size() && totalAdded < maxResultsPerSearch) { + searchResponse.addSuggestion(vnfSuggestionEntityList.get(i)); + totalAdded++; + } + if (totalAdded >= maxResultsPerSearch) { + break; + } + } + searchResponse.addToTotalFound(totalAdded); + String searchResponseJson = NodeUtils.convertObjectToJson(searchResponse, true); + + processTime = System.currentTimeMillis() - processTime; + searchResponse.setProcessingTimeInMs(processTime); + setServletResponse(response, searchResponseJson); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, + "Query search failed with error = " + exc.getMessage()); + } + } + } + + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String api = null; + try { + + // set default response + response.setStatus(200); + + if (request.getRequestURI().contains(QUERY_SEARCH)) { + api = QUERY_SEARCH; + handleQuerySearch(request, response); + return; + } else if (request.getRequestURI().contains(SUMMARY_BY_ENTITY_TYPE_COUNT_API)) { + api = SUMMARY_BY_ENTITY_TYPE_COUNT_API; + handleSummaryByEntityTypeCount(request, response); + return; + } else if (request.getRequestURI().contains(SUMMARY_BY_ENTITY_TYPE_API)) { + api = SUMMARY_BY_ENTITY_TYPE_API; + handleSummaryByEntityType(request, response); + return; + } else { + + final String errorMessage = "Ignored request-uri = " + request.getRequestURI(); + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, errorMessage); + response.setStatus(404); + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(generateJsonErrorResponse(errorMessage)); + out.close(); + + + } + } catch (JSONException je){ + handleSearchServletErrors("Caught an exception while parsing json in processing for " + api, je, + response); + } catch (Exception e1) { + handleSearchServletErrors("Caught an exception while communicating with elasticsearch", e1, + response); + } + } + + /** + * Generate json error response. + * + * @param message the message + * @return the string + */ + /* + * This is the manual approach, however we could also create an object container for the error + * then use the Jackson ObjectWrite to dump the object to json instead. If it gets any more + * complicated we could do that approach so we don't have to manually trip over the JSON + * formatting. + */ + protected String generateJsonErrorResponse(String message) { + return String.format("{ \"errorMessage\" : %s }", message); + } + + /** + * Handle search servlet errors. + * + * @param errorMsg the error msg + * @param exc the exc + * @param response the response + * @throws IOException Signals that an I/O exception has occurred. + */ + public void handleSearchServletErrors(String errorMsg, Exception exc, + HttpServletResponse response) throws IOException { + + String errorLogMsg = + (exc == null ? errorMsg : errorMsg + ". Error:" + exc.getLocalizedMessage()); + + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, errorLogMsg); + + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(generateJsonErrorResponse(errorMsg)); + out.close(); + } + + + /** + * Execute query. + * + * @param response the response + * @param requestUrl the request url + * @param requestJsonPayload the request json payload + * @throws Exception the exception + */ + public void executeQuery(HttpServletResponse response, String requestUrl, + String requestJsonPayload) throws Exception { + + OperationResult opResult = search.doPost(requestUrl, requestJsonPayload, "application/json"); + + if (opResult != null) { + + response.setStatus(opResult.getResultCode()); + String finalOutput = opResult.getResult(); + + // example: failed to populate drop-down items from formatOutputJson() + if (finalOutput != null) { + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(finalOutput); + out.close(); + } + + } else { + response.setStatus(500); + } + + } + + /** + * Sets the servlet response. + * + * @param response the response + * @param postPayload the post payload + * + * @throws IOException Signals that an I/O exception has occurred. + */ + private void setServletResponse(HttpServletResponse response, String postPayload) + throws IOException { + + if (postPayload != null) { + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(postPayload); + out.close(); + } + } + + + +} \ No newline at end of file diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationContext.java b/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationContext.java new file mode 100644 index 0000000..c5adfd4 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationContext.java @@ -0,0 +1,1649 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.services; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.http.client.utils.URIBuilder; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.entity.SearchableEntity; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.viewandinspect.config.TierSupportUiConstants; +import org.openecomp.sparky.viewandinspect.config.VisualizationConfig; +import org.openecomp.sparky.viewandinspect.entity.ActiveInventoryNode; +import org.openecomp.sparky.viewandinspect.entity.InlineMessage; +import org.openecomp.sparky.viewandinspect.entity.NodeProcessingTransaction; +import org.openecomp.sparky.viewandinspect.entity.QueryParams; +import org.openecomp.sparky.viewandinspect.entity.Relationship; +import org.openecomp.sparky.viewandinspect.entity.RelationshipData; +import org.openecomp.sparky.viewandinspect.entity.RelationshipList; +import org.openecomp.sparky.viewandinspect.entity.SelfLinkDeterminationTransaction; +import org.openecomp.sparky.viewandinspect.enumeration.NodeProcessingAction; +import org.openecomp.sparky.viewandinspect.enumeration.NodeProcessingState; +import org.openecomp.sparky.viewandinspect.task.PerformNodeSelfLinkProcessingTask; +import org.openecomp.sparky.viewandinspect.task.PerformSelfLinkDeterminationTask; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; + +/** + * The Class SelfLinkNodeCollector. + */ +public class VisualizationContext { + + private static final int MAX_DEPTH_EVALUATION_ATTEMPTS = 100; + private static final String DEPTH_ALL_MODIFIER = "?depth=all"; + private static final String NODES_ONLY_MODIFIER = "?nodes-only"; + private static final String SERVICE_INSTANCE = "service-instance"; + + private static final Logger LOG = LoggerFactory.getInstance().getLogger( + VisualizationContext.class); + private final ActiveInventoryDataProvider aaiProvider; + + private int maxSelfLinkTraversalDepth; + private AtomicInteger numLinksDiscovered; + private AtomicInteger numSuccessfulLinkResolveFromCache; + private AtomicInteger numSuccessfulLinkResolveFromFromServer; + private AtomicInteger numFailedLinkResolve; + private AtomicInteger aaiWorkOnHand; + + private ActiveInventoryConfig aaiConfig; + private VisualizationConfig visualizationConfig; + private List shallowEntities; + + private AtomicInteger totalLinksRetrieved; + + private final long contextId; + private final String contextIdStr; + + private OxmModelLoader loader; + private ObjectMapper mapper; + private InlineMessage inlineMessage = null; + + private ExecutorService aaiExecutorService; + + /* + * The node cache is intended to be a flat structure indexed by a primary key to avoid needlessly + * re-requesting the same self-links over-and-over again, to speed up the overall render time and + * more importantly to reduce the network cost of determining information we already have. + */ + private ConcurrentHashMap nodeCache; + + /** + * Instantiates a new self link node collector. + * + * @param loader the loader + * @throws Exception the exception + */ + public VisualizationContext(long contextId, ActiveInventoryDataProvider aaiDataProvider, + ExecutorService aaiExecutorService, OxmModelLoader loader) throws Exception { + + this.contextId = contextId; + this.contextIdStr = "[Context-Id=" + contextId + "]"; + this.aaiProvider = aaiDataProvider; + this.aaiExecutorService = aaiExecutorService; + this.loader = loader; + + this.nodeCache = new ConcurrentHashMap(); + this.numLinksDiscovered = new AtomicInteger(0); + this.totalLinksRetrieved = new AtomicInteger(0); + this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0); + this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0); + this.numFailedLinkResolve = new AtomicInteger(0); + this.aaiWorkOnHand = new AtomicInteger(0); + + this.aaiConfig = ActiveInventoryConfig.getConfig(); + this.visualizationConfig = VisualizationConfig.getConfig(); + this.shallowEntities = aaiConfig.getAaiRestConfig().getShallowEntities(); + + this.maxSelfLinkTraversalDepth = visualizationConfig.getMaxSelfLinkTraversalDepth(); + + this.mapper = new ObjectMapper(); + mapper.setSerializationInclusion(Include.NON_EMPTY); + mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy()); + } + + public long getContextId() { + return contextId; + } + + /** + * A utility method for extracting all entity-type primary key values from a provided self-link + * and return a set of generic-query API keys. + * + * @param parentEntityType + * @param link + * @return a list of key values that can be used for this entity with the AAI generic-query API + */ + protected List extractQueryParamsFromSelfLink(String link) { + + List queryParams = new ArrayList(); + + if (link == null) { + LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR, "self link is null"); + return queryParams; + } + + Map entityDescriptors = loader.getEntityDescriptors(); + + try { + + URIBuilder urlBuilder = new URIBuilder(link); + String urlPath = urlBuilder.getPath(); + + OxmEntityDescriptor descriptor = null; + String[] urlPathElements = urlPath.split("/"); + List primaryKeyNames = null; + int index = 0; + String entityType = null; + + while (index < urlPathElements.length) { + + descriptor = entityDescriptors.get(urlPathElements[index]); + + if (descriptor != null) { + entityType = urlPathElements[index]; + primaryKeyNames = descriptor.getPrimaryKeyAttributeName(); + + /* + * Make sure from what ever index we matched the parent entity-type on that we can extract + * additional path elements for the primary key values. + */ + + if (index + primaryKeyNames.size() < urlPathElements.length) { + + for (String primaryKeyName : primaryKeyNames) { + index++; + queryParams.add(entityType + "." + primaryKeyName + ":" + urlPathElements[index]); + } + } else { + LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR, + "Could not extract query parametrs for entity-type = '" + entityType + + "' from self-link = " + link); + } + } + + index++; + } + + } catch (URISyntaxException exc) { + + LOG.error(AaiUiMsgs.QUERY_PARAM_EXTRACTION_ERROR, + "Error extracting query parameters from self-link = " + link + ". Error = " + + exc.getMessage()); + } + + return queryParams; + + } + + /** + * Decode complex attribute group. + * + * @param ain the ain + * @param attributeGroup the attribute group + * @return boolean indicating whether operation was successful (true), / failure(false). + */ + public boolean decodeComplexAttributeGroup(ActiveInventoryNode ain, JsonNode attributeGroup) { + + try { + + Iterator> entityArrays = attributeGroup.fields(); + Entry entityArray = null; + + if (entityArrays == null) { + LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, attributeGroup.toString()); + ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR); + return false; + } + + while (entityArrays.hasNext()) { + + entityArray = entityArrays.next(); + + String entityType = entityArray.getKey(); + JsonNode entityArrayObject = entityArray.getValue(); + + if (entityArrayObject.isArray()) { + + Iterator entityCollection = entityArrayObject.elements(); + JsonNode entity = null; + while (entityCollection.hasNext()) { + entity = entityCollection.next(); + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "decodeComplexAttributeGroup()," + + " entity = " + entity.toString()); + } + + /** + * Here's what we are going to do: + * + *
  • In the ActiveInventoryNode, on construction maintain a collection of queryParams + * that is added to for the purpose of discovering parent->child hierarchies. + * + *
  • When we hit this block of the code then we'll use the queryParams to feed the + * generic query to resolve the self-link asynchronously. + * + *
  • Upon successful link determination, then and only then will we create a new node + * in the nodeCache and process the child + * + */ + + ActiveInventoryNode newNode = new ActiveInventoryNode(); + newNode.setEntityType(entityType); + + /* + * This is partially a lie because we actually don't have a self-link for complex nodes + * discovered in this way. + */ + newNode.setSelfLinkProcessed(true); + newNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED, + NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK); + + /* + * copy parent query params into new child + */ + + if (SERVICE_INSTANCE.equals(entityType)) { + + /* + * 1707 AAI has an issue being tracked with AAI-8932 where the generic-query cannot be + * resolved if all the service-instance path keys are provided. The query only works + * if only the service-instance key and valude are passed due to a historical reason. + * A fix is being worked on for 1707, and when it becomes available we can revert this + * small change. + */ + + newNode.clearQueryParams(); + + } else { + + /* + * For all other entity-types we want to copy the parent query parameters into the new node + * query parameters. + */ + + for (String queryParam : ain.getQueryParams()) { + newNode.addQueryParam(queryParam); + } + + } + + + if (!addComplexGroupToNode(newNode, entity)) { + LOG.error(AaiUiMsgs.ATTRIBUTE_GROUP_FAILURE, "Failed to add child to parent for child = " + entity.toString()); + } + + if (!addNodeQueryParams(newNode)) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Error determining node id and key for node = " + newNode.dumpNodeTree(true) + + " skipping relationship processing"); + newNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.NODE_IDENTITY_ERROR); + return false; + } else { + + newNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED, + NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK); + + } + + + /* + * Order matters for the query params. We need to set the parent ones before the child + * node + */ + + String selfLinkQuery = + aaiProvider.getGenericQueryForSelfLink(entityType, newNode.getQueryParams()); + + /** + *
  • get the self-link + *
  • add it to the new node + *
  • generate node id + *
  • add node to node cache + *
  • add node id to parent outbound links list + *
  • process node children (should be automatic) (but don't query and resolve + * self-link as we already have all the data) + */ + + SelfLinkDeterminationTransaction txn = new SelfLinkDeterminationTransaction(); + + txn.setQueryString(selfLinkQuery); + txn.setNewNode(newNode); + txn.setParentNodeId(ain.getNodeId()); + aaiWorkOnHand.incrementAndGet(); + supplyAsync(new PerformSelfLinkDeterminationTask(txn, null, aaiProvider), + aaiExecutorService).whenComplete((nodeTxn, error) -> { + aaiWorkOnHand.decrementAndGet(); + if (error != null) { + LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_GENERIC, selfLinkQuery); + } else { + + OperationResult opResult = nodeTxn.getOpResult(); + + ActiveInventoryNode newChildNode = txn.getNewNode(); + + if (opResult != null && opResult.wasSuccessful()) { + + if (opResult.isResolvedLinkFailure()) { + numFailedLinkResolve.incrementAndGet(); + } + + if (opResult.isResolvedLinkFromCache()) { + numSuccessfulLinkResolveFromCache.incrementAndGet(); + } + + if (opResult.isResolvedLinkFromServer()) { + numSuccessfulLinkResolveFromFromServer.incrementAndGet(); + } + + /* + * extract the self-link from the operational result. + */ + + Collection entityLinks = new ArrayList(); + JsonNode genericQueryResult = null; + try { + genericQueryResult = + NodeUtils.convertJsonStrToJsonNode(nodeTxn.getOpResult().getResult()); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, JsonNode.class.toString(), exc.getMessage()); + } + + NodeUtils.extractObjectsByKey(genericQueryResult, "resource-link", + entityLinks); + + String selfLink = null; + + if (entityLinks.size() != 1) { + + LOG.error(AaiUiMsgs.SELF_LINK_DETERMINATION_FAILED_UNEXPECTED_LINKS, String.valueOf(entityLinks.size())); + + } else { + selfLink = ((JsonNode) entityLinks.toArray()[0]).asText(); + + newChildNode.setSelfLink(selfLink); + newChildNode.setNodeId(NodeUtils.generateUniqueShaDigest(selfLink)); + + String uri = NodeUtils.calculateEditAttributeUri(selfLink); + if (uri != null) { + newChildNode.addProperty(TierSupportUiConstants.URI_ATTR_NAME, uri); + } + + ActiveInventoryNode parent = nodeCache.get(txn.getParentNodeId()); + + if (parent != null) { + parent.addOutboundNeighbor(newChildNode.getNodeId()); + newChildNode.addInboundNeighbor(parent.getNodeId()); + } + + newChildNode.setSelfLinkPendingResolve(false); + newChildNode.setSelfLinkProcessed(true); + + newChildNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK); + + nodeCache.putIfAbsent(newChildNode.getNodeId(), newChildNode); + + } + + } else { + LOG.error(AaiUiMsgs.SELF_LINK_RETRIEVAL_FAILED, txn.getQueryString(), + String.valueOf(nodeTxn.getOpResult().getResultCode()), nodeTxn.getOpResult().getResult()); + newChildNode.setSelflinkRetrievalFailure(true); + newChildNode.setSelfLinkProcessed(true); + newChildNode.setSelfLinkPendingResolve(false); + + newChildNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_DETERMINATION_ERROR); + + } + + } + + }); + + } + + return true; + + } else { + LOG.error(AaiUiMsgs.UNHANDLED_OBJ_TYPE_FOR_ENTITY_TYPE, entityType); + } + + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Exception caught while" + + " decoding complex attribute group - " + exc.getMessage()); + } + + return false; + + } + + /** + * Process self link response. + * + * @param nodeId the node id + */ + private void processSelfLinkResponse(String nodeId) { + + if (nodeId == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link" + + " response because nodeId is null"); + return; + } + + ActiveInventoryNode ain = nodeCache.get(nodeId); + + if (ain == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Cannot process self link response" + + " because can't find node for id = " + nodeId); + return; + } + + JsonNode jsonNode = null; + + try { + jsonNode = mapper.readValue(ain.getOpResult().getResult(), JsonNode.class); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to marshal json" + + " response str into JsonNode with error, " + exc.getLocalizedMessage()); + ain.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR); + return; + } + + if (jsonNode == null) { + LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse json node str." + + " Parse resulted a null value."); + ain.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR); + return; + } + + Iterator> fieldNames = jsonNode.fields(); + Entry field = null; + + RelationshipList relationshipList = null; + + while (fieldNames.hasNext()) { + + field = fieldNames.next(); + String fieldName = field.getKey(); + + if ("relationship-list".equals(fieldName)) { + + try { + relationshipList = mapper.readValue(field.getValue().toString(), RelationshipList.class); + + if (relationshipList != null) { + ain.addRelationshipList(relationshipList); + } + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse relationship-list" + + " attribute. Parse resulted in error, " + exc.getLocalizedMessage()); + ain.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_ERROR); + return; + } + + } else { + + JsonNode nodeValue = field.getValue(); + + if (nodeValue != null && nodeValue.isValueNode()) { + + if (loader.getEntityDescriptor(fieldName) == null) { + + /* + * entity property name is not an entity, thus we can add this property name and value + * to our property set + */ + + ain.addProperty(fieldName, nodeValue.asText()); + + } + + } else { + + if (nodeValue.isArray()) { + + if (loader.getEntityDescriptor(fieldName) == null) { + + /* + * entity property name is not an entity, thus we can add this property name and value + * to our property set + */ + + ain.addProperty(field.getKey(), nodeValue.toString()); + + } + + } else { + + ain.addComplexGroup(nodeValue); + + } + + } + } + + } + + String uri = NodeUtils.calculateEditAttributeUri(ain.getSelfLink()); + if (uri != null) { + ain.addProperty(TierSupportUiConstants.URI_ATTR_NAME, uri); + } + + /* + * We need a special behavior for intermediate entities from the REST model + * + * Tenants are not top level entities, and when we want to visualization + * their children, we need to construct keys that include the parent entity query + * keys, the current entity type keys, and the child keys. We'll always have the + * current entity and children, but never the parent entity in the current (1707) REST + * data model. + * + * We have two possible solutions: + * + * 1) Try to use the custom-query approach to learn about the entity keys + * - this could be done, but it could be very expensive for large objects. When we do the first + * query to get a tenant, it will list all the in and out edges related to this entity, + * there is presently no way to filter this. But the approach could be made to work and it would be + * somewhat data-model driven, other than the fact that we have to first realize that the entity + * that is being searched for is not top-level entity. Once we have globally unique ids for resources + * this logic will not be needed and everything will be simpler. The only reason we are in this logic + * at all is to be able to calculate a url for the child entities so we can hash it to generate + * a globally unique id that can be safely used for the node. + * + * *2* Extract the keys from the pathed self-link. + * This is a bad solution and I don't like it but it will be fast for all resource types, as the + * information is already encoded in the URI. When we get to a point where we switch to a better + * globally unique entity identity model, then a lot of the code being used to calculate an entity url + * to in-turn generate a deterministic globally unique id will disappear. + * + * + * right now we have the following: + * + * - cloud-regions/cloud-region/{cloud-region-id}/{cloud-owner-id}/tenants/tenant/{tenant-id} + * + */ + + /* + * For all entity types use the self-link extraction method to be consistent. Once we have a + * globally unique identity mechanism for entities, this logic can be revisited. + */ + ain.clearQueryParams(); + ain.addQueryParams(extractQueryParamsFromSelfLink(ain.getSelfLink())); + + ain.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK); + + } + + /** + * Perform self link resolve. + * + * @param nodeId the node id + */ + private void performSelfLinkResolve(String nodeId) { + + if (nodeId == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Resolve of self-link" + + " has been skipped because provided nodeId is null"); + return; + } + + ActiveInventoryNode ain = nodeCache.get(nodeId); + + if (ain == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Failed to find node with id, " + nodeId + + ", from node cache. Resolve self-link method has been skipped."); + return; + } + + if (!ain.isSelfLinkPendingResolve()) { + + ain.setSelfLinkPendingResolve(true); + + // kick off async self-link resolution + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "About to process node in SELF_LINK_UNPROCESSED State, link = " + ain.getSelfLink()); + } + + numLinksDiscovered.incrementAndGet(); + + String depthModifier = DEPTH_ALL_MODIFIER; + + /* + * If the current node is the search target, we want to see everything the node has to offer + * from the self-link and not filter it to a single node. + */ + + if (shallowEntities.contains(ain.getEntityType()) && !ain.isRootNode()) { + depthModifier = NODES_ONLY_MODIFIER; + } + + NodeProcessingTransaction txn = new NodeProcessingTransaction(); + txn.setProcessingNode(ain); + txn.setRequestParameters(depthModifier); + aaiWorkOnHand.incrementAndGet(); + supplyAsync( + new PerformNodeSelfLinkProcessingTask(txn, depthModifier, aaiProvider), + aaiExecutorService).whenComplete((nodeTxn, error) -> { + aaiWorkOnHand.decrementAndGet(); + if (error != null) { + + /* + * an error processing the self link should probably result in the node processing + * state shifting to ERROR + */ + + nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true); + + nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESOLVE_ERROR); + + nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false); + + } else { + + totalLinksRetrieved.incrementAndGet(); + + OperationResult opResult = nodeTxn.getOpResult(); + + if (opResult != null && opResult.wasSuccessful()) { + + if (opResult.isResolvedLinkFailure()) { + numFailedLinkResolve.incrementAndGet(); + } + + if (opResult.isResolvedLinkFromCache()) { + numSuccessfulLinkResolveFromCache.incrementAndGet(); + } + + if (opResult.isResolvedLinkFromServer()) { + numSuccessfulLinkResolveFromFromServer.incrementAndGet(); + } + + // success path + nodeTxn.getProcessingNode().setOpResult(opResult); + nodeTxn.getProcessingNode().changeState( + NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED, + NodeProcessingAction.SELF_LINK_RESOLVE_OK); + + nodeTxn.getProcessingNode().setSelfLinkProcessed(true); + nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false); + + } else { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, "Self Link retrieval for link," + + txn.getSelfLinkWithModifiers() + ", failed with error code," + + nodeTxn.getOpResult().getResultCode() + ", and message," + + nodeTxn.getOpResult().getResult()); + + nodeTxn.getProcessingNode().setSelflinkRetrievalFailure(true); + nodeTxn.getProcessingNode().setSelfLinkProcessed(true); + + nodeTxn.getProcessingNode().changeState(NodeProcessingState.ERROR, + NodeProcessingAction.SELF_LINK_RESOLVE_ERROR); + + nodeTxn.getProcessingNode().setSelfLinkPendingResolve(false); + + } + } + + }); + + } + + } + + + /** + * Process neighbors. + * + * @param nodeId the node id + */ + private void processNeighbors(String nodeId) { + + if (nodeId == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process" + + " neighbors because nodeId is null."); + return; + } + + ActiveInventoryNode ain = nodeCache.get(nodeId); + + if (ain == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESS_NEIGHBORS_ERROR, "Failed to process" + + " neighbors because node could not be found in nodeCache with id, " + nodeId); + return; + } + + /* + * process complex attribute and relationships + */ + + boolean neighborsProcessedSuccessfully = true; + + for (JsonNode n : ain.getComplexGroups()) { + neighborsProcessedSuccessfully &= decodeComplexAttributeGroup(ain, n); + } + + for (RelationshipList relationshipList : ain.getRelationshipLists()) { + neighborsProcessedSuccessfully &= addSelfLinkRelationshipChildren(ain, relationshipList); + } + + + if (neighborsProcessedSuccessfully) { + ain.changeState(NodeProcessingState.READY, NodeProcessingAction.NEIGHBORS_PROCESSED_OK); + } else { + ain.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR); + } + + + /* + * If neighbors fail to process, there is already a call to change the state within the + * relationship and neighbor processing functions. + */ + + } + + /** + * Find and mark root node. + * + * @param queryParams the query params + * @return true, if successful + */ + private boolean findAndMarkRootNode(QueryParams queryParams) { + + for (ActiveInventoryNode cacheNode : nodeCache.values()) { + + if (queryParams.getSearchTargetNodeId().equals(cacheNode.getNodeId())) { + cacheNode.setNodeDepth(0); + cacheNode.setRootNode(true); + LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId()); + return true; + } + } + + return false; + + } + + /** + * Process current node states. + * + * @param rootNodeDiscovered the root node discovered + */ + private void processCurrentNodeStates(boolean rootNodeDiscovered) { + /* + * Force an evaluation of node depths before determining if we should limit state-based + * traversal or processing. + */ + if (rootNodeDiscovered) { + evaluateNodeDepths(); + } + + for (ActiveInventoryNode cacheNode : nodeCache.values()) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "processCurrentNodeState(), nid = " + + cacheNode.getNodeId() + " , nodeDepth = " + cacheNode.getNodeDepth()); + } + + switch (cacheNode.getState()) { + + case INIT: { + processInitialState(cacheNode.getNodeId()); + break; + } + + case READY: + case ERROR: { + break; + } + + case SELF_LINK_UNRESOLVED: { + performSelfLinkResolve(cacheNode.getNodeId()); + break; + } + + case SELF_LINK_RESPONSE_UNPROCESSED: { + processSelfLinkResponse(cacheNode.getNodeId()); + break; + } + + case NEIGHBORS_UNPROCESSED: { + + /* + * We use the rootNodeDiscovered flag to ignore depth retrieval thresholds until the root + * node is identified. Then the evaluative depth calculations should re-balance the graph + * around the root node. + */ + + if (!rootNodeDiscovered || cacheNode.getNodeDepth() < VisualizationConfig.getConfig() + .getMaxSelfLinkTraversalDepth()) { + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "SLNC::processCurrentNodeState() -- Node at max depth," + + " halting processing at current state = -- " + + cacheNode.getState() + " nodeId = " + cacheNode.getNodeId()); + } + + + + processNeighbors(cacheNode.getNodeId()); + + } + + break; + } + default: + break; + + + + } + + } + + } + + /** + * Adds the complex group to node. + * + * @param targetNode the target node + * @param attributeGroup the attribute group + * @return true, if successful + */ + private boolean addComplexGroupToNode(ActiveInventoryNode targetNode, JsonNode attributeGroup) { + + if (attributeGroup == null) { + targetNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_OK); + return false; + } + + RelationshipList relationshipList = null; + + if (attributeGroup.isObject()) { + + Iterator> fields = attributeGroup.fields(); + Entry field = null; + String fieldName; + JsonNode fieldValue; + + while (fields.hasNext()) { + field = fields.next(); + fieldName = field.getKey(); + fieldValue = field.getValue(); + + if (fieldValue.isObject()) { + + if (fieldName.equals("relationship-list")) { + + try { + relationshipList = + mapper.readValue(field.getValue().toString(), RelationshipList.class); + + if (relationshipList != null) { + targetNode.addRelationshipList(relationshipList); + } + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SELF_LINK_JSON_PARSE_ERROR, "Failed to parse" + + " relationship-list attribute. Parse resulted in error, " + + exc.getLocalizedMessage()); + targetNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.COMPLEX_ATTRIBUTE_GROUP_PARSE_ERROR); + return false; + } + + } else { + targetNode.addComplexGroup(fieldValue); + } + + } else if (fieldValue.isArray()) { + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Unexpected array type with a key = " + fieldName); + } + } else if (fieldValue.isValueNode()) { + if (loader.getEntityDescriptor(field.getKey()) == null) { + /* + * property key is not an entity type, add it to our property set. + */ + targetNode.addProperty(field.getKey(), fieldValue.asText()); + } + + } + } + + } else if (attributeGroup.isArray()) { + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Unexpected array type for attributeGroup = " + attributeGroup); + } + } else if (attributeGroup.isValueNode()) { + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Unexpected value type for attributeGroup = " + attributeGroup); + } + } + + return true; + } + + public int getNumSuccessfulLinkResolveFromCache() { + return numSuccessfulLinkResolveFromCache.get(); + } + + public int getNumSuccessfulLinkResolveFromFromServer() { + return numSuccessfulLinkResolveFromFromServer.get(); + } + + public int getNumFailedLinkResolve() { + return numFailedLinkResolve.get(); + } + + public InlineMessage getInlineMessage() { + return inlineMessage; + } + + public void setInlineMessage(InlineMessage inlineMessage) { + this.inlineMessage = inlineMessage; + } + + public void setMaxSelfLinkTraversalDepth(int depth) { + this.maxSelfLinkTraversalDepth = depth; + } + + public int getMaxSelfLinkTraversalDepth() { + return this.maxSelfLinkTraversalDepth; + } + + public ConcurrentHashMap getNodeCache() { + return nodeCache; + } + + /** + * Gets the relationship primary key values. + * + * @param r the r + * @param entityType the entity type + * @param pkeyNames the pkey names + * @return the relationship primary key values + */ + private String getRelationshipPrimaryKeyValues(Relationship r, String entityType, + List pkeyNames) { + + StringBuilder sb = new StringBuilder(64); + + if (pkeyNames.size() > 0) { + String primaryKey = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(0)); + if (primaryKey != null) { + + sb.append(primaryKey); + + } else { + // this should be a fatal error because unless we can + // successfully retrieve all the expected keys we'll end up + // with a garbage node + LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract" + + " keyName, " + entityType + "." + pkeyNames.get(0) + + ", from relationship data, " + r.toString()); + return null; + } + + for (int i = 1; i < pkeyNames.size(); i++) { + + String kv = extractKeyValueFromRelationData(r, entityType + "." + pkeyNames.get(i)); + if (kv != null) { + sb.append("/").append(kv); + } else { + // this should be a fatal error because unless we can + // successfully retrieve all the expected keys we'll end up + // with a garbage node + LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: failed to extract keyName, " + + entityType + "." + pkeyNames.get(i) + + ", from relationship data, " + r.toString()); + return null; + } + } + + return sb.toString(); + + } + + return null; + + } + + /** + * Extract key value from relation data. + * + * @param r the r + * @param keyName the key name + * @return the string + */ + private String extractKeyValueFromRelationData(Relationship r, String keyName) { + + RelationshipData[] rdList = r.getRelationshipData(); + + for (RelationshipData relData : rdList) { + + if (relData.getRelationshipKey().equals(keyName)) { + return relData.getRelationshipValue(); + } + } + + return null; + } + + /** + * Determine node id and key. + * + * @param ain the ain + * @return true, if successful + */ + private boolean addNodeQueryParams(ActiveInventoryNode ain) { + + if (ain == null) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "ActiveInventoryNode is null"); + return false; + } + + List pkeyNames = + loader.getEntityDescriptor(ain.getEntityType()).getPrimaryKeyAttributeName(); + + if (pkeyNames == null || pkeyNames.size() == 0) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Primary key names is empty"); + return false; + } + + StringBuilder sb = new StringBuilder(64); + + if (pkeyNames.size() > 0) { + String primaryKey = ain.getProperties().get(pkeyNames.get(0)); + if (primaryKey != null) { + sb.append(primaryKey); + } else { + // this should be a fatal error because unless we can + // successfully retrieve all the expected keys we'll end up + // with a garbage node + LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, " + + pkeyNames.get(0) + ", from entity properties"); + return false; + } + + for (int i = 1; i < pkeyNames.size(); i++) { + + String kv = ain.getProperties().get(pkeyNames.get(i)); + if (kv != null) { + sb.append("/").append(kv); + } else { + // this should be a fatal error because unless we can + // successfully retrieve all the expected keys we'll end up + // with a garbage node + LOG.error(AaiUiMsgs.EXTRACTION_ERROR, "ERROR: Failed to extract keyName, " + + pkeyNames.get(i) + ", from entity properties"); + return false; + } + } + + /*final String nodeId = NodeUtils.generateUniqueShaDigest(ain.getEntityType(), + NodeUtils.concatArray(pkeyNames, "/"), sb.toString());*/ + + //ain.setNodeId(nodeId); + ain.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/")); + ain.setPrimaryKeyValue(sb.toString()); + + if (ain.getEntityType() != null && ain.getPrimaryKeyName() != null + && ain.getPrimaryKeyValue() != null) { + ain.addQueryParam( + ain.getEntityType() + "." + ain.getPrimaryKeyName() + ":" + ain.getPrimaryKeyValue()); + } + return true; + + } + + return false; + + } + + /** + * Adds the self link relationship children. + * + * @param processingNode the processing node + * @param relationshipList the relationship list + * @return true, if successful + */ + private boolean addSelfLinkRelationshipChildren(ActiveInventoryNode processingNode, + RelationshipList relationshipList) { + + if (relationshipList == null) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "No relationships added to parent node = " + + processingNode.getNodeId() + " because relationshipList is empty"); + processingNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR); + return false; + } + + OxmModelLoader modelLoader = OxmModelLoader.getInstance(); + + Relationship[] relationshipArray = relationshipList.getRelationshipList(); + OxmEntityDescriptor descriptor = null; + String repairedSelfLink = null; + + if (relationshipArray != null) { + + ActiveInventoryNode newNode = null; + + for (Relationship r : relationshipArray) { + + repairedSelfLink = aaiConfig.repairSelfLink(r.getRelatedLink()); + + String nodeId = NodeUtils.generateUniqueShaDigest(repairedSelfLink); + + if (nodeId == null) { + + LOG.error(AaiUiMsgs.SKIPPING_RELATIONSHIP, r.toString()); + processingNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.NODE_IDENTITY_ERROR); + return false; + } + + newNode = new ActiveInventoryNode(); + + String entityType = r.getRelatedTo(); + + if (r.getRelationshipData() != null) { + for (RelationshipData rd : r.getRelationshipData()) { + newNode.addQueryParam(rd.getRelationshipKey() + ":" + rd.getRelationshipValue()); + } + } + + descriptor = modelLoader.getEntityDescriptor(r.getRelatedTo()); + + newNode.setNodeId(nodeId); + newNode.setEntityType(entityType); + newNode.setSelfLink(repairedSelfLink); + + processingNode.addOutboundNeighbor(nodeId); + + if (descriptor != null) { + + List pkeyNames = descriptor.getPrimaryKeyAttributeName(); + + newNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED, + NodeProcessingAction.SELF_LINK_SET); + + newNode.setPrimaryKeyName(NodeUtils.concatArray(pkeyNames, "/")); + + String primaryKeyValues = getRelationshipPrimaryKeyValues(r, entityType, pkeyNames); + newNode.setPrimaryKeyValue(primaryKeyValues); + + } else { + + LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, + "Failed to parse entity because OXM descriptor could not be found for type = " + + r.getRelatedTo()); + + newNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.NEIGHBORS_PROCESSED_ERROR); + + } + + if (nodeCache.putIfAbsent(nodeId, newNode) != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Failed to add node to nodeCache because it already exists. Node id = " + + newNode.getNodeId()); + } + } + + } + + } + + return true; + + } + + /** + * Process initial state. + * + * @param nodeId the node id + */ + private void processInitialState(String nodeId) { + + if (nodeId == null) { + LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node id is null"); + return; + } + + ActiveInventoryNode cachedNode = nodeCache.get(nodeId); + + if (cachedNode == null) { + LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_INITIAL_STATE, "Node cannot be" + + " found for nodeId, " + nodeId); + return; + } + + if (cachedNode.getSelfLink() == null) { + + if (cachedNode.getNodeId() == null ) { + + /* + * if the self link is null at the INIT state, which could be valid if this node is a + * complex attribute group which didn't originate from a self-link, but in that situation + * both the node id and node key should already be set. + */ + + cachedNode.changeState(NodeProcessingState.ERROR, NodeProcessingAction.NODE_IDENTITY_ERROR); + + } + + if (cachedNode.getNodeId() != null) { + + /* + * This should be the success path branch if the self-link is not set + */ + + cachedNode.changeState(NodeProcessingState.SELF_LINK_RESPONSE_UNPROCESSED, + NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK); + + } + + } else { + + if (cachedNode.hasResolvedSelfLink()) { + LOG.error(AaiUiMsgs.INVALID_RESOLVE_STATE_DURING_INIT); + cachedNode.changeState(NodeProcessingState.ERROR, + NodeProcessingAction.UNEXPECTED_STATE_TRANSITION); + } else { + cachedNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED, + NodeProcessingAction.SELF_LINK_SET); + } + } + } + + /** + * Process skeleton node. + * + * @param skeletonNode the skeleton node + * @param queryParams the query params + */ + private void processSearchableEntity(SearchableEntity searchTargetEntity, QueryParams queryParams) { + + if (searchTargetEntity == null) { + return; + } + + if (searchTargetEntity.getId() == null) { + LOG.error(AaiUiMsgs.FAILED_TO_PROCESS_SKELETON_NODE, "Failed to process skeleton" + + " node because nodeId is null for node, " + searchTargetEntity.getLink()); + return; + } + + ActiveInventoryNode newNode = new ActiveInventoryNode(); + + newNode.setNodeId(searchTargetEntity.getId()); + newNode.setEntityType(searchTargetEntity.getEntityType()); + newNode.setPrimaryKeyName(getEntityTypePrimaryKeyName(searchTargetEntity.getEntityType())); + newNode.setPrimaryKeyValue(searchTargetEntity.getEntityPrimaryKeyValue()); + + if (newNode.getEntityType() != null && newNode.getPrimaryKeyName() != null + && newNode.getPrimaryKeyValue() != null) { + newNode.addQueryParam( + newNode.getEntityType() + "." + newNode.getPrimaryKeyName() + ":" + newNode.getPrimaryKeyValue()); + } + /* + * This code may need some explanation. In any graph there will be a single root node. The root + * node is really the center of the universe, and for now, we are tagging the search target as + * the root node. Everything else in the visualization of the graph will be centered around this + * node as the focal point of interest. + * + * Due to it's special nature, there will only ever be one root node, and it's node depth will + * always be equal to zero. + */ + + if (queryParams.getSearchTargetNodeId().equals(newNode.getNodeId())) { + newNode.setNodeDepth(0); + newNode.setRootNode(true); + LOG.info(AaiUiMsgs.ROOT_NODE_DISCOVERED, queryParams.getSearchTargetNodeId()); + } + + newNode.setSelfLink(searchTargetEntity.getLink()); + + nodeCache.putIfAbsent(newNode.getNodeId(), newNode); + } + + /** + * Checks for out standing work. + * + * @return true, if successful + */ + private boolean hasOutStandingWork() { + + int numNodesWithPendingStates = 0; + + /* + * Force an evaluation of node depths before determining if we should limit state-based + * traversal or processing. + */ + + evaluateNodeDepths(); + + for (ActiveInventoryNode n : nodeCache.values()) { + + switch (n.getState()) { + + case READY: + case ERROR: { + // do nothing, these are our normal + // exit states + break; + } + + case NEIGHBORS_UNPROCESSED: { + + if (n.getNodeDepth() < VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()) { + /* + * Only process our neighbors relationships if our current depth is less than the max + * depth + */ + numNodesWithPendingStates++; + } + + break; + } + + default: { + + /* + * for all other states, there is work to be done + */ + numNodesWithPendingStates++; + } + + } + + } + + LOG.debug(AaiUiMsgs.OUTSTANDING_WORK_PENDING_NODES, String.valueOf(numNodesWithPendingStates)); + + return (numNodesWithPendingStates > 0); + + } + + /** + * Process self links. + * + * @param skeletonNode the skeleton node + * @param queryParams the query params + */ + public void processSelfLinks(SearchableEntity searchtargetEntity, QueryParams queryParams) { + + try { + + if (searchtargetEntity == null) { + LOG.error(AaiUiMsgs.SELF_LINK_PROCESSING_ERROR, contextIdStr + " - Failed to" + + " processSelfLinks, searchtargetEntity is null"); + return; + } + + processSearchableEntity(searchtargetEntity, queryParams); + + long startTimeInMs = System.currentTimeMillis(); + + /* + * wait until all transactions are complete or guard-timer expires. + */ + + long totalResolveTime = 0; + boolean hasOutstandingWork = hasOutStandingWork(); + boolean outstandingWorkGuardTimerFired = false; + long maxGuardTimeInMs = 5000; + long guardTimeInMs = 0; + boolean foundRootNode = false; + + + /* + * TODO: Put a count-down-latch in place of the while loop, but if we do that then + * we'll need to decouple the visualization processing from the main thread so it can continue to process while + * the main thread is waiting on for count-down-latch gate to open. This may also be easier once we move to the + * VisualizationService + VisualizationContext ideas. + */ + + + while (hasOutstandingWork || !outstandingWorkGuardTimerFired) { + + if (!foundRootNode) { + foundRootNode = findAndMarkRootNode(queryParams); + } + + processCurrentNodeStates(foundRootNode); + + verifyOutboundNeighbors(); + + try { + Thread.sleep(500); + } catch (InterruptedException exc) { + LOG.error(AaiUiMsgs.PROCESSING_LOOP_INTERUPTED, exc.getMessage()); + return; + } + + totalResolveTime = (System.currentTimeMillis() - startTimeInMs); + + if (!hasOutstandingWork) { + + guardTimeInMs += 500; + + if (guardTimeInMs > maxGuardTimeInMs) { + outstandingWorkGuardTimerFired = true; + } + } else { + guardTimeInMs = 0; + } + + hasOutstandingWork = hasOutStandingWork(); + + } + + long opTime = System.currentTimeMillis() - startTimeInMs; + + LOG.info(AaiUiMsgs.ALL_TRANSACTIONS_RESOLVED, String.valueOf(totalResolveTime), + String.valueOf(totalLinksRetrieved.get()), String.valueOf(opTime)); + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.VISUALIZATION_OUTPUT_ERROR, exc.getMessage()); + } + + } + + /** + * Verify outbound neighbors. + */ + private void verifyOutboundNeighbors() { + + for (ActiveInventoryNode srcNode : nodeCache.values()) { + + for (String targetNodeId : srcNode.getOutboundNeighbors()) { + + ActiveInventoryNode targetNode = nodeCache.get(targetNodeId); + + if (targetNode != null && srcNode.getNodeId() != null) { + + targetNode.addInboundNeighbor(srcNode.getNodeId()); + + if (VisualizationConfig.getConfig().makeAllNeighborsBidirectional()) { + targetNode.addOutboundNeighbor(srcNode.getNodeId()); + } + + } + + } + + } + + } + + /** + * Evaluate node depths. + */ + private void evaluateNodeDepths() { + + int numChanged = -1; + int numAttempts = 0; + + while (numChanged != 0) { + + numChanged = 0; + numAttempts++; + + for (ActiveInventoryNode srcNode : nodeCache.values()) { + + if (srcNode.getState() == NodeProcessingState.INIT) { + + /* + * this maybe the only state that we don't want to to process the node depth on, because + * typically it won't have any valid fields set, and it may remain in a partial state + * until we have processed the self-link. + */ + + continue; + + } + + for (String targetNodeId : srcNode.getOutboundNeighbors()) { + ActiveInventoryNode targetNode = nodeCache.get(targetNodeId); + + if (targetNode != null) { + + if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) { + numChanged++; + } + } + } + + for (String targetNodeId : srcNode.getInboundNeighbors()) { + ActiveInventoryNode targetNode = nodeCache.get(targetNodeId); + + if (targetNode != null) { + + if (targetNode.changeDepth(srcNode.getNodeDepth() + 1)) { + numChanged++; + } + } + } + } + + if (numAttempts >= MAX_DEPTH_EVALUATION_ATTEMPTS) { + LOG.info(AaiUiMsgs.MAX_EVALUATION_ATTEMPTS_EXCEEDED); + return; + } + + } + + if (LOG.isDebugEnabled()) { + if (numAttempts > 0) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Evaluate node depths completed in " + numAttempts + " attempts"); + } else { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Evaluate node depths completed in 0 attempts because all nodes at correct depth"); + } + } + + } + + + /** + * Gets the entity type primary key name. + * + * @param entityType the entity type + * @return the entity type primary key name + */ + + + private String getEntityTypePrimaryKeyName(String entityType) { + + if (entityType == null) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary key" + + " name because entity type is null"); + return null; + } + + OxmEntityDescriptor descriptor = loader.getEntityDescriptor(entityType); + + if (descriptor == null) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "oxm entity" + + " descriptor for entityType = " + entityType); + return null; + } + + List pkeyNames = descriptor.getPrimaryKeyAttributeName(); + + if (pkeyNames == null || pkeyNames.size() == 0) { + LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, "node primary" + + " key because descriptor primary key names is empty"); + return null; + } + + return NodeUtils.concatArray(pkeyNames, "/"); + + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationService.java b/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationService.java new file mode 100644 index 0000000..aed7cd2 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationService.java @@ -0,0 +1,387 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.services; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; + +import javax.servlet.ServletException; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.aai.ActiveInventoryAdapter; +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryRestConfig; +import org.openecomp.sparky.dal.cache.EntityCache; +import org.openecomp.sparky.dal.cache.PersistentEntityCache; +import org.openecomp.sparky.dal.elasticsearch.ElasticSearchAdapter; +import org.openecomp.sparky.dal.elasticsearch.ElasticSearchDataProvider; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestClientBuilder; +import org.openecomp.sparky.dal.rest.RestfulDataAccessor; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.synchronizer.entity.SearchableEntity; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.viewandinspect.config.VisualizationConfig; +import org.openecomp.sparky.viewandinspect.entity.ActiveInventoryNode; +import org.openecomp.sparky.viewandinspect.entity.D3VisualizationOutput; +import org.openecomp.sparky.viewandinspect.entity.GraphMeta; +import org.openecomp.sparky.viewandinspect.entity.QueryParams; +import org.openecomp.sparky.viewandinspect.entity.QueryRequest; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class VisualizationService { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(VisualizationService.class); + + private OxmModelLoader loader; + private ObjectMapper mapper = new ObjectMapper(); + + private final ActiveInventoryDataProvider aaiProvider; + private final ActiveInventoryRestConfig aaiRestConfig; + private final ElasticSearchDataProvider esProvider; + private final ElasticSearchConfig esConfig; + private final ExecutorService aaiExecutorService; + + private ConcurrentHashMap contextMap; + private final SecureRandom secureRandom; + + private ActiveInventoryConfig aaiConfig; + private VisualizationConfig visualizationConfig; + + public VisualizationService(OxmModelLoader loader) throws Exception { + this.loader = loader; + + aaiRestConfig = ActiveInventoryConfig.getConfig().getAaiRestConfig(); + + EntityCache cache = null; + secureRandom = new SecureRandom(); + + ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + + if (aaiRestConfig.isCacheEnabled()) { + cache = new PersistentEntityCache(aaiRestConfig.getStorageFolderOverride(), + aaiRestConfig.getNumCacheWorkers()); + + aaiAdapter.setCacheEnabled(true); + aaiAdapter.setEntityCache(cache); + } + + this.aaiProvider = aaiAdapter; + + RestClientBuilder esClientBuilder = new RestClientBuilder(); + esClientBuilder.setUseHttps(false); + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(esClientBuilder); + this.esConfig = ElasticSearchConfig.getConfig(); + this.esProvider = new ElasticSearchAdapter(nonCachingRestProvider, this.esConfig); + + this.mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + this.contextMap = new ConcurrentHashMap(); + this.visualizationConfig = VisualizationConfig.getConfig(); + this.aaiConfig = ActiveInventoryConfig.getConfig(); + this.aaiExecutorService = NodeUtils.createNamedExecutor("SLNC-WORKER", + aaiConfig.getAaiRestConfig().getNumResolverWorkers(), LOG); + } + + public OxmModelLoader getLoader() { + return loader; + } + + public void setLoader(OxmModelLoader loader) { + this.loader = loader; + } + + /** + * Analyze query request body. + * + * @param queryRequestJson the query request json + * @return the query request + */ + + public QueryRequest analyzeQueryRequestBody(String queryRequestJson) { + + + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "analyzeQueryRequestBody()," + " queryRequestJson = " + queryRequestJson); + + ObjectMapper nonEmptyMapper = new ObjectMapper(); + nonEmptyMapper.setSerializationInclusion(Include.NON_EMPTY); + + QueryRequest queryBody = null; + + try { + queryBody = nonEmptyMapper.readValue(queryRequestJson, QueryRequest.class); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.EXCEPTION_CAUGHT, "Analyzing query request body.", + exc.getLocalizedMessage()); + } + + return queryBody; + + } + + /** + * Log optime. + * + * @param method the method + * @param opStartTimeInMs the op start time in ms + */ + private void logOptime(String method, long opStartTimeInMs) { + LOG.info(AaiUiMsgs.OPERATION_TIME, method, + String.valueOf(System.currentTimeMillis() - opStartTimeInMs)); + } + + private SearchableEntity extractSearchableEntityFromElasticEntity(OperationResult operationResult) { + if (operationResult == null || !operationResult.wasSuccessful()) { + // error, return empty collection + return null; + } + + SearchableEntity sourceEntity = null; + if (operationResult.wasSuccessful()) { + + try { + JsonNode elasticValue = mapper.readValue(operationResult.getResult(), JsonNode.class); + + if (elasticValue != null) { + JsonNode sourceField = elasticValue.get("_source"); + + if (sourceField != null) { + sourceEntity = new SearchableEntity(); + + String entityType = NodeUtils.extractFieldValueFromObject(sourceField, "entityType"); + sourceEntity.setEntityType(entityType); + String entityPrimaryKeyValue = NodeUtils.extractFieldValueFromObject(sourceField, "entityPrimaryKeyValue"); + sourceEntity.setEntityPrimaryKeyValue(entityPrimaryKeyValue); + String link = NodeUtils.extractFieldValueFromObject(sourceField, "link"); + sourceEntity.setLink(link); + String lastmodTimestamp = NodeUtils.extractFieldValueFromObject(sourceField, "lastmodTimestamp"); + sourceEntity.setEntityTimeStamp(lastmodTimestamp); + } + } + } catch (IOException ioe) { + LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, "a json node ", ioe.getLocalizedMessage()); + } + } + return sourceEntity; + } + + /** + * Builds the visualization using generic query. + * + * @param queryRequest the query request + * @return the operation result + */ + public OperationResult buildVisualizationUsingGenericQuery(QueryRequest queryRequest) { + + OperationResult returnValue = new OperationResult(); + OperationResult dataCollectionResult = null; + QueryParams queryParams = null; + SearchableEntity sourceEntity = null; + + try { + + /* + * Here is where we need to make a dip to elastic-search for the self-link by entity-id (link + * hash). + */ + dataCollectionResult = esProvider.retrieveEntityById(queryRequest.getHashId()); + sourceEntity = extractSearchableEntityFromElasticEntity(dataCollectionResult); + + if (sourceEntity != null) { + sourceEntity.generateId(); + } + + queryParams = new QueryParams(); + queryParams.setSearchTargetNodeId(queryRequest.getHashId()); + + } catch (Exception e1) { + LOG.error(AaiUiMsgs.FAILED_TO_GET_NODES_QUERY_RESULT, e1.getLocalizedMessage()); + dataCollectionResult = new OperationResult(500, "Failed to get nodes-query result from AAI"); + } + + if (dataCollectionResult.getResultCode() == 200) { + + String d3OutputJsonOutput = null; + + try { + + d3OutputJsonOutput = getVisualizationOutputBasedonGenericQuery(sourceEntity, queryParams); + + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Generated D3" + " output as json = " + d3OutputJsonOutput); + } + + if (d3OutputJsonOutput != null) { + returnValue.setResultCode(200); + returnValue.setResult(d3OutputJsonOutput); + } else { + returnValue.setResult(500, "Failed to generate D3 graph visualization"); + } + + } catch (Exception exc) { + returnValue.setResult(500, + "Failed to generate D3 graph visualization, due to a servlet exception."); + LOG.error(AaiUiMsgs.ERROR_D3_GRAPH_VISUALIZATION, exc.getLocalizedMessage()); + } + } else { + returnValue.setResult(dataCollectionResult.getResultCode(), dataCollectionResult.getResult()); + } + + return returnValue; + + } + + /** + * Gets the visualization output basedon generic query. + * + * @param searchtargetEntity entity that will be used to start visualization flow + * @param queryParams the query params + * @return the visualization output basedon generic query + * @throws ServletException the servlet exception + */ + private String getVisualizationOutputBasedonGenericQuery(SearchableEntity searchtargetEntity, + QueryParams queryParams) throws ServletException { + + long opStartTimeInMs = System.currentTimeMillis(); + + VisualizationTransformer transformer = null; + try { + transformer = new VisualizationTransformer(); + } catch (Exception exc) { + throw new ServletException( + "Failed to create VisualizationTransformer instance because of execption", exc); + } + + VisualizationContext visContext = null; + long contextId = secureRandom.nextLong(); + try { + visContext = new VisualizationContext(contextId, aaiProvider, aaiExecutorService, loader); + contextMap.putIfAbsent(contextId, visContext); + } catch (Exception e1) { + LOG.error(AaiUiMsgs.EXCEPTION_CAUGHT, + "While building Visualization Context, " + e1.getLocalizedMessage()); + throw new ServletException(e1); + } + + String jsonResponse = null; + + long startTimeInMs = System.currentTimeMillis(); + + visContext.processSelfLinks(searchtargetEntity, queryParams); + contextMap.remove(contextId); + + logOptime("collectSelfLinkNodes()", startTimeInMs); + + /* + * Flatten the graphs into a set of Graph and Link nodes. In this method I want the node graph + * resulting from the edge-tag-query to be represented first, and then we'll layer in + * relationship data. + */ + long overlayDataStartTimeInMs = System.currentTimeMillis(); + + Map cachedNodeMap = visContext.getNodeCache(); + + if (LOG.isDebugEnabled()) { + + StringBuilder sb = new StringBuilder(128); + + sb.append("\nCached Node Map:\n"); + for (String k : cachedNodeMap.keySet()) { + sb.append("\n----"); + sb.append("\n").append(cachedNodeMap.get(k).dumpNodeTree(true)); + } + + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, sb.toString()); + } + + transformer.buildFlatNodeArrayFromGraphCollection(cachedNodeMap); + transformer.buildLinksFromGraphCollection(cachedNodeMap); + + /* + * - Apply configuration-driven styling + * - Build the final transformation response object + * - Use information we have to populate the GraphMeta object + */ + + transformer.addSearchTargetAttributesToRootNode(); + + GraphMeta graphMeta = new GraphMeta(); + + D3VisualizationOutput output = null; + try { + output = transformer + .generateVisualizationOutput((System.currentTimeMillis() - opStartTimeInMs), graphMeta); + } catch (JsonProcessingException exc) { + throw new ServletException("Caught an exception while generation visualization output", exc); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.FAILURE_TO_PROCESS_REQUEST, exc.getLocalizedMessage()); + } + + output.setInlineMessage(visContext.getInlineMessage()); + output.getGraphMeta().setNumLinkResolveFailed(visContext.getNumFailedLinkResolve()); + output.getGraphMeta().setNumLinksResolvedSuccessfullyFromCache( + visContext.getNumSuccessfulLinkResolveFromCache()); + output.getGraphMeta().setNumLinksResolvedSuccessfullyFromServer( + visContext.getNumSuccessfulLinkResolveFromFromServer()); + + try { + jsonResponse = transformer.convertVisualizationOutputToJson(output); + } catch (JsonProcessingException jpe) { + throw new ServletException( + "Caught an exception while converting visualization output to json", jpe); + } + + logOptime("[build flat node array, add relationship data, search target," + + " color scheme, and generate visualization output]", overlayDataStartTimeInMs); + + logOptime("doFilter()", opStartTimeInMs); + + return jsonResponse; + + } + + public void shutdown() { + aaiProvider.shutdown(); + aaiExecutorService.shutdown(); + esProvider.shutdown(); + } +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationTransformer.java b/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationTransformer.java new file mode 100644 index 0000000..a6803a6 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/services/VisualizationTransformer.java @@ -0,0 +1,320 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.services; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.util.ConfigHelper; +import org.openecomp.sparky.viewandinspect.config.VisualizationConfig; +import org.openecomp.sparky.viewandinspect.entity.ActiveInventoryNode; +import org.openecomp.sparky.viewandinspect.entity.D3VisualizationOutput; +import org.openecomp.sparky.viewandinspect.entity.GraphMeta; +import org.openecomp.sparky.viewandinspect.entity.JsonNode; +import org.openecomp.sparky.viewandinspect.entity.JsonNodeLink; +import org.openecomp.sparky.viewandinspect.entity.NodeDebug; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; + +/** + * The idea here is to receive a collection of graphs and then fold them together (or not) based on + * configuration. The first goal will be to fold all like-resources together, but the choice of + * folding could/should be configurable, and will simply change the degree of link based nodes when + * we generate the Node-Array and Link-Array output. + * + * @author DAVEA + * + */ + +public class VisualizationTransformer { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger( + VisualizationTransformer.class); + + List flatNodeArray = new ArrayList(); + Set enrichableUriPrefixes = null; + + /* + * Maybe this isn't a string but Json-Model objects that we will convert to final string + * representation when we dump the node-array and link-array collections the post-data blob in the + * HttpServletResponse. + */ + + List linkArrayOutput = new ArrayList(); + + + + private VisualizationConfig visualizationConfig; + + + /** + * Instantiates a new visualization transformer. + * + * @throws Exception the exception + */ + public VisualizationTransformer() throws Exception { + visualizationConfig = VisualizationConfig.getConfig(); + + } + + + /** + * Log optime. + * + * @param method the method + * @param startTimeInMs the start time in ms + */ + private void logOptime(String method, long startTimeInMs) { + LOG.info(AaiUiMsgs.OPERATION_TIME, method, + String.valueOf((System.currentTimeMillis() - startTimeInMs))); + } + + /** + * Adds the search target attributes to root node. + */ + public void addSearchTargetAttributesToRootNode() { + + for (JsonNode n : flatNodeArray) { + if (n.isRootNode()) { + n.getNodeMeta().setSearchTarget(true); + n.getNodeMeta().setClassName(visualizationConfig.getSelectedSearchedNodeClassName()); + } + + } + + } + + /** + * Generate visualization output. + * + * @param preProcessingOpTimeInMs the pre processing op time in ms + * @param graphMeta the graph meta + * @return the d 3 visualization output + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + + public D3VisualizationOutput generateVisualizationOutput(long preProcessingOpTimeInMs, + GraphMeta graphMeta) throws JsonProcessingException, IOException { + + long opStartTimeInMs = System.currentTimeMillis(); + + /* + * iterate over the flat collection, and only add the graph nodes to the graph node collection + */ + + D3VisualizationOutput output = new D3VisualizationOutput(); + + output.setGraphMeta(graphMeta); + + for (JsonNode n : flatNodeArray) { + if ( n.getItemType()!= null) { + output.pegCounter(n.getItemType()); + } + } + + output.addNodes(flatNodeArray); + output.addLinks(linkArrayOutput); + + int numNodes = flatNodeArray.size(); + int numLinks = linkArrayOutput.size(); + + LOG.info(AaiUiMsgs.VISUALIZATION_GRAPH_OUTPUT, String.valueOf(numNodes), + String.valueOf(numLinks)); + + if (numLinks < (numNodes - 1)) { + LOG.warn(AaiUiMsgs.DANGLING_NODE_WARNING, String.valueOf(numLinks), + String.valueOf(numNodes)); + } + + ObjectMapper mapper = new ObjectMapper(); + + final String fileContent = ConfigHelper.getFileContents( + System.getProperty("AJSC_HOME") + visualizationConfig.getAaiEntityNodeDescriptors()); + com.fasterxml.jackson.databind.JsonNode aaiEntityNodeDefinitions = mapper.readTree(fileContent); + graphMeta.setAaiEntityNodeDescriptors(aaiEntityNodeDefinitions); + + graphMeta.setNumLinks(linkArrayOutput.size()); + graphMeta.setNumNodes(flatNodeArray.size()); + graphMeta.setRenderTimeInMs(preProcessingOpTimeInMs); + + output.setGraphMeta(graphMeta); + + logOptime("generateVisualizationOutput()", opStartTimeInMs); + + return output; + } + + /** + * Convert visualization output to json. + * + * @param output the output + * @return the string + * @throws JsonProcessingException the json processing exception + */ + public String convertVisualizationOutputToJson(D3VisualizationOutput output) + throws JsonProcessingException { + + if (output == null) { + return null; + } + + ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); + + return ow.writeValueAsString(output); + + } + + /** + * Builds the links from graph collection. + * + * @param nodeMap the node map + */ + public void buildLinksFromGraphCollection(Map nodeMap) { + + for (ActiveInventoryNode ain : nodeMap.values()) { + + /* + * This one is a little bit different, when we iterate over the collection we only want to + * draw the links for node that are less than the max traversal depth. We want to only draw + * links at a depth of n-1 because we are basing the links on the outbound neighbors from the + * current node. + */ + + if (ain.getNodeDepth() < VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()) { + + Collection outboundNeighbors = ain.getOutboundNeighbors(); + + for (String outboundNeighbor : outboundNeighbors) { + + JsonNodeLink nodeLink = new JsonNodeLink(); + + nodeLink.setId(UUID.randomUUID().toString()); + nodeLink.setSource(ain.getNodeId()); + nodeLink.setTarget(outboundNeighbor); + + linkArrayOutput.add(nodeLink); + + } + + Collection inboundNeighbors = ain.getInboundNeighbors(); + + for (String inboundNeighbor : inboundNeighbors) { + + JsonNodeLink nodeLink = new JsonNodeLink(); + + nodeLink.setId(UUID.randomUUID().toString()); + nodeLink.setSource(ain.getNodeId()); + nodeLink.setTarget(inboundNeighbor); + + linkArrayOutput.add(nodeLink); + + } + + + } else { + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "buildLinks()," + + " Filtering node = " + ain.getNodeId() + " @ depth = " + + ain.getNodeDepth()); + } + + } + } + + } + + /** + * Builds the flat node array from graph collection. + * + * @param nodeMap the node map + */ + /* + * Recursive function to walk multi-graph nodes and children to build a folded resource target + * graph. + */ + public void buildFlatNodeArrayFromGraphCollection(Map nodeMap) { + + for (ActiveInventoryNode n : nodeMap.values()) { + + if (n.getNodeDepth() <= VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()) { + + JsonNode jsonNode = new JsonNode(n); + + if (this.isUriEnrichable(n.getSelfLink())) { + jsonNode.getNodeMeta().setEnrichableNode(true); + } + + jsonNode.getNodeMeta().setClassName(visualizationConfig.getGeneralNodeClassName()); + + if (VisualizationConfig.getConfig().isVisualizationDebugEnabled()) { + + NodeDebug nodeDebug = jsonNode.getNodeMeta().getNodeDebug(); + + if (nodeDebug != null) { + nodeDebug.setProcessingError(n.isProcessingErrorOccurred()); + nodeDebug.setProcessingErrorCauses(n.getProcessingErrorCauses()); + } + } + flatNodeArray.add(jsonNode); + } else { + if (LOG.isDebugEnabled()) { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Filtering node from visualization: " + n.getNodeId() + " @ depth = " + + n.getNodeDepth()); + } + } + } + } + + /** + * Checks if is uri enrichable. + * + * @param uri the uri + * @return true, if is uri enrichable + */ + private boolean isUriEnrichable(String uri) { + if (enrichableUriPrefixes != null) { + for (String prefix : enrichableUriPrefixes) { + if (uri.contains(prefix)) { // AAI-4089 + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/servlet/SearchServlet.java b/src/main/java/org/openecomp/sparky/viewandinspect/servlet/SearchServlet.java new file mode 100644 index 0000000..c011e9c --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/servlet/SearchServlet.java @@ -0,0 +1,194 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONException; +import org.json.JSONObject; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.elasticsearch.SearchAdapter; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.sas.config.SearchServiceConfig; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.search.VnfSearchService; +import org.openecomp.sparky.search.config.SuggestionConfig; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.viewandinspect.services.SearchServiceWrapper; + +import org.openecomp.cl.mdc.MdcContext; + +/** + * The Class SearchServlet. + */ + +public class SearchServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(SearchServlet.class); + + private SearchServiceWrapper searchWrapper = null; + + private static final String KEY_PAYLOAD = "payload"; + + /** + * Instantiates a new search servlet. + */ + public SearchServlet() { + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse) + */ + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doPost(request, response); + } + + public void destroy() { + // TODO Auto-generated method stub + super.destroy(); + } + + public void init() throws ServletException { + super.init(); + searchWrapper = new SearchServiceWrapper(); + } + + protected Map getPayloadParams(JSONObject parameters) { + Map payloadParams = new HashMap(); + try { + JSONObject payload = parameters.getJSONObject(KEY_PAYLOAD); + if (payload.length() > 0) { + for (String key : JSONObject.getNames(payload)) { + payloadParams.put(key, payload.getString(key)); + } + } + } catch (JSONException exc) { + LOG.error(AaiUiMsgs.ERROR_PARSING_PARAMS, exc); + } + return payloadParams; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse) + */ + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String txnID = request.getHeader("X-TransactionId"); + if (txnID == null) { + txnID = NodeUtils.getRandomTxnId(); + } + + String partnerName = request.getHeader("X-FromAppId"); + if (partnerName == null) { + partnerName = "Browser"; + } + MdcContext.initialize(txnID, "AAI_UI", "", partnerName, request.getRemoteAddr()); + searchWrapper.doPost(request, response); + } + + /** + * Generate json error response. + * + * @param message the message + * @return the string + */ + /* + * This is the manual approach, however we could also create an object container for the error + * then use the Jackson ObjectWrite to dump the object to json instead. If it gets any more + * complicated we could do that approach so we don't have to manually trip over the JSON + * formatting. + */ + protected String generateJsonErrorResponse(String message) { + return String.format("{ \"errorMessage\" : %s }", message); + } + + /** + * Handle search servlet errors. + * + * @param errorMsg the error msg + * @param exc the exc + * @param response the response + * @throws IOException Signals that an I/O exception has occurred. + */ + public void handleSearchServletErrors(String errorMsg, Exception exc, + HttpServletResponse response) throws IOException { + + String errorLogMsg = + (exc == null ? errorMsg : errorMsg + ". Error:" + exc.getLocalizedMessage()); + + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, errorLogMsg); + + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(generateJsonErrorResponse(errorMsg)); + out.close(); + } + + + /** + * Sets the servlet response. + * + * @param response the response + * @param postPayload the post payload + * + * @throws IOException Signals that an I/O exception has occurred. + */ + private void setServletResponse(HttpServletResponse response, String postPayload) + throws IOException { + + if (postPayload != null) { + response.setContentType("application/json"); + PrintWriter out = response.getWriter(); + out.println(postPayload); + out.close(); + } + } + + + + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/servlet/VisualizationServlet.java b/src/main/java/org/openecomp/sparky/viewandinspect/servlet/VisualizationServlet.java new file mode 100644 index 0000000..b64d0e8 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/servlet/VisualizationServlet.java @@ -0,0 +1,203 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.servlet; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.servlet.ResettableStreamHttpServletRequest; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.viewandinspect.entity.QueryRequest; +import org.openecomp.sparky.viewandinspect.services.VisualizationService; + +import org.openecomp.cl.mdc.MdcContext; + +/** + * A dedicated servlet for handling Front-End Visualization Requests and performing feats of magic + * to execute the right model/type/config driven queries to build the D3 visualization output JSON + * back to the FE. + * + * @author DAVEA + * + */ +public class VisualizationServlet extends HttpServlet { + + /** + * + */ + private static final long serialVersionUID = 4678831934652478571L; + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(VisualizationServlet.class); + private static final String VISUALIZATION_API_ENDPOINT = "prepareVisualization"; + private final VisualizationService visualizationService; + /** + * Instantiates a new visualization servlet. + * + * @throws Exception the exception + */ + public VisualizationServlet() throws Exception { + this.visualizationService = new VisualizationService(OxmModelLoader.getInstance()); + } + + /** + * Inits the. + * + * @param filterConfig the filter config + * @throws ServletException the servlet exception + */ + public void init(FilterConfig filterConfig) throws ServletException { + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "init()"); + } + + /** + * Gets the request body. + * + * @param request the request + * @return the request body + */ + private String getRequestBody(HttpServletRequest request) { + + ResettableStreamHttpServletRequest requestWrapper = + new ResettableStreamHttpServletRequest(request); + + String body = null; + try { + body = IOUtils.toString(requestWrapper.getRequestBody()); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.EXCEPTION_CAUGHT, "Trying to get body from request", + exc.getLocalizedMessage()); + } + + return body; + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse) + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doPost(request, response); + } + + /* + * (non-Javadoc) + * + * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse) + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String txnID = request.getHeader("X-TransactionId"); + if (txnID == null) { + txnID = NodeUtils.getRandomTxnId(); + } + + String partnerName = request.getHeader("X-FromAppId"); + if (partnerName == null) { + partnerName = "Browser"; + } + + MdcContext.initialize(txnID, "AAI-UI", "", partnerName, request.getRemoteAddr()); + + String postRequestBody = getRequestBody(request); + + String requestUri = request.getRequestURI(); + OperationResult operationResult = null; + + /* + * For now we only have a single API call but there could be more in the future + */ + if (requestUri.endsWith(VISUALIZATION_API_ENDPOINT)) { + + /* + * Work our magic and determine the best way to interrogate AAI to get the stuff we are + * interested in. Perhaps it should be an edge-tag-query or perhaps it is a straight up + * derived self-link query. + */ + + /* + * Map request body to an interpreted API PoJo object + */ + QueryRequest queryRequest = visualizationService.analyzeQueryRequestBody(postRequestBody); + + if (queryRequest != null) { + operationResult = visualizationService.buildVisualizationUsingGenericQuery(queryRequest); + } else { + LOG.error(AaiUiMsgs.FAILED_TO_ANALYZE, + String.format("Failed to analyze post request query body = '%s'", postRequestBody)); + + operationResult = new OperationResult(); + operationResult.setResult(500, + String.format("Failed to analyze post request query body = '%s'", postRequestBody)); + + } + + } else { + // unhandled type + LOG.error(AaiUiMsgs.UNKNOWN_SERVER_ERROR, "Unhandled requestUri - " + requestUri); + operationResult = new OperationResult(); + operationResult.setResult(500, "Unknown Server Error: Unhandled requestUri = " + requestUri); + } + + PrintWriter out = response.getWriter(); + response.addHeader("Content-Type", "application/xml"); + + response.setStatus(operationResult.getResultCode()); + + if (operationResult.getResultCode() == 200) { + response.setContentLength(operationResult.getResult().length()); + out.print(operationResult.getResult()); + out.print("\n"); + } else { + response.setContentLength(operationResult.getResult().length()); + out.print(operationResult.getResult()); + out.print("\n"); + } + } + + @Override + public void destroy() { + super.destroy(); + visualizationService.shutdown(); + } +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/task/CollectNodeSelfLinkTask.java b/src/main/java/org/openecomp/sparky/viewandinspect/task/CollectNodeSelfLinkTask.java new file mode 100644 index 0000000..6c482e9 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/task/CollectNodeSelfLinkTask.java @@ -0,0 +1,60 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.task; + +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.rest.OperationResult; + +/** + * The Class CollectNodeSelfLinkTask. + */ +public class CollectNodeSelfLinkTask implements Supplier { + + private String selfLink; + private ActiveInventoryDataProvider aaiProvider; + + /** + * Instantiates a new collect node self link task. + * + * @param selfLink the self link + * @param aaiProvider the aai provider + */ + public CollectNodeSelfLinkTask(String selfLink, ActiveInventoryDataProvider aaiProvider) { + this.selfLink = selfLink; + this.aaiProvider = aaiProvider; + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public OperationResult get() { + return aaiProvider.queryActiveInventoryWithRetries(selfLink, "application/json", 5); + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTask.java b/src/main/java/org/openecomp/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTask.java new file mode 100644 index 0000000..b7fe3a5 --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTask.java @@ -0,0 +1,104 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +package org.openecomp.sparky.viewandinspect.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.viewandinspect.entity.NodeProcessingTransaction; +import org.slf4j.MDC; + +/** + * The Class PerformNodeSelfLinkProcessingTask. + */ +public class PerformNodeSelfLinkProcessingTask implements Supplier { + + private static final Logger logger = + LoggerFactory.getInstance().getLogger(PerformNodeSelfLinkProcessingTask.class); + + private NodeProcessingTransaction txn; + private ActiveInventoryDataProvider aaiProvider; + private Map contextMap; + + /** + * Instantiates a new perform node self link processing task. + * + * @param txn the txn + * @param requestParameters the request parameters + * @param aaiProvider the aai provider + */ + public PerformNodeSelfLinkProcessingTask(NodeProcessingTransaction txn, String requestParameters, + ActiveInventoryDataProvider aaiProvider) { + this.aaiProvider = aaiProvider; + this.txn = txn; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public NodeProcessingTransaction get() { + MDC.setContextMap(contextMap); + String link = txn.getSelfLinkWithModifiers(); + + if (link == null) { + OperationResult opResult = new OperationResult(); + opResult.setResult(500, "Aborting self-link processing because self link is null"); + txn.setOpResult(opResult); + return txn; + } + + if (logger.isDebugEnabled()) { + logger.debug(AaiUiMsgs.DEBUG_GENERIC, "Collecting " + link); + } + + OperationResult opResult = null; + try { + opResult = aaiProvider.queryActiveInventoryWithRetries(link, "application/json", + ActiveInventoryConfig.getConfig().getAaiRestConfig().getNumRequestRetries()); + } catch (Exception exc) { + opResult = new OperationResult(); + opResult.setResult(500, "Querying AAI with retry failed due to an exception."); + logger.error(AaiUiMsgs.ERROR_AAI_QUERY_WITH_RETRY, exc.getMessage()); + } + + if (logger.isDebugEnabled()) { + logger.debug(AaiUiMsgs.DEBUG_GENERIC, "Operation result = " + opResult.toString()); + } + + txn.setOpResult(opResult); + return txn; + + } + +} diff --git a/src/main/java/org/openecomp/sparky/viewandinspect/task/PerformSelfLinkDeterminationTask.java b/src/main/java/org/openecomp/sparky/viewandinspect/task/PerformSelfLinkDeterminationTask.java new file mode 100644 index 0000000..948c5cb --- /dev/null +++ b/src/main/java/org/openecomp/sparky/viewandinspect/task/PerformSelfLinkDeterminationTask.java @@ -0,0 +1,96 @@ +/** + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ +package org.openecomp.sparky.viewandinspect.task; + +import java.util.Map; +import java.util.function.Supplier; + +import org.openecomp.sparky.dal.aai.ActiveInventoryDataProvider; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.logging.AaiUiMsgs; +import org.openecomp.sparky.viewandinspect.entity.SelfLinkDeterminationTransaction; +import org.openecomp.cl.api.Logger; +import org.openecomp.cl.eelf.LoggerFactory; +import org.slf4j.MDC; + +public class PerformSelfLinkDeterminationTask implements Supplier { + + private static final Logger logger = + LoggerFactory.getInstance().getLogger(PerformSelfLinkDeterminationTask.class); + + private SelfLinkDeterminationTransaction txn; + private ActiveInventoryDataProvider aaiProvider; + private Map contextMap; + + + /** + * Instantiates a new perform node self link processing task. + * + * @param txn the txn + * @param requestParameters the request parameters + * @param aaiProvider the aai provider + */ + public PerformSelfLinkDeterminationTask(SelfLinkDeterminationTransaction txn, String requestParameters, + ActiveInventoryDataProvider aaiProvider) { + + this.aaiProvider = aaiProvider; + this.txn = txn; + this.contextMap = MDC.getCopyOfContextMap(); + } + + /* (non-Javadoc) + * @see java.util.function.Supplier#get() + */ + @Override + public SelfLinkDeterminationTransaction get() { + MDC.setContextMap(contextMap); + if (txn.getQueryString() == null) { + OperationResult opResult = new OperationResult(); + opResult.setResult(500, "Aborting self-link determination because self link query is null."); + txn.setOpResult(opResult); + return txn; + } + + OperationResult opResult = null; + try { + opResult = aaiProvider.queryActiveInventoryWithRetries(txn.getQueryString(), "application/json", + ActiveInventoryConfig.getConfig().getAaiRestConfig().getNumRequestRetries()); + } catch (Exception exc) { + opResult = new OperationResult(); + opResult.setResult(500, "Querying AAI with retry failed due to an exception."); + logger.error(AaiUiMsgs.ERROR_AAI_QUERY_WITH_RETRY, exc.getMessage()); + } + + if (logger.isDebugEnabled()) { + logger.debug("Operation result = " + opResult.toString()); + } + + txn.setOpResult(opResult); + return txn; + + } + +} \ No newline at end of file diff --git a/src/main/resources/authentication/tomcat_keystore b/src/main/resources/authentication/tomcat_keystore new file mode 100644 index 0000000000000000000000000000000000000000..efa01f8d79fcee29a1cbeb19395ab776dcb6b46b GIT binary patch literal 7201 zcmds62{hFGzn&R0V_(L;X6)>3Pm6wKy)jX9biQ=Il_^AFh>Gb2oRtr*!Z}dNI`xCKc7HVv_&9M4J!#q z&y6w^tsk`WA?BJiCEKvEEyNBHqwNbPCk~$KZrp#}wqEgidQmMQ zf1y=XrgViLIVN0{JEk8Py)xXPsde4%ncoEOEvGDvSD4w)EC`DJ?mUT{k!_@K~LI17LTKVVP@ zK%wWM;eLP*Kti(bp_xNzpSD1mg_r;*K#>s@1{DWxVPXq6Z%1qp1cK09^D|-1M=-!CQ|60vJ6)Ip7|E zd-3j~oq!9l18@{%1#giLN!`cK8;}Ad0cIpZg^LL$3g=HBc_vCLOHJF?@FZ$)x4E$D zK@l}TPLR>PKiP-;YySs=*YfC*6zScg;9$VB5`Y+11P}rMr#d1 zB)H&$aDGnHU4jJD`!Po((3j=rp~SxW@Nb~mPWLx(yl~F{s&(T-Qj}F!$zs7=J7GGeu%vi2`@VBQi2qv& zG3({7`xwN%KLW2wQygEQ@rHQj2c+2LbQA5oKe%%JDN}#uN9n!Lo{tEa=oCI|VcDAV zP(<-eeP5{T;!$~8Ks>_G$frv|ck;Elgmecxd-9$6%7ZIaITyzdD8CcM#0OHJCG>BQ zZhj+pLnVfv?ZmE+wi6YPS3R{k%Sq3WMubTie@nHqVsY8qofFq1Pg;IvYAKh0HR>J4 zYx&}|@#lA^MvY_h($jrp$2>%;`#Y-HHOfsHL8HD*r+g^_NhS1v~9ZWSEhQ1q8m zs2#W&=iDs0al_fhHt#hyfYaIF8yRqeB)}j8eUJ<`f@FYRCj&c!TIxp1O$|e%R3Hsb%BiCxi!h1X4*vvf@O8J)LW;=-F@%ARKW- z#q4B?;8cGX4u4dY6)F-9J>jx+XE7p2AqI0Zw%=7D@)}b_K0$8WT+(cNOM%j-`f#T| z7gt{B`o2uIioqN4Hnixs_hd!HNr_6snG}SQ#eF?8xw-73@Df$kk3Ssg^emgYeh^BX z_S5{(`g~LARQ9RX)JvvZayga)S!W*yz#Jr%_sS0Rn}c0nYOEZ>OC*tH487C zek8Yh4x&3CZ&Rhj+QGd^hAe0qE%^CkEKfsCPoE=h(`+4gU5folOTp|2dnsawy5Lfy zcl|;&Yu~B@q{H+O*~;MRA_LTk;7Ymw7M=k*Tu8!|00lsrl>2BOtLFIIV zrt>`|nTJirsZtr5iu1&AwaLYWU6EF~)#!1yy0=?+IQUQG?mjnUl`$rpXJ$y6mk#R4 z`ILZKn2Q|6$BbTVI`XxXWl+wUWxwxe$C*+7fo=A(n3a)mKEiu9b&QQzfT4iB%3^$C z7;kjo3!6)ruX5w2RNcWQu~xY)#yI%?cqk%@4L*u zP-j#mO-!7<#Z6WsdSeXAQi7U3KOFAn{;0P-Md=|3tWXG&N)7)<4Tj1=oGWGmXF6gw6udK ziG{hQynU`a1!&g^Zoj$2Px2bb*eKpisevEuy4)8f^k~Br-dXRw@ho1CE4v&!p8YT( z>%4+$q#UA*v~tjfa*mGKRSo6C?B0dGq!8XpQet48JE@1^R# zR~VKk8ca8@&94`@8@xxana0`ehuwFrX>+e0$4IB^wIq_cVp8Dvvt27AhZ_nYb1wS~ zBri4RTQ|I9p3QCBI{rpyalm6nka=uemGH2s6EQaW!cL~^&rJcRUf7NmP4HG+Zw|lz zEZ;o2rs;UcR+ohX`Z8B#tXJ&y@SFDyCVM+dDvWdv-(xbR;_nRB?&5DDNbCcAI#Cu- ze5-D+2+s}qR$D1nBRN60uBgwLEr-2Exgp%>G^xFtrrvz^TdELXJ$LfG_Sct}QWlEY zUq2W%?xgJXnV;e&Kh~@^5X5?GIyj^3r+W4{*B4G2AQ89Jo;p8xUz^)Xi?vF&-B z`s*&>#fVb^BKJ$j#Kl?4`ISktkoQ;G)Si6uPUaxpOvtI5%)y=1TNt4JtfaCy+T^oO&IvvIE&aKc}&Uno+1L zVfZL{qP1=cmtr#Q{<;NUK4v`T&^*XBQ}~p1o2BUzwxf)E{GGtAl<9WJ=tW_y(nrB$ z75DA}!$)F`HHe)%|C`@#aA^oCoo1;S=7o3|H1~*w46<1*L)u1!pOD* zg^A%Bp|7x^jal-VEMj}>5NBG*;x4DppQ(D}URr%D{Iv3$(m8{!5R0=+o zde7ANXdKw<^)|bOu#LG_H~(^u@r4UMgUiU?k_f1%k)h{UnD%_A@zv*^fz1n{jfVMB z;(Pbpd+DDRc8dLsYz|7gEwKbueDv<&#bZJbGLfzsTy~SY6Ef7FzL(g&G2eYekQAhK zLNBjhK$~yO$Y3nnX<%pwDtYfu8KW7M%78^937*o;GYLDrpO`BUA1Ly#0V6|WBAJ00 zBol~253BT$> z6u?bqT^0@&8$Y72pN~f%mJdMDvzR&92xO4iz5Pi3URW_egr3R9!E5B;u=arWBa(vG z%@YBDk6y^m!QtWIhxQAkV$oit1K4%V$;81AW@G5tYGAgS2bD@!3;(DPd!D*&yZhSu zJml?;qOMSyK=+~%FKcL(`W+7C3mM{HPr+0=P|p3b!loyBWA+#SaZI=KMs5zNX$A58 z`1Rwr_(~h+V!H!SgmF~V$*B1DiY-___&_NU<#WP5W6%k5PL2zfRnlTY`D80+UPje| zWPDUC6p>L6jm9P3x3n!6b5E?aYdkV^H%17m@TheiZ(%RRGW`!}RnYl9y(MU;!o3!-v71bF?3#WvXtdiS%{oy^L zgH1kWhW?WFI<&wq-j>$IRd$(2H)&R&hASp|S1WE5`^u|SCziUT4_d4%L>L8#f%$*a zI4Jt#G3NKnNq4y<_~0-A^6P^cQS?nd$-xByeuju+f`0!713c(0(QsG58E`@(eWE!) zXAQ6hWEfRI#V15pe01N(>xWuYr^`LL@0DPHi0E&t(Y4;sPv754I2HOBB;m3EC)LPayw;A|ckv3q{zYIacqEzDl}}GN3=V@w z&%>iUNDRNC@O?E$gu=VQ2pM+k9u*eJE`Dny27PVsC1OLOZxx zIA8Ou_xo(EUo*fIhPDj6mr_ad^St@Oxr+p{O3+Bgodh?^uLO13%_yB?bAM*>Y|*f zJ(&mjPs8?%>Q_km9pvRow7jjJn{u}bfKbI`igO<&%4HRq0G};HK@bx z0yz5*ZBQ1Ug^GuVulr`*YqK6$`gAWackF9G(yp)WBnJ&eFX1YGv`l73OdW^2%ca6d zgIctvta4l4v598YmmUJi#bT5Nrl(GK0Caxx9>C-_$w=7T1H|29%p#?$b-fBvia zKrlDLX=&g`Dlt2ozF$fKz`)HHH&AcZC* zKRUo}XQ|rxxr^U5Xz@{hO{{pcSqfGzgUdyzq?0e@`2C7#hi!K}VTG42i00S!Gf9;7}T(Fvqlu$strp#Yy>+$8elFuYi7CZ_*g+2B+C-{<5UF z$5+D6>|rAu;j9EFx!89$!JZI4AZq&x6i~~)opApJ2CyQN(L!I)@9X?LFx859j`c$uPKr|wBm?MS#gUSQx zt96dIUer2vNk=rpwiz^NGAxJ)aHS6X)`Hml4VAS4a0iO8Md~!n`pcj0*M_bk?3$Of z_WBQD93BkauKTTXtNjyjgBCELz(|9JgxGrMHShW_vHy3C31#}MQFO=TEckCBhV)_L z<7M2Fb;B30jsMA+c;$)yd>EE^=&2t0;V{l0VJ}f$`3vve2wnCw-+G$B`~1Nl%(w&0 zUD?bce?f*ds1<2HIjDZnqSO2sVV=1XZK_G(e{QOcot~z2VXVh7R|y$VYlruT^Iqz% z-fZJ92e|!2m1pjBXnrt%T^qmhq|)_I#7ljzQI*lx!EU*I9C=T(ua7OqpCr7?DNh(% zzQbyBrg5*Bq8_F}N??kAx!@Gfhtr!!Wo6wX#1Xf1u`Ndb7mR(vNGkjFiyli>d5%3V iE062lZOB_vz4A<_F=Tk8^U@cfw$Pxr(?!(T;6DJ!-Dd>= literal 0 HcmV?d00001 diff --git a/src/main/resources/extApps/aai.war b/src/main/resources/extApps/aai.war new file mode 100644 index 0000000000000000000000000000000000000000..e112b07086f364068abab29550d36c3d407e4fab GIT binary patch literal 999673 zcmZU)W2`Vt&@6bY=h(Jw+qP}nwr$(CZQHhOW8Zso@8;WVCpF!ZsUKC7{xRufGamar_{+~1%0a*!A z5hZ0>8PO-1=_zR`8rlUIDH_Vz>A5CF`emm56Gv*v*>P$q8bJu~<6_lhRH}Yr_l|5y zCMQYf9LgHxM#ZN_rl&}cj_fE!Bq>Fu{=it!hzX1esNM*Z3J<7J&r#1*cp8dMU5X8S zE$V0JLzoz7j}G?^fd4BH;C~DJPt*S!5dT$bB7*-P8~=Y6{QqDXSsOSxnK=Cq(^CJL zUeg))^!krg@E`uyjak?lo4C`OJKI>(#5TYV;X@0%`GFdqr#8CMi(@lFiFUZ=vy10V#2X;t6{@eY7U$1L@HMrW_yv^k0DJ%K3u-rU{ z0=Vx00l@!LGVlMRf%hkd==aO+X59CK82%FwKQYYy!vF@b2S@}U^C#-Z!3VkrQU}=n z?c(?j0|21pf8lEeatF8uBn4ReLznbF^^3mp<6!;k0t49xpa)$4R9f1vvBb zSpLh)&HpPS0>G~%efg7y235$1ZFdIL_5=VsZ}80<`Nmwfn+(h zJ+nIk0xXJf=FAIs54$p1o%?r)A!1Q7+nUDY{zuJZ!#H#i%E@wXL0c{ZfAk(hcj44j zn1L`FhDyc)Clo2thMTm+nan%~se^{C_0Bl^L%uPr=I^lX-LdRrcLU47b()`K#PII| z`X|NMv@8Opxq5ZMEyggUv+V=;M!@LcD%q>Gx5R9%18=a<`q zN09kTfX8kN#LoCwNFNqq^qoxNh<*Z~c%(dYQC4M`I5Z;~H_(YS6{L#Nr>Z3I!(nK6 zrdIw+j&-KGqwYFnYL|dr?^fB}k~nlYaQqQ&irO;LWkz2!m%bfd8cF3lMPpIcj$Ow` zML5d9a8gvJ?z1~&u;tCPqb4`xq}GJ0jc@DE^nU2+93d$*AU|BfDWhY3T5pE0-B62D zslfL9a%fP;Ch7Iv8E#yI%tBK!0YsOxfYL%=Ih`qW3(P%s=^S6!>ersNwOwliCxV=Q z!KRPo6C4b5#wY$yOHRm4YakTTpuQH}wwR<~lzzy=?MaZBuoRTp^i~rtQY&REM1{l) zC*_M%9znfzhFgz;$;nm80ClYteERH6XA41uOqtA;EaYxC0DjD}UtNlV%Mi z;C;zz;SGq^nstHR$0MDx*sJM%+ky#QNFK=Aok%o~_cPWesym|`7r#yzvk~J=XjU#< zC~XKFZr<3ps1@uzLPcT?N0GE*^s*><^!rW*`Ha9QJr*Hguwmw{aWN;cP2j=%UG-u= z*)Yc!&)B}?#Od?0p&>D9=Eytt0v-|M3^tc()bvsEt{BA12L@mbi-f7K4CJ>szW}0L z=~+s#{nU!*YDMv;sco4-WjtV85$FehoCsr8;eOMsbX#3FSo1nz246916EbW@Lsna* zl#e4(rvKJU4!J7XbOhNoJhE4Vrt zAfAEM?l1$?oN~)0;Mq8N$%a29l|Rn~Wp#(J+{-zgbdR? zyf!}Hx4;FSlLk4q)isoREmtH_vw=Q&hi~v;@)ExTUeWQD|)l; z`tA|2x?Qs?oL*4_#W`gu-wqlTHtW!me>;g?*1HK}I$6qXOG}AqT`5a5Km?Ued+L zf#|S~oS^Vaqd$$hB>tY>PEip;6Qzh9AZlNZ2C_XUU){d(K-cCQn*LHVVi}^hPhSVB z&H2lbwo~7BQTG(_lDRs8&%cb=mRmQEbNxuyeVXvJKPaIFhnI9Jx>ECz{xYp#l4~-r zK#W+S8Asz#ozk#wrE!RUw7vV(z-~Rp5DLo&ka8x;>~bDZZ&<(D(^e7g)!gg`+J?50 zu&EH1o(9Ui(DmRL*e`71nV8I+nMUY{tCp!5sCmLW+eY##TPY^Nn$D`q4jII~?}dE4 zHWLta>^sv^{*4!_qyb#wv%W13b`aE>f6?Xq0O@!9pJM_DIQq}{c4&5VLVB)x-~f6qOuVDZos2 zp!HT%3x0o0JgZuHlzf*j*UXZ~y{ANKa!LMdUam;pcDgGkb1Y|$ijWUapJ zhA&g!&g?kLW0F@*Sv(!#yV1UqjqOJ#xF?Ca+#3|6 zNp*!$P^%9@w(Qj{HWB4WKQF|va?_Q1Q`McbqlP*AY@t|2{5LIl>feELB2ErMNk zofE@ybR6?A3t79uCi>IE`hmM~uUV7h$)`|oL+`vkA0*SCUvgk!d=jXKEBCx$wF z=pY0Z(2WaLVv_6dpc65lOIblU<(|uV!Jv6si4>a>giunB@>L0h(3;(BS zF#NaNZO|o$R(5{&S>ye%5aeXg=D)5;GQ^DkY&6YTYX}3mKIt1k4cO{ zB9ZyHB10v*2!ghx4;d_4p5f{Y;KcEh51t=0ij6dErE8YlbPhVtWh5(sLRiSq^-ydS zH3!0`9mcri#U;21&p{zdXn|8fajD{O{HVO*{OxZ{oViuO{T{XnVrLImg@l^MG;y>B z3dSopMt1hDg8s!6*QK_sDFwcA7!C-xKFK*+xrkvHLNIuMLF3Q%W5R1>@xac4X^1oL zM8yahIChRDC?1SQ&_B-e4SB=K88_TCzr6ZwS;IYDvuLN{hxO6D-~s2HTG=P{Xc>*G zPGxaD0oA%7*55Tlmw-&lx+WAsYg{$~#POJQNAuE?mK1UUvV|$?R1IgSS_=F<6weqk z7ZO?M7F*#jU}i8*(MVlH`5t^5^zH|tcn3y|1e&}r%E`K~#AQceTddI)mnxX{WKxwA zfKM)|Xa^dzt2zC%Qi@rmIdlO{mo#5%iOnS4MaWPu%}dUf9$%nZUReeL+Q^mS@N;e# z=h-Ze(g>)t*85t2wvAuG!)kMc>ciMXMstx_(!9gMhE94z{W^FeqlP=M0KyJnJX?tc z8dsG*+}Hvq7n>SdtUKUM(F0+1i{B@lTU{7FF(n9?f2YjO@~Y>=R|*nkB9LUWu8Fc&ukf zYQG9Yt}aG0-P?pc46lIrp5ZYf`B~t^+s$j1v{xmx)5$Q#^LtVChWGVGcw1C}n$_@4 z3jDv6elFpDwYjEvxebb*l1nqq$1ZyKB9?4}efJsJ@euZTGXdXU5@e6xa_FiQS~Q`w zY4V`*T*DsjQxLlNNx#Z+*qj${f=4&LH%wFV=9Al%e~leSij6CJphD6N zgzyT}-*i}7<6^Anpub_w1SQl&yL{#KiD|ci$qiN`=^9dJAW`t1a;7RSHnHvyO@e;~ z(AX0ZO?QMi9rIu>Aq{V28=7w}vs$3J$G3IskOeGBU?{LR(*v&I4uDlt~ zsD+`&2G~ zWSvifNdx1zsj9-^u1`g6BCMS65`~=QO*Yl45O}(*18fg6vT()cpfP?p}<_mPG_E7^y^{{KpUN6o|763{iUDLHl&3k2GbfK&LUPQ?&zz4s{4}l zqC1Wm0U#F+{8;(QN?&#OWlWB{llw%3gU%yXF)%2+Qa?T`DRdb@Qho-}+0PU5Tm~_` z-g8Q+=HQqJittU)yI}$z^tAwp@s$-|`k^C^IOkU-NGs`!{4D1GAU9GLNrWDP>uV7= z_AkhI8}m~vuuU_sUDS4yP$qsv*8i;BUvSZ+9OOXgjODoD&Im`~=^?ru4UG4y(lzyZ z4q4@jj*fF#*1_SMgq0Ft$d{W;a#UaNHvDZ#sm%3Sc!xcthqxXctR6$Dz}`o z+f^}M48R&o=ji+6>LpvMzNF+sx=}C?)khfNEI*v6@xc30frlE7k_#=^WfE7W4uM+g zT|p_@OHI4eu{Ph$7&&o6qfR%8Q3Xi2a)bzc`;?7AF{krXJ$jEojrr+3D29lDKTZAt z0;dB5d2Gq4JK0u&Z25#sBm(~mN7G&i^Bw}-a&)vili%Tz0f|H9m*Xkf0DR5>1uGIW z-+Fjh79rN4@k;$|^ZA~P&iGX)S2Qp@3UJ|iRhhW8d>x#u=Cl;Haf*{g2>Ti9Zug;# zh4MCs;b8n8fRFPO5njZVWQ|1s(< z>%GlwafSSR=53L&1Kl({ z)aD^tE#2q7+tO$xmnDb1-d~PU(?AQiw8gK3Rp_K*JxX`#*-;{iN~==G1WL0+Y|dkL ze?1OdwHVm9gE5UYd^@1Sb*77EkR;U|8psrKM3C zD$Y$}Nei3D54HY#D!Tw0{$`k(UQ(yh)-lIW6L z6A9dCiFXw20VKEU%l|$u^wy7q&hE*8A7=$Zn-}%k*ffI#yo%0Y9<={qZp>E^Vl=I! zRMkXyNiC8;=bP!ES8WooBUK0idx8f$lL?!S%L}dMFlRkVc1vBBPn}Des~E@m^2W#n z1Y^<@hG*~+XTPRd6Ysr8`mYrggayNX{;-7??OUu0B;P0N8C%`$laNKs4z_7Uf1B{RImPDz@P=H_73~%Sm^(dsVe7oOyj)mR-Djq5jfc9pE+rCjpT69!%Y|Q$~=k9}tA4Aj}pn+aTQlpb3mlAO>sgiqP_W_`+kLcT-&fu9ALA zye{5hoII-$B6!Dch!YcxG{N)YdV?*ud>LUYEH)G6`M7wZ(p~)qLL?q2U@5<|OcXH$ z-+~TyS($DFvkc-lE4yF6Ft8Q^O^`_b+h>-M4j`~U4gsoOY&FI8^eOhkJ%Go?I+t16 zf2VF5E0MO~7vy#nLTTkzN^0wONTFdMUz#seG@h81q>wI9IYO^0S)r2kQi2gd*N2b= znP&8bnS1=v)Ay7E{`OjG7hOP_PTXfdRkBBPDmMDDrf^ls5FV|W)!&Z)DpRbuzqYEq z|K^hYynHQ|MefWiM499A(iC#n7RFkkM(tHc?Z(&+^cY|V<9AV=0MQ76-8_kh#0(Po z;Jmt|g)4yBTO5pvJ}DssY*YB2`^BC9)lh$U#m!kHbPzZA4t}@sb+el#K5yTNs)i^9 zf@~pRokwl*7R7FXn`bj`*!{;gKEtpEhH&&Ja_fUTY?mtF{qWG_X8z4`;@z=OlmkWO z*PK^2asqN^4cO5s4d~nE?ta*}ZAiRqGh{`P+nl zLcj@Yg6f0gOb_?{a-v>I^bx1oFeQz|llK;8#*ezDT7Y(7F$CAU51*YYh2O!F;Sg{L zNTl{Q0EbtB65(QZxl5pz*azn7*y-E=y?Qd~nK{)Qi9&Z7XEK)5J^q@ck10C~Vf~~c zUlLW|j9dmZ*tyX*FQPd-x$CqJmC9=bO%{n%NN^uCP9kaX2wBoieT*r_j4d;-U)nS^ z*C_Ug&x~YNMChyQh`jpS$MES+ZxpX?&Mc$N%_Pcht@VuOKeG{xZOT5tzUHNjw%L3k z?vZ-_;7iGXQE0LxO!*uG`*ZMVNJSBnb?S3?nb;NPRF2rqN(XE{JzG&i`dtsL`cM>vqH1^Mq~l8L|v1H>>wK(Gz?4% z{qhEhm_h2Krbs%aRJ;MR5kZZ5)Q2$EgPSs4q-5Pz8#%FfdVvTa z#6Ha6R^jg$Tnas_%DS0EEM+dRb2}2;QaS4X&e|0nq~0&`jnS5 zG|_gFQJeQpiy|u6hjLSsqs3M7*mxzs)u_2K(N3qEW77Ucrk)$DuVAmm@;5w+n83H4|x zM77@oSoZ%K5QYmmU&nSYe5f)SHh?1QBTRNqb{YRBARsI<`;?cj>02snoi2?xI0z$v z;}1=O!SSlqEx3wY$=nQEiwheicYU(Et*_Fc8N2tcopG@(GGxgcdOGU zI`#M+Fx}2~<*l}hX)~9wR6DH1T9+|2U2>(t)h7w+tq+7!XGR=!&mTWBx3RSrILvzl z!aokT4D7_WY+_f|APoL8!O8>RT7G*bmtN=ittghScuAcrPE;YHCnva;2Nd!`1E7J9 zIT8X7C}2!~e;H?d{JJIRqrKk3r_!uz$?W)HvWnpc+5G?iwazO{gYKEb8KuM=hrEqf%mq1>l4Ke`8E`Ydr zq`bNJh|suJ6X@)@pxl#obyzlJM~V?xFAHs(8`kW;w4qF0!ZRmt7fA@k$3N-pt<>bn zg|HT8C|Zx<^p=;$%(wa0@wn}Hv7BCr>Z80a59a&AUQoXndZseB< zM}OBTzxY+$E+k+0eix5=p2)*gCd~9>&!(b2e?S#zsp3n>l0F7DQN}S5UdE{9poGYUIbg zYzmsO6Liw!SD7HgVd-SG>@%LGPKnDAVw9pQTJIyLcVEiU3yK?5H+$4?KTt*z67pf^ zkXF%47?6r7do2$`_meYegmy zDHOj}W9X)>Ziu9k8D}9Ga4<(xZl*v=sgna;k#9;@ZmwV|+666vF?M(;)SQ*D_WHPG zEh-)|xn@o=V26RDv$u0V2X2>}*zK+vsJzC09y&@GRui1;na>GUBb=3kjSCAh3t*t$ z&*NmaX_!a}b-<={Rqm_)805o&H9!$&U48bD zM#1wNBQ`mVQ2ErPJVr?rRb5Ac;v;|w=T<1Vya@TRzQhtyjY{vU9F8T0O7Ezyy;M;m zwvBFNNU2vp3Fb*`ERSY;y8gS zIP{*p;VvwO{_#4p$Njz*Xx{Y4ZvBE6SEKebK(PJ}bUm~86z}QgCsdEVWXtSKb`zP; zTFn2n>x@p;lI$C~-``2Y8fdno7dAh-JpTZ_A-y(!QyhUhdcm&sSO`_FO>yK-{BHo-8fLb)YN^SKM5mM1eE8;0%!& zo6zT^Vw4cjjrr%K$q}HRY$n)Y_O+$(1ZNM1Z~nU`H{HR0gWf`AP?`&=RQc?CmF69O z`&KYLIN)oH@64KCFjg2#wV6P2j5yFu6aY%M8EGt5i(H0+Yj4v4Q6aBGE%{_cNlEdo{wLn2vUSV_TnGEgX$2chiIT8`dr<&GKhY?0zi!Olksw)>;y|#KQ0%dvH z1qmCo2k|tr7rxeopHW*VhoP^KVT~566}ZK9`dr<HJNckZ2LTnp<+N(qkZVrtd@Nkvt5Ot!A~1{+>Dt8n>dmZuT(wvTrC87h9Qg4 zb941@JzN^qF4?3<&gsNL`nO{eR8EWw1v$)YVE<{1yVyU3Nu13tDlnUJj|XAY`wVC? zS*%ROM~QT4C{z2*R~Q7Z9LLBNNWGr$2)AUSfIt$6N&fLa}6D@UY+F1 z0fl{NqL1gf*RWF>7+|=6^xIZEPRy$OrjH?aDT5<=Em!`NjH`HxnCM)1TtMAY^*luk zWG@~$zG!=&4#Xgw%r=IyYCs9abgPtIM%SEP*Uk*YFL0N0V9Si4ya_ch9Iq4zss%2$ z_X0>Dj;rnkDw^ULD*Xo41=#5<4Fare_HK&Ua{trLR}c*3HG<$67n{Gx3{%tmB$dfEN-D1~J4?e{V;cY{rdC zhLv7{gUL3zPp@(@=jD2Fo@GgWEutR;}sk7SRR}7O>$<)(5Y!zgne7JXueQ3iU1^ zi8&qMq|irn9qJk{4!>Bvlccs1uuQlnqd|D2w=Joz4jg`DK&-{28ha2{5c)4_{Wy^T zWakAbUc9XhKAh^ocW;cT^c-*DXyykkm~uZ1H28uahN)*apM1qfrYQQqdG4~e!NCwz zR7{FGy+V+waCV{#^6^wZDeIeyn(H3avvuYXv%NL6iRdGL&>>!IViu_u{9ypWennYX z)o68!xT2jln@LnbRY0CDk(;<}X(6?d#$lxtYP{sENer@}e#oSmHSxL0xnT7j-XhRM zvl@g8vfqNTkk|yAGo0c=_o070$I!D*S8>7^)N}#J-mUq0j&U7p%)k3~-CiAG#*xYy zu*h2*Tw_~9CgKid1;~HJ@Ny2Tnn{Ey=$x0j^G~4;!?o;Y0cP3*KWUWHU|08|^K$HK zkObb(_?#FLgq2RBDs8*tUa(9_6Bf;-n1D|Q=XuTNWpLR|kOb&JQ^oYuh)cOWjqjS4 zMh)?YXd))IIAtW|ga;2CH|{cIx{K#R3^=oPUR9SSe3$^GomcL3mv&!846!9ME%tjO zC@c%s8L-MGD|jB6BQG}~(P>)?uN0y0G>wg(pCP1r6lsKlevTD3qab4OU=xI%OrE=39up*S-`^iuDM4D>NhOYNcSo)$g|c*Dwa3q_24cndQjh;U7?hvM&%bcc#` zBq`?8Mu1!le_;1);^&V_fwJ_B9xk{w_sVBR^~M2(UfUJC0oR%P*O7bipc^2f(^>~$In zb!f}j;atc#i8n)IA}ZneiVR2^^*QhoOZM;mvBkDjUIKTK<6N#kj^3E!0J1V{f$S$E z=ED5T*M{N0EH#H3adI3>d!y+bHON+;BhIsZsDsmO8(8>&s%8X!rrggw84cy?;9x{Le}`@uPfN@Zpx_{)euz**kMo<%|T8&uQ=m;9~0y``1H{wXbrwr ze5P8WCFn@{b5KTNqO$@6-{s_RxZKn-p|~Xf%4RD=9k1K$OXdmNMZJjj^EH&DuojW2 zLg3M4^3DVw5kKOmp2znWY2Im^6Ivph^cN-@s;1^CRt9aWVTxY3!a*X?{K=_CLs7t(MPAYEJ`NilRihX-JHUA4&7G zD+G|C!1CnU7Oyo-Y78rw3#QDu4Sr3<{57~> z!JAYNE4l$j6YIu?Ht_*Q1D73-{(5`7qh~BTN4b_yjb%XxH$mDSdaRqaaaz%h$gYcDuNJNFUo>Y*u&|GJE==kOKwqu>m? z8afq!NP#EfhvuPf`OkFO{=I+vo1)FnBgiQ)ex&fhI(m3Mmxx`0gc=HPw-V|>IfAB^ zz*4$mXR$1^Q9=~}T4$XV50H}tcZB=#Ot%kY9}N=$bv#vnmr~LcM_!mGxaKaR4Uz|9 zqr(29708+j5JCKHnXPrZpoYr5fWvX5asi(azzFZd>VEKbdtWj&AlkB56u{mC+ca70 ze#kESRO9iBq(0QBzX3lE$>3GsG2}8NteC%S;0gBblb4H`q>(b}7>hBj#6?XL_d}%M zY*7{<8mU87tZ|JATUw=^sZs;68j^G5DTb>qWMqgLD&=s@p1pH`QH2vrJA&I)LSX%3 z8)0(Unx%3Zaom}3EU^6ZHD^*O&H&#x|A%ElrvKGd0N=5;UnSYiaBT*!LNzIuA0}~o={SG z=_HJ`%V~HsDjW)@gutx8(&4bopl7J^Sx2XN(WiIpMiSwcV%HyAW z?#v6>s3YW|r0!cF2=HmNYd$h^_D?q$;?!$!3`s%R?505YIG=V#jwZ|Nja&*^$%1S5 zn9^AFa-Y45>9BErXVX~1dbge!koPFu=OV*c|3<5Df*v6jexL4%CJ8u%g1s)&i9%AO z2rMR2Uj%+rnR34KHu0P- zOh(cSDY(YyQnPmmKr;wIfWK82^sx-ep3782xRwEDhrLoTe<3HQcLl9XE^^#dSS_7g zDfc!z8xyCN*)Wn3oo`$n#z^@U^Uug=#@d|hB9+>Xop{W4xGvfYbhVC4DIdy>>65Ka zLtv58Nt5f#!gY{>B#_e-gc2rfeGcAWl57Z>RWV6VME~9(DR@yOUM=H=)bn-N`mnx* z)^MKLFhYW0PejiKlkh0DFnP(~!Er^NHMIqC_LEwI5E1xA)HUANY-yZw-s9_h*w44v z%;`5CScOJ8@tDaUTwr9WUtt91?UY%O{ih|=_*R^^onZab{;~X_&_gKDsSiGD`$3O> zOgTE_j8k!#z3!np&cGwPwd64O!#gTXnnEpSg|YKNSy-{o&59?gP2|g=>{>Ow!nQ9e zaw?S&^a0nek$9gyhV*#)Yv~8 ztZmLeQ2CXpv!3bbVl~^UWMbXfEpW2y#gj|{WZLy&5|E^?Y;uuSv@gIFYMg!XZIM=8 z#)kmGR z{c`+c3szr?lJMJpL^1eeKziVkyE!rb$rNzcYCC8qjnf1>wf!$pLZgY~Pk1CIHUzpT zwhRB@3_8y7Qz}1ks0Kg_>Le9Hscb-jOxXn95719F3%1Q>`&>o|=?uc3T*xeW3@|Y@ zWz8!$U`XjFCks?&2;u-t3B43B6J)QokIox(Q(edX?JRa%O)@o+)@n0>-#{WY3CZKB z_FD^spo`cD;w{HfKpa6tAdq&h&@Jl@l4XuAaz7HL0k}e8?s~Xav*_Pi1nOV*arbGx zx7=@EZQG}`QOtKPVG{7f04gd!B}y<5DtA@}D(+0MEsyz5Gb-`zm~>+eQ9`k%H7L#V z#~t-{%a^aDS~-Rr|6ujhl}HD3mqdMpv6Lb|S1;|zysRYrVy|s@oDhvJ3_IwT(O7kg5e!wSuHwJ za(kt6qdt=qD9Q?Qu+b~Q2kS!y{6buo2eg!H?;;4=#50--;46;aI-N;tv|<>R24})S z#9Cd9G%MKS1c&RPkNJhna1V7%hhsjFJrWpnvKNEk&Mluc)t0Lx)c9;R-Ykk3$%Zm0 zQS$s8*Y_6GE0g6pyT+(`93pN3gAu#q*aJ_zDcTS19YcBL7H7d%ENsJ8dgs(>J+6mi zRs)HURvj(73YyAhNlxqbC!^(2`Tf*ZM7n-Pn7q|6QC8w2>x~eq-!}!UBf0NbgcFoLSIf|;$iHzBpH*@|O zX%GDk6f3Y;e7P?e5N4YE;Rrq@};A$h1L+LcOk2rbALz=4kp=7G<|!l!Pg z<`aVF>=oZBc)--f8-LsO zI*?;?NLjo>nBv=k3_Es&r?K*`iu*^kq5!-uUBlu+^R#2ItxXw)`>l`;#&Ig;_@sdn zj$N&^On+r>HcsiYdPYt{5^ySGS`KKY`h;z@IQq3D{Gjeehfa6Fkrk#<*iXH(0FTYN zO7f7Aa;3jXIju-d@{EIT3f2bsA*iOx@^iI!kf9(#wWLrEwS9z^l(1RS z82BKDy^d^1rS)Eg7aM3;S1r!?oZUXc?G>239y!~s*}_jmu5(;mjwqWeW14aB@44>Y zWZ@%E(gl31Lf6K`k|A*#MQ%0L@H4w<++fw_5&|X|&xIkS1zw`PgF3z&YAD7K9BO`D zp?Sa`z?8=^Ic}jVcSwZ*iW+RQA18Ro;P9EVjH%{yO5&rTSf{h99jK1_u)T%vbV3?=y$ z%7p}_{=>Q6P}!{A;x^+w%us zAbtlQb^d6IEQ2t@{)or!HuGQfFBG3jlpCz6Kz3bVHTp_%HuJM$Zs>(m;7j$@%jm3pbLXal(L0bZ4k7#P+%;Zl6f46zH;UEJ*sU)c$2DM{ zO`{dH@kcx7(dcKI1F!;~_Aqy426YH#FZDOvc;9Y8TK=%FUOL_kuQk#YtYpr$^GmWH zDb^PEu}Q|^u*U*pR@fm|TXE27jp}6n2Z3a9Hv!dEqGx7+h+z$!q5lDUNwt#BEDsq; zPW~eLgrhdJ#LYqv@R15H%qjay@w`cc9*SRU3Xv+-0IIPBwLgcM7co|_@#F6AXhZJu zlKHbZgu%qTbk(^bZ{zCiLM|K!?8BB_uh0)fR-6){{VJG-vju{WK^PGgJQ`C8=ie%E zzuOr?y(J-NGCi{A%N@$|#c4t6Q4&!nq1ux-_G7gf3A#BA#4vTSZgpJ68EaB>eOYxE zB5E@9irso><0^iypa%Q8k+2VF&s)_}ZuFoo|C_7CN-v`aG0tj(VT3MrpZ74vEN+=7 z@=X5i0NhK5D!Yss=zz$J8I`ccTN)rNPxIHvF-r=C3PmSqX1y_)aI{DY{^Stlqqw<% zz_gl}2&tyKper+(4g>32HUal5oC)SN6ZWSpXD)0W$N}wIA#ek6tl&{>p&4s4wN~uLvJ1w8?-3oI+s%3Jl-3mUFCEEYfB;1BeM6{uZ>>P^ zZZ)Z8(Y-1(nQXVz`d7yG^gThP3C@+|TD1bn<^%P@cKLM}Q}cuHi1-=6kVdV18|VVP zNBZ;f24nm61K<5B&9VI&1+CFla>sSoggKYh$uz+Ze7N(WM_9Q2d~y$1y)Y|Gq^=+3 ze8q?@&}<$y4dyF;biEsv4P_+h6*o(!wn8mL%m$?Ze+Z#-3fsgTbVTxTB6@8S!kONX zV+-!~>Ck*NoRnW^gvIz^s8FX4IPEi?vzWZE1Yqv`n`HTe7?0i3v)O8=f;Xg%6-`z98JSVxddVX_AAbb7 z`6~hPfFG*qU1*)$Eu6k?Pq<^}%chd^^~RyZ-L7WWRZ7?l*KHT8Ya_n`wm_dluX$Of zA2zWr+mL|m+mriS9Dz!VyLba%>7JYFpCK$*N4*;)zwUwfP}ArivXKuA;rS1L8Ydyv z!KGV)p7ZliU!ma?L)he6ko%nJTNdx9$V98>-~y_v^N5||E-Q_4FpR}`5dyF7XgA$N z=S?(%Le4q{B*0TVW~GqQ9L@$f~d<^;z!;=4DUAUhFx{M$?Vk(e0~t$-1Dk@cZWZ!RmC3-e{K z>Iz|tHR#vEtY5NINnoj}Xi^gx|c zGMBXAU&<|bm6E>!?(iJf;zN5AyhGKs?=o<^pAP58#mM#Q_ zbX$KIcMTg)_@!5(U!!*UVN8Qjtw{>iMkH(r3m5{U5KTz@zJmfo&!y;-*VdWC`n^La zfu)>fj^{Ho76RFK^+b(6z45GVKQ9dDMu7qpxV>~A5I~Ei<8R4c-Mqz zYJ#p=#*8*`sxMef4Bay+P2q2wdE|4z?{;>7Tsxf#BW7-81cRJzLfE%=F1g;wj+cIu z4zD3#z&ksRxk7%Y7uWKab`iuZITkG#S>4UjerrJp4gd@UW4S~g_MAXmVETL#_XQr# zb+(HEU8E7T!|Z#QkP$XVzW%x3O!}{Jzmj=jgaFp%dI<-6e=9pH4 zGglz{5>rI7g=92zu)4-1^?XuxwpSd3W=-8rNauQ6#`2wAxhz3(Wr6?`l8O9uA$T&- z&kZ=(KgOHLlnr@RekwwdZ}bw>333RQoZ>Bp$-V-1UXqZT(77RTVp0eHK$siIBxlrDOpbmYSvG;RNtV5k!fL2b5RF_a+4Z zjYDNHbP58mz%+vQp+ZQ-0@3@Lc#lG zCd18wfjbw?-N0-n$l3xks>%x2a}`g_Q4Np}GBAB&xR-3I;llhn%ng@4TZ0HYCKDFr zsk5QnFcb7r5(#bYjJR6Zq|#slig`Qc0FuMxej*C+-#Oz> zrxMsfPvbJ-$`R%(slya~fASl6$O2d)#2NCA2tYdXM z?H8aTEV%b1bA1ssy=AxF_?SmwXj6pkOjvD9N>6J3BgK`Ou!o}PliT$F<_%FFe#{|j%5A`z$fi>plO5<%L)7?3=ny; zguspfAm5l8_5;GwrFq;^u9xJ4Ua>!e+5c&4Mm_M4@;coA$s|y;AwP{@Jjw#SJ_q{b zd0mvKOx<~;U#}o&ng1iBpVOT*kZ%qpN8tFQ7IyZ)9J4XTd({3~5cF#G@?s?8`XDbS28y=1>9#E&|c!tjN>DIQACP!nj961G9VHXZf{`-e;$iqr@B zA`^-#c8BY6BZx4HyAaFp&sMYWTYG}p;pp)RG84!UD^fAVoXtwo$_<=^t>axu2aI&P z_{OQ^{{c5Z$iLYNk?i?dCnu9oS5g~3CI=^@uITX}otnCIkbA3lWz65Y7ha1CZ#^rT zdsMM=-P?BxRUMq^@nXtxDpSJ|^wceqsuXgVesLoS{q=F?T*b6EaWTM0hYYK9^)met z?_CRg47}XArsf_v|NY@yr@WFkaH%pDZk9te)HTRG>xN|({VcACJhkqk#G#_rxphWikMT$!zuHaKFK@`u<22sX7#{8?Kj5{~L zdk&|ACGU7NGsQCfmip@K-;i_z-cGKb?b?=~4&s!VyB#7+n}v>!ws-b6KQ!&3>O1df z>Im4_aKH(tT=mtax^6M7TG<1U8V;`Mk&ECcRB@55zpZ~kK%mtUK%7(KDhf>@QrFRs z%DQd2X22C`oRaO(Q*kaVtY;OWQyR4%0M5NR^>gl&TB$=w;hz)0uvPr~#XIEnv_Kdt zbSLr2#EYE(dygZO%Z-X|_X}7yTUOo+-y=o|v~VptnH^jo6wh%sZvS5Z4M6h0Ge$@c zUx{te&>*HWLHL#>q0yJ$@&Ri=Al50TGR!DVFeTo?{FGTQ1M3#xZblbkW#-cLcR1jP~ak@w?T%O;NvDBJd2TkA% z)l(|G?#q=4+cBuo=?Z@~bAbQ%aI$fE#$oz4tWU|2oJ4LW$8V^|2UtxhZoVN2yzH-h zL4^h$3r~b*u|FnIt(D+Fk$uqQz$oiievtrPGG)uQV%K-QT327Ndx^!OvLj1D^^dOO zq1kCu{!zdnH=A+atEeYCJ;d1dNc>^~0Pn>jSM}AG;4O$0;4%l0Rg4B&`HG=zpsp?3 zW+;KD0~94diRsET&dt&p46~RDOEZ(9W(APWFdxiON4GzG#{F2E_GxD{gy51bFmkPw zzM@ee1s$q2os=2tjCi$V(W5o|knZIf27wJ>lda9Wq+Y~ko)h`*6c-r#t0o; z)H-fCz-4CRvHZd9pZc>y(1^$ZXv`tG2H4lVnRf(&K{s>^W5IwLN=_0e>To8ah5lp7 zX9;#Uj*x68c#r_AEC=?((51OT3Eo&h5!FsYGZ$0JAHUpmd~Y}tceaTT724gy2y1gb zf|h1kf9g&N&}C~LBU+%cKUOw*f(j&3XTqGhMuEAYrHG$IUjCB5l8Jn0y|aJ; zJCsNQ>gP-;-)kaXB?rAkEGG!m+N_oZu=I7}h=*M-SYo1fiN}C^Ns#I25j^7mhI9#6 z1}_3yQQXgGqO&Pv60o;jDO9Vx>au$hLQ8)^AEhrEDY;Io`F;Vk=MLGYVy8nF`4n3z z5wG$Nv&WXabRbyiDvBAzS#s^D+K2`3u>jwTuuJ$;)z$u9!0>gPtQq^f@M_I_?3D^T zAv9>4x#$$OGG85>2}Xw0ul`sNOc?u#mCc6mW5AbNEa<)4P{P6Vtbo{jOcYm|WlD3B>HX@_;J^ zFwrPR)+w}4oVSMZaP#RgvXaVR-u=-+B0r*bE5darWh4VNN!7HfT@8~(VWFz7O8&9o zh+EEPTXd5d0CS+&+7#;#nKvs;GnrLV{$8lBNjzkhMzhibtZrwIL{CxT9IFLln?7?X zNc(#&Tt{WPctUrTQ-PrF7x0q=;5>C+M#+?0i+=t9oy`F?LP@1(lOYgN@aUPTyA>r@ zFo>dosXzvb`WvHscsGElv!sQ^DFy->i$fPXt6T5CW1X4rns6E0T3f6U2r#&3v*jqQMl40SWwFAboRCECeU9 zScw4V8Y=X%_JofRmL`oi(ZnfvDrym}IU@_SMk3x-@}x(_1PblMu#6^)b*gv;3yBIG zfd@b!y#t>_7;~IuNUNWI@Ewveg#8=_a`}z2vyRdgt*)#22WTs2PVcT5ieiGd5Sk0f z6bG?)wr|ggvs8&e$daU>1`<$UVMoEc99z6VUI{qq8D*6eidFzM!{QeYa2<+4zSL1c z{~BQE0j}Z!1XkxGu=9n=pf>x3+S#&pWICDPM1ZKfho9X&52Y(Nv7da}pX4?UU9y&0 zDv-`%b8$B&CJm7**HD^SOn;5+f#|ge0AOS-2oQ|p5Ed0kzLVhLpyhZ6_w0z(fC=NZ zRTPwf(A9`h3fLuC6Qe2<1(X({J{bfut>m8IW()*VZv>MK+khujDf^2#?C}xoab7w| zd^8CL!!62p{jc7(=6rGe@6L)sz%jAe9Cct)z%X6WZ*OZg#N^sM$N1xUUCJ}G4qB)mZJzki?SqM4G6`KM8Z%8$22hIQiVP{%R4L5g6 z_(ryhvlL5yXvMsg^YKY)LWGO@=@BiGnOj_hnxH+~7uBMHy5!yYW<_uTH1GeWmVV&c zjeJ0m>lA|8zsFrXl%_5{WHDoGtDaw{XMmOwL?b`hWL#&e5bppz53 z?aG-a*`Jq>Vj*ahsj1i8O0(4i@_{D`1E)AKETH{FtO+zCLUh>#3WT)&P(Ew?EGr!8 z35zHs$X8Xa&O8atpD(W`eGOBNUIBBSd|uwZeTyEY%n|K%pP&3GOf?je#lm~sxk?I8f9A?Bza{XB z_u5v|Rg8E6kL2CCuGdUv_dy;&*icnjdQnVpG_x#m?XZISU6}kScwK)rb4-ID8JVET$gA-t9mcKp<-?S)rVERb(G2z2AUr8B)TApJ26qLIT0Q~s$jeZu12LPSI zg9hI4Ju!5o(`Kd;gm3AgZJvn|aiO0J+v?h_Hd9@vn`tSKX@pIP#@Gj0;}iPmeD#N} zT;ieeeeh|XZ4HEXDqBKNQRqO55T8wQZ5}<-`9eJluR{(1$RLW`!n7Q$(znO~E(JiX?TBHg-6$5dJ2Ry9ps# z$E2WBwpPWnyhT_-Dc=Hoe6@>9veVow3&dDIA!@|p3;@M{_P+>?+B5~$9z6q2d!Rtz zz+>)>{p3I=9}ZdH5ugCwts1av95)~!1Rv$qzMMT^FBo|7%w?cEKo*xR)|8ZHlTfl-5`MiLaPejphP>UA{vV! zki{Pk2XpJ)Q`f`A%r?U61gU_dOo28}y9Q=9PVd;L)eM#BVWgr|76y2m!ig3mwli}c z8TdYHn&n5}k+Ar=EkUP-?dHoMcx*c(5%Y4|iUjENSHWX_JSVdX@NvG?!9T-LvV7j! z=@X(vEL$a0kSFxhof%BRQwzI!QC-O07_k_f`Jr25m;Wt^bW$0PowCe`TprIW=8zyn zcwCYK0&7-Pj8Z6Sl|X6f3<~2(w3w#KeT^}rFkBFrq1=bDECTu7ek2DLP$Y~5z{)XVfDjaD%-q`&z^hO-)%nX>qXG;v z7-*=U9u%a?;C{7x+xaXBC~wGQX8+Xi07^CwB?(uk^kfjjB83-K$I$=vsy378LK6hV449R22K{H~Z#&~#%4B!F=Si?u|Ur670 zduU{57H8^;X>q1KTzRfTfnJ+20fwKzQpg1yMilq3#LY`&m5!CgP$&*jl2Y%01_s;+ zY8(7dSF!!aZXhgdh&3Bnn-kcY^aL8BRZ=j1btKlt#Lro)4EL+1J<;4WuV0sCw-@-G zZ1g8=>FTb*Dk!J=zy{an6b~6Q9n-Iqi}`%)hqTzbPMFsw=oaS}J8;?8Cb3eRqW#-W461Z`}J zW}2j|S!tOivVjw|FE>XIgVC*!pkbs&;+mdOG4$h{rl8nEX35|s2=SsT4??SCFGyj2 zzyaLMwlD;e!fnII4BZ6-qJc1#kQ$f`!e+yz>A9ugV5F(=g2zXWrp9bVU17qAWpmNN zBcs$P>xd`^*1Sz_Y&!$R)V+yd2nkSBw$5kcXocw}x~3wPK>0V-X;b0M1K6H(M?P?F z!8>UHjqyhYDrOoSZE<3Yqp`GV0UNPMp=)A^s&@*$mfa|})$v);9@3*CG2gDYNGl%7 z(FiVnMHdaI7X}-B(8O9EiIyST9B+3ha)c-eMd>ocO36z0TAfQ4a9M1RP9Idjo)L?5t6Fqk)0d??;wsa0g zK)!Xft_PeC2JQ`lRFt%vINcwJvyXqkLJJUxQ{72~%*0vS2sWJ}!*m2ZNMl-|TNA>6_QQ~d@i?jZ zV>O{A+8C1w$rmm!={_!}!w#2Q3l4z~e^=2VSWh}-@CpeiD8l2}&+6tv43fsL_70AY z490m#_8a=#6itvlq>g~j7d=SMECO`piQPB((% z$OAGGhhD;5L>3Ztgpex|<3L8N5M!1~Yp@{2L;I8u>Z7h=U-?{#zwqv-^<)PmrELw! zM?9Ay8w&^CidWHA@Dou+AfK~52xNWkfc_*w(j|r`QJ#^z{g5*h%JV#t-=ozsb{${g zXMT*r-|dDVVCKc9+E+7Ospp>rADaEilpE4WCi^)e6Ptl!7>WLn&7ztQHn#ELJlc-} zrq7?D9f{0MqM{M9%PJ!sjfYoagN|H)D+Jgrg4Avy9hK(>fI3c7U_TT`YZ$@OaEM+lVqQ)!UhGgPnP}5;Igsccs$BYNwht%GjD-z_yGu*7 z=RT@1U&tzs$ zK+Zs%&zf2(R-O-E*fJ1>1SlF*yO8AnE&aoC zaX&Pk)h8p@>>QIruI&Da&I2%OW;tdn)QZOeuX|8Tj#Gqlk%b^lb3Ee$xRqXo!Iq08 z^1~#a_60#t7183(e;4g_5Fj1AeuCQ+;L|{;{C?W~TrA_<8qKkZ&Zqq3C1Co!Wa;}c zicw}h7-WRK^t|3H3vcfwvF>ColviM>z_A3j5`4EM5(#PnUpV(oG*_sYaU}YH*Ij9D z^@LM~hQB-Q5eALa-w`v!DagW3vn|5-Oaq7sgB+0(+zm+Wj$O%BVU2TanuEBKmmSc5 zjbk;&23z>^cWN5KDwb|>7IEZ1g{Y1t znYVD>>a0jJpzY>`L?R3VvDqsb$hL64)m^vSZ(nd5{$SH06i`p#$h~lm023?A@GKK# z4-gCyN7Rj?W?S%^Kn*6wZeO-u5eYZ96!8CDc4XC+of2_@=9jD<@(=HjpF4G|&W!NAFdr|I zEfI?k<+;Mqp)>~T8LMF5hp45kfm`y0x}unjQkwRD(!{gTlw6r0NaI6(dA$h83-%x* z3MhHF5T~_W4r#jDFwo{%(&l6_s5-Pzs6&K^%~zT>Fvl98gNRzbaf#0JRKMuRRO2;` z3WuR2FB4P*q}*CMUMCLlDKgC%>X~Q`6c(!`V(U_{1^hWiq)mb*ktzS~dVn^GN2Vo6xl29CeVDkxc1d%a zx;AX(KWH2`%oh?Q+joPIRkTxti$dKefs_)(2rL`zWs{wm(rlm{UB|egDE7>x*xxjf zk=^0oZXLVmG15O_u4`(0n_mT^=!c{Zr6Eo}gc(X* zaV{8-Nk~HQcT%-EMHj~4pww#F*Gwl4%_>>MrkE%2Yrf{AD|YWarQ4n&7`NqxY*Hyy z7C%2fkfBaWCO)Fh({p8KzEyoUowyKfzL5QhCo7SJ_U~w?m>9RHu1cym}FS^A-^_^97zATT>c6)zhU3s!Q$R8uRgHX z$E|?V>ie_dz)9cg{{vWi_)`u$Iaj1!4RXWq^8MjBL`I}x7_L~F_<{!QA5@dt(vX78 zF48hR`?G`INEnb$7;}|G_zeJbj`r%B(HOi);|Fqj@Pg=0mVKv))pqfJtztO__ym|d zm^^M_N8HjJ8R1OfPvo9i*$)>eLx3@?$2!O3atwI~r^sv7aU37L6J`2^kP$=@EGl($ zjLeyJjXWS=`T)Azea;1?GF@}>5hRq6AtX19oJ2~QQpr%j6N27+iUlL9F3$>8=^LW1 z|I#L*mBPToM~SnJavDPFyg&|MXLE)bV^Y|g8zMQKm7Tkl-wMn`_sfv715$}{`3LoL zrnW8u;lWsC7^qe*|Fb`gn#zu=RER5-aPJhDtQ{lsNj}Eg+4XDOY+=c^p$-VhO8u2f z$6)gXL2c0(T?1>Mp&_jDvIxLn%Av2}9ko(sxhg+J2OcDDP}Z7SHXv&(>J1 zSEkC89x9;Vw1xjv3K}qBE*oh)x0?}gZUdn*!vx_B%1l+-^lJrAR0X&;Bb88~8xhB@ zu<7X9feO`|EW5K#`n9wf5IH;Ke02tgdFg*fM8~Ixx~f>ro)v{K=`zeyQPC`Fko~P8 zjSrysI|(BWoAIqL?X+phB%v2^P^D2tw0g`d3f&<*@|NnsZW&`0?-c~#i^G=vT?PdK zC8g!>m8et74C`U?@?DwH0Yx&(pJ+#D$CPT&imriKbZIi(IoTjiQRK<>$Z&50(rap@ zaa@(FeewAQgEha@Q;v?ap(&RlO0tQiGhKs*92_tSP0xY=u;BF~_8Zr=z-E2L2=pnc zgHi-~n%#G3463R0r;N?G*GfZy7tDd0N5WuhBU~yxFQhjqI`t|Y%aUiLVC^*`EO(I) zRuosq09$<#uDe7L5+!)ha2b^YjbTuUDs=eYQ-wxV1wl`#isT2%eL2sCo+>cDfgQ1c z0IAazC`oZd7YrUXcXjfH_p*5hV<+_FA^)@)A1L2As2b9r1na;edF=RnRMt_b5-i@` zc$rBj#a&CpNGD=2lhwqnh+Huf2d#gRaOP9+x0v&|Ht!pNT7bM(LtdR@~)YsPu)WVApfDkoKFl~;$@)m9Am`^UH z9Plb_+%JY_N0`l|5SZw=AUoa9Suj(YW|If2ojNfy@0@}$z3-yM^QXpM@XP$rC4 zuoJ;nTO8)!01?X86;=Mq$h46$4I7xdlUA_dfG4uUYgM!hv+FNBqu`B8dYvkS@z_)% z@YPWvpJXdpOxjtuhd39)`<1azWdNuTZ%`n~(Ib zjM*7v&)#3LU?>?WSLg18ly);AU)#KrbR(h$iR_-pXgABFf50z7y6?ib>xPukG9ZUC z`!dZYmt_i3heJjput>drUbY4UIJMUs@?d|=Tm#zJm{X&aaF7ICd2mPaG}j;$5wNdo z@lbH?Toc%fLV)RFft+$Moz>*!1Y#8yqcYqTg^f^#XJ+hQW3g;0%+z!mx0V^@^$+n) zNRJ&qiUX2AAa_W)1y5h2=vbg)aZ$Av(SD_~5I_w0Ny4o(QZ1w8^IH9@P4FyawY zLbJ7kDahg%F&zy|l!5@kF{nq)GF1uYebk|sq+G5c065?8U7?{Qv&n&1@<5O$_{j}% zwaYJJp<%pujAnUAJ9r2s>(TfGwIt!v;8YnhXj&$HY61**nwQCc?fK77ZYJeZv5j;ee^GEI^xi10FDpkG|-U9=pMDFbc zXb&nBlrCyLbeBu274yTgh|&}j7M8%afNBiGiCZ~ZmQ^F$_+#0@(n2>LoqvH>BSMfDHlUse4Q4pD#oRd1@hlat}_yMga4Vic$th7!TBq$nkB z(L{Sy^Or&R8m8W!Q*vAx)iX0DN+TFTA*<*E0{Xn^Nk-_DWEWiS6Qqx{*sg*i5a{eN z)vR}gbjBMl(RU(dE?c}&W~Pb_})3W9(GYt<32P*Fs3I0+FYhwp@*V8D_aS(d(|;wex?mM>-{IEmOkh_tcTk2FA2 zVGZLU*SvHhj!5B0d9%e`yZ}@<@Nnw`nAkHiO0*FJ#couZFSRsJPE;e21Vu8}`!1yD z;27(`qJW);p(HMWNFT>cJ7s@ME?Ra*v-|WYcpuGffgB$pF#r_)2`3KWC23PD*Rn<$0G?^gU40gfzNW9%^nj1{7tY5&Wt zss_wb;^#>CqIqK-sfJ3aX3mw3Sc>wS?juJ>Y;V^z^niO{C-Yco$i6#6fUKhO2-79Z zEpF`Xjm<4M{gGtDXToenI)|d^ORQl&H-Pz|T65uwU250}bS=W0l~H+AcWgbIIoW?UBK z21Jz=WG|YI<{)N|M=6;ktn{;rG5ktc+EzI^Y3`kV>8FKnjSp}+u#HGm(MVG$RE{~MSaf~>= z%#Q}T_Mbu$t^Gl?L=+IrhmwSxQ3*_}Odyz~%&Da6QW8DeXL-LpTp$zz-Z`cWWlLSP zfUc&AX2ZH9PF7$bAiTO|*dD0Lw~Ks1-V{7wdVULnaH1&9iv876_)Yj`XdgE)U#>`WGGs?Qd_O3}yi zOqxgyqM>nZNWbbO;&XV^(g=58Gf5jFq&L37h~OV=3sDnB!01rxE;R6pP--f&a3AAi z0=dF$yxBM`RppiV)?O;jU?+`q5g(6Cs}u}L4RA9t>q;$XNw5_W@A0S#MTUBVz32=@ zv+0f9cz?r&j4|29!0wX4XEpiz2E<6J1%t$iG%8^@86|)WZ`pF6@^u$b7}SmN;7U__ zf$w0kr*qPts5XgBe~lmEktA#zCEITH%h*DnkODyz+i;D85ur3s1`xa|y>pKctEYJC zyuQ3BS>U9~^Z|z3r!igIAxNT)Gf5D93gBZ%QYA8zgYZ*t|DrH{jZ+(o1NBJ^#UV;} zU%NR*>zE=N2?;jD1XM@esshO!KG7d8>n?pQSU6iFu46NxRaA+&ldb?ykDsjofUMI- zD}!Z)U7sTxc#!%@M8^r(F8mcdDU?z$_)~ceBX~q$EZf&f0G2QPgn6wt#)94{69z}g zgWCrq5oP1u)SUA#$)#^<%gSG%sjJ(o+wNu zT0)aUG$cw`fq+k#l^R<81fG-x0mPH|L+MUOo)a6daig?|RnqJ;E!|cWq@g?{#Wef4 z)7^mcn~n4V@!_raE-Kxxyq%sl_W|+D8~X@IaiA74K6E0p9w9xJ4mO1U4#|Ab{=Awl z7-(=tNT3rUrRzQ%DuFK{cPZkdKpLvYLuDGiNHbKSCh{1O1;wfT^S_Q?kOzU#EeAvcp2@jWDa;y1-y|2VI%NB&k!h4dxc|^G?XOM>BDc` z(T0i)-Jvv#c{oax!^#P3T_@rG6JD4SFXHxrc*oR1{~~6t5N;tBv0EV3fgIdcxY^iQ z1(1lPkjGJ!#8IhWpgLmRgY`yClndz5POQrgTN-d=%6~=21GY5r_ePlXzC(t%`DAGp z1<0NGvFNLfyoQ56KaK1k#RQ{AM2&uTfpX+<^nijXPUw(ENz?MfLzQ#rtg@9LfF_Im z6Pw${yaz1thK(KwrupuBwZfU2*{u*+aTMqUVrO$p1LY5=;`0>ossUZXbpyrpr2qdr zW1eYx%FJ`o*K-Q!hNI8S*tGfL)PNk~GMVAEX-B<)LPR-$%~RGr77*&Va7bhb=Cu){ zLleCZ0&2#@tQwr&~u!SEZz3>MzAn5!wR0X-zte^!k8e*JW9f)r+E zZ{n4#4%eS?yk-DFCa?W zs(0hzH@Bx(YgaV~8}pzrD5RV4;Jyz}bSw*`u;@bvub1)?bGig*o&k&~;U(Gt(`vzk zE|>LYuBKL_w3GH6*7Uj-Z}VRe-0+uX)Q~pkSm&2OOq|UVZI3zE$89v@K(wfmM%L8n z5B<$hiXW4-<1sU3#aB92MF{Mra(XXD1T=0~h=X^M8&I**G^?^pq6jQOGlB z9IovHX>N~t@kC!I*DhmSg$c49#8Wl@4bgk#*TAGe#}ye%vG}#7;f{6(@5}|tD@XA^ zc`{X*2oerV1M&SW-t~B(GF272JwKZpi_9kN~0GAiJ-Ue&$b~Krlc|W7Q$t< zv8@y*ie(yQ8iuqT>+K+$5+yiP#7rbiGzDU(8}rbCdYa4>9MXQlT_!`kdo>O^eSbh9 z-BnE?rkb|;ScaL?`nbIeNB|ju>~jZ%t%=&~{n25jvf;T%soc{p=CYl4M-(z50~XcS zmap=Q9D2sQLx3&d)Lff1txYuQ-EHdbwq!u#(D&^>1gkgQ#r9_l6=^57@F6Fp z5GOHI6>CrXQn04kMLTGSX1ezig<*`?*aU~)a-n~u>Z|rB655l6qj?{#8igSN_zsi? zaak5wIZUHUVjt1a%C#tY%(bT$L0P2)16K!Bw=>bKM2|F1T9`H(cVz!NL?HtQypc z+@uQ4%Pvr1XwWcl=_Udq;o)WumeO*D6r$f|KE`=2yIKR^-zlg30m80hMf9pk|y z0;{+SknnHu;3c5pe;DyiiynF$9SD+>9S6*#kV4*=wLKGu0+qB92R_F&E4V6cebCA+ zq}inmI0UU9!1a4J0TQXq%*HfneJy=Cj{|ksO;9`AIg~tz+`vCWLU$g}HAp~dR70i( zV`aFRb(k^@!vIfx#-V~sM3SrRK{zS~+tvTgOZk-k1jET9DOK7PSYoQ<(E0~=X8_`o zSU#XZPo_*7=7|1n4yt`??Z;$EX7yOW13(--j^4p7uDzELm<52Bi#14tL=H%bjx`4w zogw9Lqs>Pd0?1iUScMq7^;<}xPzB)7lPaaDavC7NXx=S*4#WyEzFb?uU@bIT*T;P< z00;`=L|mtM)%2nN0&jSLv5S`q0z>Plkkl$wL#Ut<40mY?9G7y=1H>f_{MrZk6>|^x z+)xN$mVa<~(jdM13t_*51L^Gz#2bRTYIm8U;=ky^8x2YDa-nUb6DFZgAPA2`Ib6{g z(W~$SPl=%vz1;eYj0VlYv(#W72iProq~e}yC?$Q5>zpY?T_~ELaGbcU0E)mf$lGn9 zg)AZm8ePDW;^@`u@#7&+Ah=rH?m`-B%_!L?NX90Touzp0zA=#}*Z>0<1$JKtzKh{~ zIOYn81ppLk)dMd`%zVmEkhBjXy5mSt$c)1D+%*=0hIF?J$>aeQS#fK8>nm?}wK7ryqR?^=cj`byYQFIfgKMLEN>;f)u6OTLO91lVySfy z?{K5R+`bVe+l1#*J`EaOh;1iQh?M^fm;zR1$0?A^ETwe^Fwxa| z$V%*>?%ZS2#0=o%|04BV6PV&O?C}*!CuMb=n`I%N2KGJsVTe^wql|?Wly+ugnY@1w2x3$Q)VQG)t!M&6k%VOzurufAmSnq zCvRoS-E}P!j*-5wm+EtLq6|?SGm2ZJTL#}JtUQ9vz!nX-;SOj3v(#U6P}%SN=2;~~ zf;Y1L)8I=th42j#!5?Z#d?NT9Hb)8193>GD7KT2Bw&S?blgqM?iH!xwGSyqYrSP z5ioAxxUgXHR!|ZX{FdsYn&uG5?CxI7m`rY(`iLvdCa{4}`OX^2J&N+J{y#7r41m|_ zwak6xa>Msd5-J~A-rSU5eogtkSo=6+@OuH`96qBr(|bU~^Hh@_!p*5Nb6nT75S-Ir zIWqrOFRQZ9Qb&4NDrY++J{~QMl;vk_rV~5?4=B&sdSodr4YQYZxW*P>+b><&d0=7_ zO$rP|_cQLHi6AUc!ld`2JLS+xcUZVJW-bAZo2uA0f~<*?PkUvbsVGUSX-0UENB;r9 zoR1fQSX+Z{iPwv($N;cL5dk2VcHBX#QXsvZktiXq32xf@SB{-+>Y|?X)b2R6t%H_XIx^>kRjKSw+6HbM|weua!@2m$<0ab*I0$6{J02# zG#oO1hR`FsLYMRK>YD$JaV&m4XeochIT(JF$L5H1UH)_c!15ZdBG?Ea(qY1?OOhHt zM)zJ${;M>HeGmvbNkVFbvr8aSQq}d7>iVAl%jC*^^4mR0MA2h;b^`#8P56^R856p5 zA(ToXE-T_bfbBd-AU*WBD8lIswtBK4b>NL6I*<=&{e>)6m%Bt06XUjU3aK2hnoKHr z#tM@1(XjL(R2fXl7nAVr7M&u%$@t0N;Y^+Eg@h2*aq&``h0%dX5ic#d&}IVEHn_C< zj+zzL=;_g>HZB^ zA6@`+n`o2J4hs1t5thSM=GxJ0|H6@TKyL@C3rgEoJ5U60b}z#`T!f$xHE1(fxN)YD zygtR4zjJ2ZzNUuH*h>jXn@%$6*+9*UwY6$g+h*>xkbqJ(Fm*5y`~4(Rh`}{bl`<0g z7_5G!MDSQbo7!_{lz-qQ2Lez)61Hu9*|lYnFlPQygP3Wow5onO5&&z0Z-QQ!Bzi9#h3X_X&4*oKyTXu!<5UGEqv$6lP9odEy_ z=!nLdWK2UnyDl)dIunYft>*M-Hm01R81m`OL12+hS5N~*qI5BriHAQ$;j(7Mc@}tu zsKcq}`AbKE2o-WrVDo`rzn)2sP>`THvCXu{+cjG?M8qbQ%L06sK4s5hM0*IT0rTQH zwAu(p;9zX(F7$FNMvD*pK);kC8L{Bl@vW0!EOmy^iv7e99-+aDJ%A5eF}u_7S0UB7 z^>a^ZjrMM1m6pI@0F#z>8N>B#?Ni>kj?iSms`oDEDRVG|jDxEo&7MH36ZFULcNr z+Sy2u1Yj1X0YF(T=N5e*?95@y6Y%K3Y=YO_!KSNzu@g&WSU(!OXWQYp@q3?$+kj~F z2up25HYAXyNQq@46bQ+j^KQ(;M^^PBYj4C#s$P8%Vio`dof*;e%tjbg7jqN^K_uyd zjuZQ!in!jCs@n9CsohG%`$JNIcuoL}V~uT7A|r7TDROId*f6lQ{PNB7eKQXs0-KrWv2N#EwWF3-@D5I9CvSu>-NATk>htu2 zKR(40vJymyQ^3QH!SpwAQ%<^bjI&y8Q=q{{}{KgO>zUxr;0k@bNmwK0{JS z1A2TsFZ41jX#iM`j!$|ZK=($e74cpvN*KB1HtJss{Pa0R6!4)Z9s@H<3yu-156J>c z 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�hd9IZ5^u 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

    RWd|NPsmJZo~H3Mt<| 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

    *G%ds2Rr>k2;5FkYl;(nQFrrV5v& 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


    La3M`Z|N?33B9`x(2lhdG%ySHTyh9jq7_mEyFLL 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
    Un==W$qM>jy%K612m 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
      b`L#E<5gO!cbS!lqoqrie zA3l;2%k8cPJeF-!Jq3+V>n@tOzD$cbl6>5_^<($&#p~}+ULL z2ak47XOo8yCcV@BNi+@nyS=^B$;115;iK8ZaKE=Vwe+r@N5pX=OG(y1q{StX%*|KTc@*Ly zvuV|?tyRX%@~%z`)}wIPV#iB|R!w!+)Zew_4$hm~GE*N)*`5gVf|g%XiBnYqPewoG zWR0JBXuFr4JySz*kxj#-BRBM#F_E=)pJvk~8=QEZz>P`E^l`@l7w@89 z7jYg$-NY251X~PR z8J1v{Vrk~^;NMtnNiw!y z;iyHy>$IAF3e55QYN8;URWdWl7o;4c#})SfJdb8tW`5?9Ih;xr=YpWkTWC3j7k05Y zy@*RXCyr$ExBI+l@I$tJyI++@S|nyb15P!|JuW<}*z;j(v|dDJhI=AunSM9&NDJu% zZiT>(mGA%<4ub^R7 z_w5UhKO7w%ym}=bexj$bEQg*ju%NVxg%_dfHI!nP7uJk>*a(TEiX

      InU)zCbI~JS?+(Xjxo5XSA}dyhzvZ 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

      B<=7Hv)*6 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%0A>=o&<< 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 + + + + /extApps/aai.war + + /services/aai/webapp + /staticContent/aai + diff --git a/src/main/resources/logging/AAIUIMsgs.properties b/src/main/resources/logging/AAIUIMsgs.properties new file mode 100644 index 0000000..99e40c8 --- /dev/null +++ b/src/main/resources/logging/AAIUIMsgs.properties @@ -0,0 +1,797 @@ +#Resource key=Error Code|Message text|Resolution text |Description text +####### +#Newlines can be utilized to add some clarity ensuring continuing line +#has at least one leading space +#ResourceKey=\ +# ERR0000E\ +# Sample error msg txt\ +# Sample resolution msg\ +# Sample description txt +# +###### +#Error code classification category +#000 Info/Debug +#100 Permission errors +#200 Availability errors/Timeouts +#300 Data errors +#400 Schema Interface type/validation errors +#500 Business process errors +#900 Unknown errors +# +######################################################################## + +#-------------------- 000 Series Info/Warning/Debug --------------------# + +DANGLING_NODE_WARNING=\ + AAIUI0001W|\ + Dangling node issue detected: {0} + +FILE_READ_IN_PROGRESS=\ + AAIUI0002W|\ + Attempting getFileContents() for file: {0} + +VISUALIZATION_GRAPH_OUTPUT=\ + AAIUI0003I|\ + Generated graph output has {0} node(s) and {1} link(s) + +MAX_EVALUATION_ATTEMPTS_EXCEEDED=\ + AAIUI0006I|\ + Evaluate node depths exceeded max evaluation attempts + +SYNC_DURATION=\ + AAIUI0007I|\ + {0} + +SYNC_TO_BEGIN=\ + AAIUI0008I|\ + [{0}] next synchronization operation will begin at {1} + +WILL_RETRIEVE_TXN=\ + AAIUI0009I|\ + About to retrieve the txn {0} + +ALL_TRANSACTIONS_RESOLVED=\ + AAIUI00010I|\ + All transactions are resolved, total resolve time was, {0}, total links retrieved, {1}, with an opTime of, {2} ms + +OUTSTANDING_WORK_PENDING_NODES=\ + AAIUI00011I|\ + Method hasOutstandingWork: Number of pending nodes, {0} + +OPERATION_TIME=\ + AAIUI00012I|\ + Operation: {0} - Time taken: {1} + +NO_RELATIONSHIP_DISCOVERED=\ + AAIUI00013I|\ + No relationships discovered for entity: {0} + +ACTIVE_INV_NODE_CHANGE_DEPTH=\ + AAIUI00014I|\ + AIN - {0} - changing depth from {1} to {2} + +ACTIVE_INV_NODE_CHANGE_STATE=\ + AAIUI00015I|\ + [{0}], State change from {1} to {2}, caused by action {3} + +ACTIVE_INV_NODE_CHANGE_STATE_NO_NODE_ID=\ + AAIUI00016I|\ + Node state change from {0} => {1} caused by action = {2} + +INITIALIZE_OXM_MODEL_LOADER=\ + AAIUI00017I|\ + Initializing OXM Model Loader + +OXM_READ_ERROR_NONVERBOSE=\ + AAIUI00018I|\ + Unable to Read OXM File + +OXM_LOAD_SUCCESS=\ + AAIUI00019I|\ + OXM File Loaded Successfully + +OXM_PARSE_ERROR_NONVERBOSE=\ + AAIUI00020I|\ + Unable to Parse OXM File + +ETAG_RETRY_SEQ=\ + AAIUI00021D|\ + doEdgeTagQueryWithRetries: attempt number = {0} + +QUERY_AAI_RETRY_SEQ=\ + AAIUI00022D|\ + queryActiveInventory: {0} attempt number = {1} + +QUERY_AAI_RETRY_DONE_SEQ=\ + AAIUI00023D|\ + queryActiveInventory: {0} after = {1} attempt(s). + +QUERY_AAI_RETRY_MAXED_OUT=\ + AAIUI00024I|\ + Failed to queryActiveInventory: {0} after max attempt(s). + +DATA_CACHE_SUCCESS=\ + AAIUI00025D|\ + InMemoryEntityCache cached data with key = {0} + +RESTFULL_OP_COMPLETE=\ + AAIUI00028I|\ + doRestfulOperation() operation for {0} execution time = {1} ms for link = {2}, ResultCode = {3} + +COOKIE_FOUND=\ + AAIUI00030I|\ + attESHr cookie found in the request <{0}> + +INDEX_ALREADY_EXISTS=\ + AAIUI00031I|\ + [{0}] - Index Already Exists + +INDEX_RECREATED=\ + AAIUI00032I|\ + [{0}] - Index successfully re-created + +INDEX_EXISTS=\ + AAIUI00033I|\ + [{0}] - Index exists + +INDEX_INTEGRITY_CHECK_FAILED=\ + AAIUI00034W|\ + [{0}] - Index Integrity check failed, a failure occurred re-creating index. Aborting sync operation. Index Creation error = {1} + +INDEX_NOT_EXIST=\ + AAIUI00035I|\ + [{0}] - Index Does not Exist + +SYNC_INTERNAL_STATE_CHANGED=\ + AAIUI00036I|\ + [{0}] Changing from state = {1} -> {2} caused by {3} + +SYNC_START_TIME=\ + AAIUI00037I|\ + Scheduled synchronization will happen on default time '05:00:00 UTC'. Check value for 'synchronizer.syncTask.startTimestamp' parameter + +SKIP_PERIODIC_SYNC_AS_SYNC_DIDNT_FINISH=\ + AAIUI00038I|\ + Synchronization did not finish yet. Skipping periodic synchronization at {0} + +SEARCH_ENGINE_SYNC_STARTED=\ + AAIUI00039I|\ + Search Engine synchronization starting at {0} + +FAILED_TO_RESTORE_TXN_FILE_MISSING=\ + AAIUI00040D|\ + Failed to restore txn because {0} does not exist. + +ERROR_BUILDING_RESPONSE_FOR_TABLE_QUERY=\ + AAIUI00041W|\ + Caught an exception while building a search response for table query. Error: {0} + +ERROR_BUILDING_SEARCH_RESPONSE=\ + AAIUI00042W|\ + Caught an exception while building a search response. Error: {0} + +WAIT_FOR_ALL_SELFLINKS_TO_BE_COLLECTED=\ + AAIUI00043D|\ + Waiting for all self-link lists to be collected + +ES_SIMPLE_PUT=\ + AAIUI00044I|\ + Element {0} not discovered for merge. Simple put will be used. + +ES_OPERATION_RETURN_CODE=\ + AAIUI00045I|\ + Operation did not return 200, instead returned code : {0} + +ES_CROSS_REF_SYNC_VERSION_CONFLICT=\ + AAIUI00046W|\ + Store document failed during cross reference entity synchronization due to version conflict. Entity will be resynced. + +ES_PKEYVALUE_NULL=\ + AAIUI00047W|\ + getPopulatedDocument() pKeyValue is null for entityType : {1} + +ES_SYNC_CLEAN_UP=\ + AAIUI00048I|\ + ElasticSearchEntityPurger.performCleanup() for indexName : {0} + +ES_SYNC_CLEAN_UP_SIZE=\ + AAIUI00049I|\ + [ {0} ], performCleanup(), Pre-Sync Collection Size : {1} and Post-Sync Collection Size : {2} + +ES_SYNC_SELECTIVE_DELETE=\ + AAIUI00050I|\ + About to perform selective delete with indexName={0}, indexType {1}, numrecords= {2} + +ES_BULK_DELETE=\ + AAIUI00051I|\ + [ {0} ] - Sending bulk delete request with a total of {1} records + +COLLECT_TIME_WITH_SUCCESS=\ + AAIUI00052I|\ + retrieve {0}AllDocumentIdentifiers operation completed in {0} ms successfully + +SYNC_NUMBER_REQ_FETCHES=\ + AAIUI00053D|\ + numRequiredFetches : {0} + +SYNC_NUMBER_REQ_FETCHES=\ + AAIUI00054D|\ + Total fetched {0} of total available {1} + +COLLECT_TOTAL=\ + AAIUI00055I|\ + retrieve {0}: Total returned : {1} + +COLLECT_TOTAL_TIME=\ + AAIUI00056I|\ + retrieve {0}, took = {0} + +ES_SCROLL_CONTEXT_ERROR=\ + AAIUI00057W|\ + Failed to get results from elastic search scroll context. Error cause : {0} + +ES_BULK_DELETE_SKIP=\ + AAIUI00058I|\ + Skipping bulkDelete(); operation because docs to delete list is empty + +ES_BULK_DELETE_START=\ + AAIUI00059I|\ + bulkDelete: about to delete {0} docs + +GEO_SYNC_IGNORING_ENTITY=\ + AAIUI00060I|\ + GeoSynchronizer ignoring an entity of type {0} because of missing / invalid long/lat coordinates. Entity : {1} + +HISTORICAL_ENTITY_COUNT_SUMMARIZER_STARTING=\ + AAIUI00061I|\ + Historical Entity Count Summarizer starting at {0} + +HISTORICAL_SYNC_PENDING=\ + AAIUI00062I|\ + History Entity Summarizer is already running, skipping request for another doSync + +HISTORICAL_SYNC_TO_BEGIN=\ + AAIUI00063I|\ + Next historical entity summary will begin at {0} + +HISTORICAL_SYNC_DURATION=\ + AAIUI00064I|\ + {0} synchronization took {1} ms. + +DEBUG_GENERIC=\ + AAIUI00065D|\ + {0} + +INFO_GENERIC=\ + AAIUI00066I|\ + {0} + +WARN_GENERIC=\ + AAIUI00067W|\ + {0} + +VALID_REDIRECT_URL=\ + AAIUI00070D|\ + Redirecting to login URL: {0} + +LOGIN_FILTER_INFO=\ + AAIUI00071I|\ + {0} + +LOGIN_FILTER_DEBUG=\ + AAIUI00072D|\ + {0} + +#-------------------- 300 Series Errors --------------------# + +ETAG_WAIT_INTERRUPTION=\ + AAIUI3001E|\ + doEdgeTagQueryWithRetries: interrupted while sleeping with cause = {0} + +QUERY_AAI_WAIT_INTERRUPTION=\ + AAIUI3002E|\ + queryActiveInventoryWithRetries: interrupted while sleeping with cause = {0} + +EXECUTOR_SERV_EXCEPTION=\ + AAIUI3003E|\ + Thread: {0}. The following exception has occurred: {1} + +SYNC_NOT_VALID_STATE_DURING_REQUEST=\ + AAIUI3006E|\ + Sync requested while synchronizer not in valid state. Current internal state: {0} + +SYNC_SKIPPED_SYNCCONTROLLER_NOT_INITIALIZED=\ + AAIUI3007E|\ + SyncController has not been initialized. Synchronization skipped + +ENTITY_SYNC_FAILED_DESCRIPTOR_NOT_FOUND=\ + AAIUI3008E|\ + Entity sync failed because entity descriptor could not be located for entityType = {0} + +ENTITY_SYNC_FAILED_DURING_AAI_RESPONSE_CONVERSION=\ + AAIUI3009E|\ + Sync Entity Failure caused by error in converting AAI response into an object. + +ENTITY_SYNC_FAILED_QUERY_ERROR=\ + AAIUI30010E|\ + {0} + +ENTITY_SYNC_FAILED_SELFLINK_AMBIGUITY=\ + AAIUI30011E|\ + Entity sync failed due to self-link determination ambiguity. Unexpected number of links = {0} + +AGGREGATION_KEY_ERROR=\ + AAIUI30012E|\ + Failed to derive {0} for aggregation by {1} + +INTERRUPTED=\ + AAIUI30013E|\ + Interrupted {0} while waiting for elastic search tasks to be processed with error : {1} + +JSON_PROCESSING_ERROR=\ + AAIUI30014E|\ + Failed to process json with error : {0} + +HISTORICAL_COLLECT_ERROR=\ + AAIUI30015E|\ + Caught an error while collecting results for historical entity summary. Error : {0} + +HISTORICAL_ENTITY_COUNT_SUMMARIZER_NOT_STARTED=\ + AAIUI30016E|\ + HistoricalEntityCountSummaryTask has not been initialized. Synchronization skipped + +OXM_FAILED_RETRIEVAL=\ + AAIUI30017E|\ + Failed to load searchable entities for {0} in OXM file. Synchronizer stopped. + +SELF_LINK_GET_NO_RESPONSE=\ + AAIUI30018E|\ + AAI did not provide a response for self-link: {0} + +ES_BULK_DELETE=\ + AAIUI30019E|\ + [ {0} ] - An error occurred while attempting to perform selective delete to elastic search index with an error cause : {1} + +COLLECT_TIME_WITH_ERROR=\ + AAIUI30020E|\ + retrieve {0} operation completed in {1} ms with some errors + +ES_SEARCHABLE_ENTITY_SYNC_ERROR=\ + AAIUI30021E|\ + {0} + +ES_STORE_FAILURE=\ + AAIUI30022E|\ + There was an error storing the document into elastic search. Error : {0} + +ES_PRE_SYNC_FAILURE=\ + AAIUI30023E|\ + {0} An error occured while collecting the pre-sync object id collection. Error : {1} + +ES_CROSS_REF_SYNC_FAILURE=\ + AAIUI30024E|\ + Store document failed during cross reference entity synchronization with result code {0} and result message {1} + +ES_FAILED_TO_CONSTRUCT_URI=\ + AAIUI30025E|\ + Failed to construct an elastic search uri during re-sync, with error : {0} + +ES_RETRIEVAL_FAILED_RESYNC=\ + AAIUI30026E|\ + Elasticsearch retrieval failed for re-sync. Error : {0} + +ES_CROSS_ENTITY_RESYNC_LIMIT=\ + AAIUI30027E|\ + Cross entity re-sync limit reached for {0}, re-sync will no longer be attempted for this entity + +ES_CROSS_ENTITY_REF_PUT=\ + AAIUI30028E|\ + Cross entity reference sync UPDATE PUT error: {0} + +ES_ABORT_CROSS_ENTITY_REF_SYNC=\ + AAIUI30029E|\ + Error extracting {0} from response, aborting cross entity ref sync of {1}. Error : {2} + +MISSING_ENTITY_DESCRIPTOR=\ + AAIUI30030E + Missing entity descriptor for type : {0} + +SELF_LINK_GET=\ + AAIUI30031E|\ + Failure during self link GET. Error : {0} + +SELF_LINK_CROSS_REF_SYNC=\ + AAIUI30032E|\ + Self link GET has returned null during cross entity reference sync + +ES_FAILED_TO_CONSTRUCT_QUERY=\ + AAIUI30033E|\ + Failed to construct an elastic search uri with error : {0} + +ES_RETRIEVAL_FAILED=\ + AAIUI30034E|\ + Elasticsearch retrieval failed. Error : {0} + +ES_LINK_UPSERT=\ + AAIUI30035E|\ + Error creating link for upsert. Error : {0} + +ERROR_GENERIC=\ + AAIUI30036E|\ + {0} + +ERROR_PROCESSING_REQUEST=\ + AAIUI30037E\ + Failure to process request with error: {1} + +ERROR_CSP_CONFIG_FILE=\ + AAIUI30038E|\ + Failed to load CSP filter configuration properties + +ERROR_SHUTDOWN_EXECUTORS=\ + AAIUI30039E|\ + Failure during shutdown of executors. Error : {0} + +ERROR_LOADING_OXM=\ + AAIUI30040E|\ + Failed to load searchable entities in OXM file. Synchronizer stopped. + +ERROR_GETTING_DATA_FROM_AAI=\ + AAIUI30041E|\ + An error occurred getting data from AAI. Error : {0} + +SOT_FILE_NOT_FOUND=\ + AAIUI30042E|\ + Error in reading source-of-truth configuration + +INVALID_REQUEST_PARAMS=\ + AAIUI30043E|\ + Invalid request parameters + +PEGGING_ERROR=\ + AAIUI30044E|\ + Pegging UNKNOWN_EXCEPTION due to unexpected exception = {0} + +INVALID_REQUEST=\ + AAIUI30046E|\ + {0} + +INVALID_URL_VERBOSE=\ + AAIUI30047E|\ + Invalid URL: {0}. Reason: {1}. + +DI_DATA_NOT_FOUND_NONVERBOSE=\ + AAIUI30048E|\ + No data integrity data found for rowID: {0}. + +DI_DATA_NOT_FOUND_VERBOSE=\ + AAIUI30049E|\ + No data integrity data found for rowID: {0} after {1} attempts. + +OXM_FILE_NOT_FOUND=\ + AAIUI30050E|\ + Unable to find latest OXM file in directory: {0} + +OXM_READ_ERROR_VERBOSE=\ + AAIUI30051E|\ + Unable to read OXM file: {0} + +ERROR_PARSING_JSON_PAYLOAD_NONVERBOSE=\ + AAIUI30052E|\ + Error in parsing JSON payload for {0} + +ERROR_PARSING_JSON_PAYLOAD_VERBOSE=\ + AAIUI30053E|\ + Error in parsing JSON payload: {0} + +ERROR_FETCHING_JSON_VALUE=\ + AAIUI30054E|\ + Error in getting value for key: {0}. Data: {1} + +OXM_READ_PARSE_VERBOSE=\ + AAIUI30055E|\ + Unable to parse OXM file: {0}. The following exception has occurred: {1} + +OXM_PROP_DEF_ERR_CROSS_ENTITY_REF=\ + AAIUI30056E|\ + Invalid OXM definition of xml-property 'crossEntityReference' for entity : {0} with a value of : {1} + +SYNC_INVALID_CONFIG_PARAM=\ + AAIUI30057E|\ + {0} + +ERROR_PARSING_PARAMS=\ + AAIUI30058E|\ + Error parsing parameters. Error: {0} + +ERROR_SORTING_VIOLATION_DATA=\ + AAIUI30059E|\ + Error in sorting violation data based on key: {0} + +CONFIGURATION_ERROR=\ + AAIUI30060E|\ + Failed to load {0} configurations + +ERROR_SERVLET_PROCESSSING=\ + AAIUI30061E|\ + Failure during servlet request processing. Error: {0} + +QUERY_AAI_RETRY_FAILURE_WITH_SEQ=\ + AAIUI30062E|\ + Failed to queryActiveInventory {0} attempt number = {1} + +DISK_CACHE_READ_IO_ERROR=\ + AAIUI30063E|\ + Failed to read from disk cache. Exception: {0} + +DISK_CREATE_DIR_IO_ERROR=\ + AAIUI30064E|\ + Failed to create directory in disk. Exception: {0} + +DISK_DATA_WRITE_IO_ERROR=\ + AAIUI30065E|\ + Failed to persist data in disk. Exception: {0} + +DISK_NAMED_DATA_WRITE_IO_ERROR=\ + AAIUI30066E|\ + Failed to persist data for {0} in disk. Exception: {1} + +DISK_NAMED_DATA_READ_IO_ERROR=\ + AAIUI30067E|\ + Failed to retrieve data for {0} from disk. Exception: {1} + +OFFLINE_STORAGE_PATH_ERROR=\ + AAIUI30068E|\ + Error in determining offline storage path for link: {0}. Exception: {1} + +RESTFULL_OP_ERROR_VERBOSE=\ + AAIUI30069E|\ + Error retrieving link: {0} from restful endpoint due to error: {1} + +USER_AUTHORIZATION_FILE_UNAVAILABLE=\ + AAIUI30071E|\ + User authorization file unavailable. User {0} cannot be authorized. + +COOKIE_NOT_FOUND=\ + AAIUI30072E|\ + No cookies found in the request + +CONFIG_NOT_FOUND_VERBOSE=\ + AAIUI30073E|\ + Error in loading configuration from file: {0}. Cause: {1} + +FILE_NOT_FOUND=\ + AAIUI30074E|\ + Failed to find file: {0} + +SELF_LINK_NULL_EMPTY_RESPONSE=\ + AAIUI30076E|\ + AIN - Failed to process null or empty pathed self link response + +SELF_LINK_RELATIONSHIP_LIST_ERROR=\ + AAIUI30077E|\ + AIN - Caught an error processing the self-link relationship-list: {0} + +SEARCH_SERVLET_ERROR=\ + AAIUI30078E|\ + Search Servlet Error: {0} + +SEARCH_RESPONSE_BUILDING_EXCEPTION=\ + AAIUI30079E|\ + Caught an exception while building a search response. Error: {0} + +SEARCH_TAG_ANNOTATION_ERROR=\ + AAIUI30080E|\ + An error occurred annotating search tags. Search tags: {0} Error: {1} + +QUERY_FAILED_UNHANDLED_APP_TYPE=\ + AAIUI30081E|\ + Do-Query failed because of an unhandled application type: {0} + +ENTITY_NOT_FOUND_IN_OXM=\ + AAIUI30082E|\ + No {0} descriptors found in OXM file + +JSON_CONVERSION_ERROR=\ + AAIUI30083E|\ + An error occurred while converting JSON into {0}. Error: {1} + +ERROR_LOADING_OXM_SEARCHABLE_ENTITIES=\ + AAIUI30084E|\ + Failed to load searchable entities in OXM file. Synchronizer stopped. + +AAI_RETRIEVAL_FAILED_GENERIC=\ + AAIUI30085E|\ + Retrieving data from AAI failed with error = {0} + +AAI_RETRIEVAL_FAILED_FOR_SELF_LINK=\ + AAIUI30086E|\ + Failed to get result from AAI for link = {0} + +FAILED_TO_REGISTER_DUE_TO_NULL=\ + AAIUI30087E|\ + {0} + +FAILED_TO_ADD_SKELETON_NODE=\ + AAIUI30088E|\ + Failed to add skeleton node: {0} + +FAILED_TO_PROCESS_SKELETON_NODE=\ + AAIUI30089E|\ + Failed to process skeleton node: {0} + +INVALID_RESOLVE_STATE_DURING_INIT=\ + AAIUI30090E|\ + An error has occurred because Node in INIT state should not already have its self link resolved + +FAILED_TO_PROCESS_INITIAL_STATE=\ + AAIUI30091E|\ + Failed to process initial state: {0} + +SKIPPING_RELATIONSHIP=\ + AAIUI30092E|\ + Skipping relationship because failed to generate nodeId for relationship, {0} + +FAILED_TO_DETERMINE_NODE_ID=\ + AAIUI30093E|\ + Failed to determine node id: {0} + +EXTRACTION_ERROR=\ + AAIUI30094E|\ + Extraction failed: {0} + +SELF_LINK_NODE_PARSE_ERROR=\ + AAIUI30095E|\ + Self link node parsing error: {0} + +SELF_LINK_RETRIEVAL_FAILED=\ + AAIUI30096E|\ + Complex Entity Self link retrieval for link = {0} failed with error code = {1} and message = {2} + +SELF_LINK_DETERMINATION_FAILED_GENERIC=\ + AAIUI30097E|\ + Self link determination failed for entity with link = {0} + +SELF_LINK_DETERMINATION_FAILED_UNEXPECTED_LINKS=\ + AAIUI30098E|\ + Self link determination failed with an ambiguous result with an unexpected number of links = {0} + +ROOT_NODE_DISCOVERED=\ + AAIUI30099E|\ + Root node discovered for search target node ID = {0} + +SELF_LINK_PROCESS_NEIGHBORS_ERROR=\ + AAIUI300100E|\ + Self link node process neighbors error: {0} + +SELF_LINK_JSON_PARSE_ERROR=\ + AAIUI300101E|\ + Self link JSON parsing error: {0} + +SELF_LINK_PROCESSING_ERROR=\ + AAIUI300102E|\ + Self link processing error: {0} + +UNHANDLED_OBJ_TYPE_FOR_ENTITY_TYPE=\ + AAIUI300103E|\ + Error: Unhandled object type for entityType, {0}, which is not an array + +ATTRIBUTE_GROUP_FAILURE=\ + AAIUI300104E|\ + Failure to process attribute group field, fields is null for attribute group {0} + +EXCEPTION_CAUGHT=\ + AAIUI300105E|\ + Exception caught. {0} Exception: {1} + +ERROR_EXTRACTING_FROM_RESPONSE=\ + AAIUI300106E|\ + {0} + +PROCESSING_LOOP_INTERUPTED=\ + AAIUI300107E|\ + Processing loop interrupted: {0} + +IGNORING_SKELETON_NODE=\ + AAIUI300108E|\ + Ignoring skeleton node with unique ID, {0}, because of processing error + +VISUALIZATION_OUTPUT_ERROR=\ + AAIUI300109E|\ + An error occurred while preparing D3 visualization output: {0} + +FAILURE_TO_PROCESS_REQUEST=\ + AAIUI300111E\ + Failure to process request. {0} + +FAILED_TO_DETERMINE=\ + AAIUI300112E\ + Failed to determine {0} + +FAILED_TO_ANALYZE=\ + AAIUI300113E|\ + Failed to analyze {0} + +FAILED_TO_GET_NODES_QUERY_RESULT=\ + AAIUI300114E|\ + Failed to get nodes-query result from AAI with error {0} + +UNEXPECTED_NUMBER_OF_LINKS=\ + AAIUI300115E|\ + Unexpected number of links found. Expected {0}, but found {1} + +ITEM_TYPE_NULL=\ + AAIUI300116E|\ + Item type null for node, {0} + +UNEXPECTED_TOKEN_COUNT=\ + AAIUI300117E|\ + Unexpected number of tokens returned from splitting typeAndField by period delimiter. Field value: {0} + +ADD_SEARCH_TARGET_ATTRIBUTES_FAILED=\ + AAIUI300118E|\ + Add SearchTargetAttributes failure: {0} + +ERROR_LOADING_OXM_SUGGESTIBLE_ENTITIES=\ + AAIUI300120E|\ + Failed to load suggestible entities in OXM file. Synchronizer stopped. + +ES_SUGGESTION_SEARCH_ENTITY_SYNC_ERROR=\ + AAIUI300121E|\ + {0} + +ES_AGGREGATION_SUGGESTION_ENTITY_SYNC_ERROR=\ + AAIUI300122E|\ + {0} + +ENTITY_SYNC_SEARCH_TAG_ANNOTATION_FAILED=\ + AAIUI300123E|\ + {0} + +UNSUPPORTED_URL_ENCODING=\ + AAIUI300124E|\ + Unsupported URL encoding: {0} + +INVALID_REDIRECT_URL=\ + AAIUI300125E|\ + Cannot redirect to invalid URL: {0} + +ERROR_REMOVING_URL_PARAM=\ + AAIUI300127E|\ + Failed to remove query param from URL: {0} + +ERROR_INVALID_HASH=\ + AAIUI300128E|\ + Invalid hash value: {0} + +ERROR_HASH_NOT_FOUND=\ + AAIUI300129E|\ + Could not find hash value. + +ERROR_READING_HTTP_REQ_PARAMS=\ + AAIUI300130E|\ + Could not read HTTP header parameters. + +ERROR_D3_GRAPH_VISUALIZATION=\ + AAIUI300129E|\ + Failed to generate D3 graph visualization, due to a servlet exception with a cause: {0} + +ERROR_AAI_QUERY_WITH_RETRY=\ + AAIUI300130E|\ + Querying AAI with retry failed due to exception: {0} + + +#-------------------- 900 Series Errors --------------------# + +UNKNOWN_SERVER_ERROR=\ + AAIUI9001E|\ + Unknown Server Error: {0} + +SEARCH_ADAPTER_ERROR=\ + AAIUI9002E|\ + Search Adapter Error: {0} + +QUERY_PARAM_EXTRACTION_ERROR=\ + AAIUI9003E|\ + Query Parameter Self-Link Extraction Error: {0} + \ No newline at end of file diff --git a/src/main/runtime/context/__module.ajsc.namespace.name__#__module.ajsc.namespace.version__.context b/src/main/runtime/context/__module.ajsc.namespace.name__#__module.ajsc.namespace.version__.context new file mode 100644 index 0000000..8514196 --- /dev/null +++ b/src/main/runtime/context/__module.ajsc.namespace.name__#__module.ajsc.namespace.version__.context @@ -0,0 +1 @@ +{"context":{"contextClass":"ajsc.Context","contextId":"__module_ajsc_namespace_name__:__module_ajsc_namespace_version__","contextName":"__module_ajsc_namespace_name__","contextVersion":"__module_ajsc_namespace_version__","description":"__module_ajsc_namespace_name__ Context"}} \ No newline at end of file diff --git a/src/main/runtime/context/default#0.context b/src/main/runtime/context/default#0.context new file mode 100644 index 0000000..d1b5ab4 --- /dev/null +++ b/src/main/runtime/context/default#0.context @@ -0,0 +1 @@ +{"context":{"contextClass":"ajsc.Context","contextId":"default:0","contextName":"default","contextVersion":"0","description":"Default Context"}} \ No newline at end of file diff --git a/src/main/runtime/deploymentPackage/__module.ajsc.namespace.name__#__module.ajsc.namespace.version__.json b/src/main/runtime/deploymentPackage/__module.ajsc.namespace.name__#__module.ajsc.namespace.version__.json new file mode 100644 index 0000000..d0954cf --- /dev/null +++ b/src/main/runtime/deploymentPackage/__module.ajsc.namespace.name__#__module.ajsc.namespace.version__.json @@ -0,0 +1 @@ +{"deploymentPackage":{"Class":"ajsc.DeploymentPackage","Id":"__module.ajsc.namespace.name__:__module_ajsc_namespace_version__","namespace":"__module_ajsc_namespace_name__","namespaceVersion":"__module_ajsc_namespace_version__","description":"__module_ajsc_namespace_name__ __module_ajsc_namespace_version__ - default description","userId":"ajsc"}} \ No newline at end of file diff --git a/src/main/runtime/shiroRole/ajscadmin.json b/src/main/runtime/shiroRole/ajscadmin.json new file mode 100644 index 0000000..f5e981e --- /dev/null +++ b/src/main/runtime/shiroRole/ajscadmin.json @@ -0,0 +1 @@ +{"shiroRoleClass":"ajsc.auth.ShiroRole","shiroRoleId":"ajscadmin","name":"ajscadmin","permissions":"[ajscadmin:*, ajsc:*]"} \ No newline at end of file diff --git a/src/main/runtime/shiroRole/contextadmin#__module.ajsc.namespace.name__.json b/src/main/runtime/shiroRole/contextadmin#__module.ajsc.namespace.name__.json new file mode 100644 index 0000000..2dae9f5 --- /dev/null +++ b/src/main/runtime/shiroRole/contextadmin#__module.ajsc.namespace.name__.json @@ -0,0 +1 @@ +{"shiroRoleClass":"ajsc.auth.ShiroRole","shiroRoleId":"contextadmin:__module_ajsc_namespace_name__","name":"contextadmin:__module_ajsc_namespace_name__","permissions":"[]"} \ No newline at end of file diff --git a/src/main/runtime/shiroRole/contextadmin#default.json b/src/main/runtime/shiroRole/contextadmin#default.json new file mode 100644 index 0000000..5de814e --- /dev/null +++ b/src/main/runtime/shiroRole/contextadmin#default.json @@ -0,0 +1 @@ +{"shiroRoleClass":"ajsc.auth.ShiroRole","shiroRoleId":"contextadmin:default","name":"contextadmin:default","permissions":"[]"} \ No newline at end of file diff --git a/src/main/runtime/shiroUser/ajsc.json b/src/main/runtime/shiroUser/ajsc.json new file mode 100644 index 0000000..f4c7855 --- /dev/null +++ b/src/main/runtime/shiroUser/ajsc.json @@ -0,0 +1 @@ +{"shiroUserClass":"ajsc.auth.ShiroUser","shiroUserId":"ajsc","passwordHash":"9471697417008c880720ba54c6038791ad7e98f3b88136fe34f4d31a462dd27a","permissions":"[*:*]","username":"ajsc"} \ No newline at end of file diff --git a/src/main/runtime/shiroUserRole/ajsc#ajscadmin.json b/src/main/runtime/shiroUserRole/ajsc#ajscadmin.json new file mode 100644 index 0000000..cb8d483 --- /dev/null +++ b/src/main/runtime/shiroUserRole/ajsc#ajscadmin.json @@ -0,0 +1 @@ +{"shiroUserRoleClass":"ajsc.auth.ShiroUserRole","shiroUserRoleId":"ajsc:ajscadmin","roleId":"ajscadmin","userId":"ajsc"} \ No newline at end of file diff --git a/src/main/runtime/shiroUserRole/ajsc#contextadmin#__module.ajsc.namespace.name__.json b/src/main/runtime/shiroUserRole/ajsc#contextadmin#__module.ajsc.namespace.name__.json new file mode 100644 index 0000000..95d2361 --- /dev/null +++ b/src/main/runtime/shiroUserRole/ajsc#contextadmin#__module.ajsc.namespace.name__.json @@ -0,0 +1 @@ +{"shiroUserRoleClass":"ajsc.auth.ShiroUserRole","shiroUserRoleId":"ajsc:contextadmin:__module_ajsc_namespace_name__","roleId":"contextadmin:__module_ajsc_namespace_name__","userId":"ajsc"} \ No newline at end of file diff --git a/src/main/runtime/shiroUserRole/ajsc#contextadmin#default.json b/src/main/runtime/shiroUserRole/ajsc#contextadmin#default.json new file mode 100644 index 0000000..2bd5063 --- /dev/null +++ b/src/main/runtime/shiroUserRole/ajsc#contextadmin#default.json @@ -0,0 +1 @@ +{"shiroUserRoleClass":"ajsc.auth.ShiroUserRole","shiroUserRoleId":"ajsc:contextadmin:default","roleId":"contextadmin:default","userId":"ajsc"} \ No newline at end of file diff --git a/src/main/scripts/encNameValue.sh b/src/main/scripts/encNameValue.sh new file mode 100644 index 0000000..daefd00 --- /dev/null +++ b/src/main/scripts/encNameValue.sh @@ -0,0 +1,20 @@ +# The script invokes the com.amdocs.aai.audit.security.encryption.EncryptedPropValue class to generate an encrypted value +# e.g +# ./encNameValue.sh odl.auth.password admin +# will return: +# odl.auth.password.x=f1e2c25183ef4b4ff655e7cd94d0c472 +# +if [ "$#" -ne 2 ]; then + echo "Illegal number of parameters (expected 2)" + echo "Usage: `basename $0` " 1>&2 + exit 1 +fi + +# On Windows we must use a different CLASSPATH separator character +if [ "$(expr substr $(uname -s) 1 5)" == "MINGW" ]; then + CPSEP=\; +else + CPSEP=: +fi + +java -cp ".${CPSEP}../extJars/*" com.att.aai.util.EncryptedPropValue -n $1 -v $2 diff --git a/src/main/scripts/start.sh b/src/main/scripts/start.sh new file mode 100644 index 0000000..3d1af06 --- /dev/null +++ b/src/main/scripts/start.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +BASEDIR="/opt/app/sparky" +AJSC_HOME="$BASEDIR" + +if [ -z "$CONFIG_HOME" ]; then + echo "CONFIG_HOME must be set in order to start up process" + exit 1 +fi + +if [ -z "$KEY_STORE_PASSWORD" ]; then + echo "KEY_STORE_PASSWORD must be set in order to start up process" + exit 1 +else + echo -e "KEY_STORE_PASSWORD=$KEY_STORE_PASSWORD\n" >> $AJSC_CONF_HOME/etc/sysprops/sys-props.properties +fi + +if [ -z "$KEY_MANAGER_PASSWORD" ]; then + echo "KEY_MANAGER_PASSWORD must be set in order to start up process" + exit 1 +else + echo -e "KEY_MANAGER_PASSWORD=$KEY_MANAGER_PASSWORD\n" >> $AJSC_CONF_HOME/etc/sysprops/sys-props.properties +fi + +CLASSPATH="$AJSC_HOME/lib/ajsc-runner-2.0.0.jar" +CLASSPATH="$CLASSPATH:$AJSC_HOME/extJars/" +CLASSPATH="$CLASSPATH:$CONFIG_HOME/portal/" +PROPS="-DAJSC_HOME=$AJSC_HOME" +PROPS="$PROPS -DAJSC_CONF_HOME=$BASEDIR/bundleconfig/" +PROPS="$PROPS -Dlogback.configurationFile=$BASEDIR/bundleconfig/etc/logback.xml" +PROPS="$PROPS -DAJSC_SHARED_CONFIG=$AJSC_CONF_HOME" +PROPS="$PROPS -DAJSC_EXTERNAL_LIB_FOLDERS=$AJSC_HOME/commonLibs" +PROPS="$PROPS -DAJSC_EXTERNAL_PROPERTIES_FOLDERS=$AJSC_HOME/ajsc-shared-config/etc" +PROPS="$PROPS -DAJSC_SERVICE_NAMESPACE=ajsc-tier-support-ui" +PROPS="$PROPS -DAJSC_SERVICE_VERSION=v1" +PROPS="$PROPS -DSOACLOUD_SERVICE_VERSION=0.0.0" +PROPS="$PROPS -Dserver.port=8000" +PROPS="$PROPS -DCONFIG_HOME=$CONFIG_HOME" + +echo $CLASSPATH + +/usr/lib/jvm/java-8-openjdk-amd64/bin/java -Xms1024m -Xmx4096m $PROPS -classpath $CLASSPATH com.att.ajsc.runner.Runner context=/ port=9517 diff --git a/src/test/java/org/openecomp/sparky/analytics/AveragingRingBufferTest.java b/src/test/java/org/openecomp/sparky/analytics/AveragingRingBufferTest.java new file mode 100644 index 0000000..d8a558c --- /dev/null +++ b/src/test/java/org/openecomp/sparky/analytics/AveragingRingBufferTest.java @@ -0,0 +1,133 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.analytics; + +import static org.junit.Assert.assertEquals; + +import java.security.SecureRandom; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * The Class AveragingRingBufferTest. + */ +@RunWith(PowerMockRunner.class) +public class AveragingRingBufferTest { + + protected SecureRandom random = new SecureRandom(); + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + // nothing at the moment + } + + /** + * Validate pre index roll averaging. + */ + @Test + public void validatePreIndexRollAveraging() { + + AveragingRingBuffer arb = new AveragingRingBuffer(5); + assertEquals(0, arb.getAvg()); + + /* + * On initial buffer fill, the average will be re-calculated on the fly for the first nth data + * points until the data buffer has been filled the first time, and then the buffer + * automatically recalculates the average every time the buffer index rolls over, to the keep + * the average relative to the last "nth" data points. + */ + + // [ 1, 0, 0, 0, 0 ], sum = 1, avg = 1/1 =1 + arb.addSample(1); + assertEquals(1, arb.getAvg()); + + // [ 1, 2, 0, 0, 0 ], sum = 3, avg = 3/2 = 1 + arb.addSample(2); + assertEquals(1, arb.getAvg()); + + // [ 1, 2, 3, 0, 0 ], sum = 6, avg = 6/3 = 2 + arb.addSample(3); + assertEquals(2, arb.getAvg()); + + // [ 1, 2, 3, 4, 0 ], sum = 10, avg = 10/4 = 2 + arb.addSample(4); + assertEquals(2, arb.getAvg()); + + // [ 1, 2, 3, 4, 5 ], sum = 15, avg = 15/5 = 3 + arb.addSample(5); + assertEquals(3, arb.getAvg()); + + } + + /** + * Validate post index roll averaging. + */ + @Test + public void validatePostIndexRollAveraging() { + + AveragingRingBuffer arb = new AveragingRingBuffer(5); + arb.addSample(1); + arb.addSample(2); + arb.addSample(3); + arb.addSample(4); + arb.addSample(5); + + /* + * The behavior switches, and now doesn't re-calculate the average until each nth data point, to + * reduce the computational over-head of re-calculating on each value. + */ + + // [ 10, 2, 3, 4, 5 ], + arb.addSample(10); + assertEquals(3, arb.getAvg()); + + // [ 10, 20, 3, 4, 5 ], + arb.addSample(20); + assertEquals(3, arb.getAvg()); + + // [ 10, 20, 30, 4, 5 ], + arb.addSample(30); + assertEquals(3, arb.getAvg()); + + // [ 10, 20, 30, 40, 5 ], + arb.addSample(40); + assertEquals(3, arb.getAvg()); + + // [ 10, 20, 30, 40, 50 ], s=150, avg=150/5=30 + arb.addSample(50); + assertEquals(30, arb.getAvg()); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/analytics/HistogramSamplerTest.java b/src/test/java/org/openecomp/sparky/analytics/HistogramSamplerTest.java new file mode 100644 index 0000000..70e9e3c --- /dev/null +++ b/src/test/java/org/openecomp/sparky/analytics/HistogramSamplerTest.java @@ -0,0 +1,90 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.analytics; + +import java.security.SecureRandom; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * The Class HistogramSamplerTest. + */ +@RunWith(PowerMockRunner.class) +public class HistogramSamplerTest { + + protected SecureRandom random = new SecureRandom(); + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + // nothing at the moment + } + + /** + * Validate basic construction and delimited reporting. + */ + @Test + public void validateBasicConstructionAndDelimitedReporting() { + + HistogramSampler histoSampler = new HistogramSampler("[File byte size]", 500000, 22, 3); + + SecureRandom random = new SecureRandom(); + + for (int x = 0; x < 100000; x++) { + histoSampler.track(random.nextInt(9999999)); + } + + System.out.println(histoSampler.getStats(false, " ")); + + } + + + /** + * Validate basic construction and formatted reporting. + */ + @Test + public void validateBasicConstructionAndFormattedReporting() { + + HistogramSampler histoSampler = new HistogramSampler("[Queue Length Samples]", 100000, 15, 3); + + SecureRandom random = new SecureRandom(); + + for (int x = 0; x < 100000; x++) { + histoSampler.track(random.nextInt(9999999)); + } + + System.out.println(histoSampler.getStats(true, " ")); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/analytics/TransactionRateControllerTest.java b/src/test/java/org/openecomp/sparky/analytics/TransactionRateControllerTest.java new file mode 100644 index 0000000..c5f14e6 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/analytics/TransactionRateControllerTest.java @@ -0,0 +1,217 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.analytics; + +import org.junit.Before; + + +/** + * The Class TransactionRateControllerTest. + */ +public class TransactionRateControllerTest { + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + // nothing at the moment + } + /* + * @Test public void tenTPS_oneThread_validateRateEnforcementWhenAvgResposneTimeIsUnderBudget() { + * + * TransactionRateController trc = new TransactionRateController(10.0, 1, 5); + * + * trc.trackResponseTime(25); trc.trackResponseTime(35); trc.trackResponseTime(45); + * trc.trackResponseTime(55); trc.trackResponseTime(70); + * + * // avg should be 46 ms + * + * assertEquals(54, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void tenTPS_oneThread_validateRateEnforcementWhenAvgResposneTimeIsOverBudget() { + * + * TransactionRateController trc = new TransactionRateController(10.0, 1, 5); + * + * trc.trackResponseTime(75); trc.trackResponseTime(125); trc.trackResponseTime(250); + * trc.trackResponseTime(105); trc.trackResponseTime(23); + * + * // avg should be 115 ms + * + * assertEquals(0, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void oneTPS_oneThread_validateRateEnforcementWhenAvgResposneTimeIsUnderBudget() { + * + * TransactionRateController trc = new TransactionRateController(1.0, 1, 5); + * + * trc.trackResponseTime(25); trc.trackResponseTime(35); trc.trackResponseTime(45); + * trc.trackResponseTime(55); trc.trackResponseTime(70); + * + * // avg should be 46 ms + * + * assertEquals(954, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void oneTPS_oneThread_validateRateEnforcementWhenAvgResposneTimeIsOverBudget() { + * + * TransactionRateController trc = new TransactionRateController(1.0, 1, 5); + * + * trc.trackResponseTime(75); trc.trackResponseTime(125); trc.trackResponseTime(250); + * trc.trackResponseTime(105); trc.trackResponseTime(23); + * + * // avg should be 115 ms + * + * assertEquals(885, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void halfTPS_oneThread_validateRateEnforcementWhenAvgResposneTimeIsUnderBudget() { + * + * TransactionRateController trc = new TransactionRateController(0.5, 1, 5); + * + * trc.trackResponseTime(25); trc.trackResponseTime(35); trc.trackResponseTime(45); + * trc.trackResponseTime(55); trc.trackResponseTime(70); + * + * // avg should be 46 ms + * + * assertEquals(1954, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void halfTPS_oneThread_validateRateEnforcementWhenAvgResposneTimeIsOverBudget() { + * + * TransactionRateController trc = new TransactionRateController(0.5, 1, 5); + * + * trc.trackResponseTime(75); trc.trackResponseTime(125); trc.trackResponseTime(250); + * trc.trackResponseTime(105); trc.trackResponseTime(23); + * + * // avg should be 115 ms + * + * assertEquals(1885, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void tenTPS_tenThreads_validateRateEnforcementWhenAvgResposneTimeIsUnderBudget() { + * + * TransactionRateController trc = new TransactionRateController(10.0, 10, 5); + * + * trc.trackResponseTime(25); trc.trackResponseTime(35); trc.trackResponseTime(45); + * trc.trackResponseTime(55); trc.trackResponseTime(70); + * + * // avg should be 46 ms + * + * assertEquals(540, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void tenTPS_tenThreads_validateRateEnforcementWhenAvgResposneTimeIsOverBudget() { + * + * TransactionRateController trc = new TransactionRateController(10.0, 10, 5); + * + * trc.trackResponseTime(75); trc.trackResponseTime(125); trc.trackResponseTime(250); + * trc.trackResponseTime(105); trc.trackResponseTime(23); + * + * // avg should be 115 ms + * + * assertEquals(0, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void oneTPS_tenThreads_validateRateEnforcementWhenAvgResposneTimeIsUnderBudget() { + * + * TransactionRateController trc = new TransactionRateController(1.0, 10, 5); + * + * trc.trackResponseTime(25); trc.trackResponseTime(35); trc.trackResponseTime(45); + * trc.trackResponseTime(55); trc.trackResponseTime(70); + * + * // avg should be 46 ms + * + * assertEquals(9540, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void oneTPS_tenThreads_validateRateEnforcementWhenAvgResposneTimeIsOverBudget() { + * + * TransactionRateController trc = new TransactionRateController(1.0, 10, 5); + * + * trc.trackResponseTime(75); trc.trackResponseTime(125); trc.trackResponseTime(250); + * trc.trackResponseTime(105); trc.trackResponseTime(23); + * + * // avg should be 115 ms + * + * assertEquals(8850, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void halfTPS_tenThreads_validateRateEnforcementWhenAvgResposneTimeIsUnderBudget() + * { + * + * TransactionRateController trc = new TransactionRateController(0.5, 10, 5); + * + * trc.trackResponseTime(25); trc.trackResponseTime(35); trc.trackResponseTime(45); + * trc.trackResponseTime(55); trc.trackResponseTime(70); + * + * // avg should be 46 ms + * + * assertEquals(19540, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void halfTPS_tenThreads_validateRateEnforcementWhenAvgResposneTimeIsOverBudget() { + * + * TransactionRateController trc = new TransactionRateController(0.5, 10, 5); + * + * trc.trackResponseTime(75); trc.trackResponseTime(125); trc.trackResponseTime(250); + * trc.trackResponseTime(105); trc.trackResponseTime(23); + * + * // avg should be 115 ms + * + * assertEquals(18850, trc.getFixedDelayInMs()); + * + * } + * + * @Test public void oneTPS_fiveThreads_validateRateEnforcementWhenAvgResposneTimeIsOverBudget() { + * + * TransactionRateController trc = new TransactionRateController(1, 5, 5); + * + * trc.trackResponseTime(0); trc.trackResponseTime(0); trc.trackResponseTime(0); + * trc.trackResponseTime(0); trc.trackResponseTime(0); + * + * // avg should be 0 ms + * + * assertEquals(5000, trc.getFixedDelayInMs()); + * + * } + */ + +} diff --git a/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryConfigTest.java b/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryConfigTest.java new file mode 100644 index 0000000..24cb405 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryConfigTest.java @@ -0,0 +1,182 @@ +package org.openecomp.sparky.dal.aai.config; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; +import org.openecomp.sparky.dal.aai.enums.RestAuthenticationMode; +import org.openecomp.sparky.synchronizer.config.TaskProcessorConfig; + +public class ActiveInventoryConfigTest { + + /** + * Test case initialization + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception {} + + @Test + public void validateBasicConstruction_emptyProperties() throws Exception { + + ActiveInventoryConfig config = new ActiveInventoryConfig(getTestProperties()); + + assertNotNull(config); + + } + + private Properties getTestProperties() { + + Properties props = new Properties(); + + props.put("aai.rest.host","aai-host"); + props.put("aai.rest.port","8443"); + props.put("aai.rest.resourceBasePath","/aai/v10"); + props.put("aai.rest.connectTimeoutInMs","30000"); + props.put("aai.rest.readTimeoutInMs","60000"); + props.put("aai.rest.numRequestRetries","5"); + props.put("aai.rest.numResolverWorkers","15"); + + props.put("aai.rest.cache.enabled","false"); + props.put("aai.rest.cache.numWorkers","10"); + props.put("aai.rest.cache.cacheFailures","false"); + props.put("aai.rest.cache.useCacheOnly","false"); + props.put("aai.rest.cache.storageFolderOverride",""); + props.put("aai.rest.cache.maxTimeToLiveInMs","-1"); + + props.put("aai.rest.shallowEntities","cloud-region,complex,vnf-image,att-aic,image"); + + props.put("aai.ssl.truststore.filename","synchronizer.jks"); + props.put("aai.ssl.truststore.type","jks"); + + props.put("aai.ssl.keystore.filename","aai-client-cert.p12"); + props.put("aai.ssl.keystore.pass","70c87528c88dcd9f9c2558d30e817868"); + props.put("aai.ssl.keystore.type","pkcs12"); + + props.put("aai.ssl.enableDebug","false"); + props.put("aai.ssl.validateServerHostName","false"); + props.put("aai.ssl.validateServerCertificateChain","false"); + + props.put("aai.rest.authenticationMode","SSL_CERT"); + props.put("aai.ssl.basicAuth.username",""); + props.put("aai.ssl.basicAuth.password",""); + + props.put("aai.taskProcessor.maxConcurrentWorkers","5"); + + props.put("aai.taskProcessor.transactionRateControllerEnabled","false"); + props.put("aai.taskProcessor.numSamplesPerThreadForRunningAverage","100"); + props.put("aai.taskProcessor.targetTPS","100"); + + props.put("aai.taskProcessor.bytesHistogramLabel","[Response Size In Bytes]"); + props.put("aai.taskProcessor.bytesHistogramMaxYAxis","1000000"); + props.put("aai.taskProcessor.bytesHistogramNumBins","20"); + props.put("aai.taskProcessor.bytesHistogramNumDecimalPoints","2"); + + props.put("aai.taskProcessor.queueLengthHistogramLabel","[Queue Item Length]"); + props.put("aai.taskProcessor.queueLengthHistogramMaxYAxis","20000"); + props.put("aai.taskProcessor.queueLengthHistogramNumBins","20"); + props.put("aai.taskProcessor.queueLengthHistogramNumDecimalPoints","2"); + + props.put("aai.taskProcessor.taskAgeHistogramLabel","[Task Age In Ms]"); + props.put("aai.taskProcessor.taskAgeHistogramMaxYAxis","600000"); + props.put("aai.taskProcessor.taskAgeHistogramNumBins","20"); + props.put("aai.taskProcessor.taskAgeHistogramNumDecimalPoints","2"); + + props.put("aai.taskProcessor.responseTimeHistogramLabel","[Response Time In Ms]"); + props.put("aai.taskProcessor.responseTimeHistogramMaxYAxis","10000"); + props.put("aai.taskProcessor.responseTimeHistogramNumBins","20"); + props.put("aai.taskProcessor.responseTimeHistogramNumDecimalPoints","2"); + + props.put("aai.taskProcessor.tpsHistogramLabel","[Transactions Per Second]"); + props.put("aai.taskProcessor.tpsHistogramMaxYAxis","100"); + props.put("aai.taskProcessor.tpsHistogramNumBins","20"); + props.put("aai.taskProcessor.tpsHistogramNumDecimalPoints","2"); + + + return props; + + + } + + @Test + public void validateAccessors() throws Exception { + + ActiveInventoryConfig config = new ActiveInventoryConfig(getTestProperties()); + + ActiveInventoryRestConfig airc = config.getAaiRestConfig(); + ActiveInventorySslConfig sslConfig = config.getAaiSslConfig(); + TaskProcessorConfig tpc = config.getTaskProcessorConfig(); + + assertNotNull(airc); + assertNotNull(sslConfig); + assertNotNull(tpc); + + assertEquals("https://aai-host:8443/aai/v10", config.getBaseUri().toString()); + + assertTrue(config.toString().contains("ActiveInventoryConfig")); + + config.setAaiRestConfig(null); + config.setAaiSslConfig(null); + config.setTaskProcessorConfig(null); + + assertNull(config.getAaiRestConfig()); + assertNull(config.getAaiSslConfig()); + assertNull(config.getTaskProcessorConfig()); + + config.setAaiRestConfig(airc); + config.setAaiSslConfig(sslConfig); + config.setTaskProcessorConfig(tpc); + + + } + + @Test + public void validateRepairSelfLink_nullLink() throws Exception { + + ActiveInventoryConfig config = new ActiveInventoryConfig(getTestProperties()); + + ActiveInventoryRestConfig restConfig = config.getAaiRestConfig(); + + restConfig.setAuthenticationMode(RestAuthenticationMode.UNKNOWN_MODE); + restConfig.setHost("aai-host"); + restConfig.setPort("9191"); + + assertNull(config.repairSelfLink(null)); + } + + @Test + public void validateRepairSelfLink_emptyString() throws Exception { + + ActiveInventoryConfig config = new ActiveInventoryConfig(getTestProperties()); + + ActiveInventoryRestConfig restConfig = config.getAaiRestConfig(); + + restConfig.setAuthenticationMode(RestAuthenticationMode.UNKNOWN_MODE); + restConfig.setHost("aai-host"); + restConfig.setPort("9191"); + + assertEquals("http://aai-host:9191", config.repairSelfLink("")); + } + + @Test + public void validateRepairSelfLink_withResourceUrl() throws Exception { + + ActiveInventoryConfig config = new ActiveInventoryConfig(getTestProperties()); + + ActiveInventoryRestConfig restConfig = config.getAaiRestConfig(); + + restConfig.setAuthenticationMode(RestAuthenticationMode.SSL_CERT); + restConfig.setHost("aai-host"); + restConfig.setPort("9191"); + + assertEquals("https://aai-host:9191/aai/v10/business/customers/customer/1234", + config.repairSelfLink("/aai/v10/business/customers/customer/1234")); + } +} \ No newline at end of file diff --git a/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryRestConfigTest.java b/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryRestConfigTest.java new file mode 100644 index 0000000..e1421c4 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventoryRestConfigTest.java @@ -0,0 +1,293 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.dal.aai.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import org.junit.Before; +import org.junit.Test; +import org.openecomp.sparky.dal.aai.enums.RestAuthenticationMode; + + +public class ActiveInventoryRestConfigTest { + + /** + * Test case initialization + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception {} + + private Properties buildExpectedPropertyDefinition() throws Exception { + + Properties props = new Properties(); + + props.put("aai.rest.resourceBasePath", "/aai/v9"); + props.put("aai.rest.host", "1.2.3.4"); + props.put("aai.rest.port", "4321"); + props.put("aai.rest.numRequestRetries", "100"); + props.put("aai.rest.numResolverWorkers", "50"); + props.put("aai.rest.maxConcurrentWorkers", "50"); + props.put("aai.rest.connectTimeoutInMs", "1000"); + props.put("aai.rest.readTimeoutInMs", "1500"); + props.put("aai.rest.shallowEntities", "a,b,c,d"); + props.put("aai.rest.authenticationMode", "HTTP_NOAUTH"); + + props.put("aai.rest.cache.enabled", "true"); + props.put("aai.rest.cache.storageFolderOverride", "folderOverride"); + props.put("aai.rest.cache.cacheFailures", "true"); + props.put("aai.rest.cache.useCacheOnly", "true"); + props.put("aai.rest.cache.numWorkers", "50"); + props.put("aai.rest.cache.maxTimeToLiveInMs", "500"); + + + return props; + } + + /** + * Success path initialization and validation of accessors + * + * @throws Exception + */ + @Test + public void successfulInitialization() throws Exception { + + ActiveInventoryRestConfig config = + new ActiveInventoryRestConfig(buildExpectedPropertyDefinition()); + + /* + * Now verify that all the internal members have been set to default values + */ + + assertEquals(config.getResourceBasePath(), "/aai/v9"); + assertEquals(config.getHost(), "1.2.3.4"); + assertEquals(config.getPort(), "4321"); + assertEquals(config.getNumRequestRetries(), 100); + assertEquals(config.getNumResolverWorkers(), 50); + assertEquals(config.getConnectTimeoutInMs(), 1000); + assertEquals(config.getReadTimeoutInMs(), 1500); + + List expectedEntities = new ArrayList(); + expectedEntities.add("a"); + expectedEntities.add("b"); + expectedEntities.add("c"); + expectedEntities.add("d"); + + assertEquals(config.getShallowEntities().size(), 4); + assertTrue(config.getShallowEntities().containsAll(expectedEntities)); + assertEquals(config.getAuthenticationMode(), RestAuthenticationMode.HTTP_NOAUTH); + + assertTrue(config.isCacheEnabled()); + assertEquals(config.getStorageFolderOverride(), "folderOverride"); + assertTrue(config.shouldCacheFailures()); + assertTrue(config.isUseCacheOnly()); + assertEquals(config.getNumCacheWorkers(), 50); + assertEquals(config.getMaxTimeToLiveInMs(), 500); + + + } + + /** + * Failed path initialization + * + * @throws Exception + */ + @Test + public void validateInitializationWithNullProperties() throws Exception { + + /* + * Setup encryptor expectations + */ + + ActiveInventoryRestConfig config = new ActiveInventoryRestConfig(null); + + /* + * Now verify that all the internal members have been set to default values + */ + + assertNull(config.getResourceBasePath()); + assertNull(config.getHost()); + assertNull(config.getPort()); + assertEquals(config.getNumRequestRetries(), 0); + assertEquals(config.getNumResolverWorkers(), 0); + assertEquals(config.getConnectTimeoutInMs(), 0); + assertEquals(config.getReadTimeoutInMs(), 0); + + assertNull(config.getShallowEntities()); + assertNull(config.getAuthenticationMode()); + + assertFalse(config.isCacheEnabled()); + assertNull(config.getStorageFolderOverride()); + assertFalse(config.shouldCacheFailures()); + assertFalse(config.isUseCacheOnly()); + assertEquals(config.getNumCacheWorkers(), 0); + assertEquals(config.getMaxTimeToLiveInMs(), 0); + + } + + /** + * Failed path initialization + * + * @throws Exception + */ + @Test + public void validateInitializationWithInvalidProperties() throws Exception { + + /* + * Setup encryptor expectations + */ + + ActiveInventoryRestConfig config = new ActiveInventoryRestConfig(new Properties()); + + /* + * Now verify that all the internal members have been set to default values + */ + + assertEquals(config.getResourceBasePath(), "/aai/v7"); + assertEquals(config.getHost(), "localhost"); + assertEquals(config.getPort(), "8443"); + assertEquals(config.getNumRequestRetries(), 5); + assertEquals(config.getNumResolverWorkers(), 15); + assertEquals(config.getConnectTimeoutInMs(), 5000); + assertEquals(config.getReadTimeoutInMs(), 10000); + + assertEquals(config.getShallowEntities().size(), 1); + assertEquals(config.getAuthenticationMode(), RestAuthenticationMode.SSL_CERT); + + assertFalse(config.isCacheEnabled()); + assertNull(config.getStorageFolderOverride()); + assertFalse(config.shouldCacheFailures()); + assertFalse(config.isUseCacheOnly()); + assertEquals(config.getNumCacheWorkers(), 5); + assertEquals(config.getMaxTimeToLiveInMs(), -1); + + } + + /** + * Class accessor validator + * + * @throws Exception + */ + @Test + public void validateClassAccessors() throws Exception { + + /* + * Setup encryptor expectations + */ + + ActiveInventoryRestConfig config = + new ActiveInventoryRestConfig(buildExpectedPropertyDefinition()); + + /* + * Now verify that all the internal members have been set to default values + */ + + config.setAuthenticationMode(RestAuthenticationMode.SSL_BASIC); + config.setCacheEnabled(true); + config.setConnectTimeoutInMs(1000); + config.setHost("myhost"); + config.setMaxTimeToLiveInMs(1234); + config.setNumCacheWorkers(1000); + config.setNumRequestRetries(1500); + config.setNumResolverWorkers(150); + config.setPort("11223344"); + config.setReadTimeoutInMs(54321); + config.setResourceBasePath("/aai/v21"); + config.setStorageFolderOverride("override"); + config.setUseCacheOnly(true); + config.setShouldCacheFailures(true); + + assertEquals(config.getResourceBasePath(), "/aai/v21"); + assertEquals(config.getHost(), "myhost"); + assertEquals(config.getPort(), "11223344"); + assertEquals(config.getNumRequestRetries(), 1500); + assertEquals(config.getNumResolverWorkers(), 150); + assertEquals(config.getConnectTimeoutInMs(), 1000); + assertEquals(config.getReadTimeoutInMs(), 54321); + assertTrue(config.shouldCacheFailures()); + + List expectedEntities = new ArrayList(); + expectedEntities.add("a"); + expectedEntities.add("b"); + expectedEntities.add("c"); + expectedEntities.add("d"); + + assertEquals(config.getShallowEntities().size(), 4); + assertTrue(config.getShallowEntities().containsAll(expectedEntities)); + assertTrue(config.isShallowEntity("b")); + assertFalse(config.isShallowEntity("f")); + assertFalse(config.isShallowEntity(null)); + assertEquals(config.getAuthenticationMode(), RestAuthenticationMode.SSL_BASIC); + + assertTrue(config.isCacheEnabled()); + assertEquals(config.getStorageFolderOverride(), "override"); + assertTrue(config.shouldCacheFailures()); + assertTrue(config.isUseCacheOnly()); + assertEquals(config.getNumCacheWorkers(), 1000); + assertEquals(config.getMaxTimeToLiveInMs(), 1234); + + assertTrue(config.toString().contains("ActiveInventoryRestConfig")); + + } + + + /** + * Validate auth mode edge cases + * + * @throws Exception + */ + @Test + public void validateUnknownAuthModeDefaultsToSslCert() throws Exception { + + /* + * Setup encryptor expectations + */ + + Properties props = buildExpectedPropertyDefinition(); + props.setProperty("aai.rest.authenticationMode", "invalid mode"); + props.setProperty("aai.rest.storageFolderOverride", ""); + + ActiveInventoryRestConfig config = new ActiveInventoryRestConfig(props); + + /* + * Now verify that all the internal members have been set to default values + */ + + assertNotNull(config.getShallowEntities()); + assertEquals(RestAuthenticationMode.SSL_CERT, config.getAuthenticationMode()); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventorySslConfigTest.java b/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventorySslConfigTest.java new file mode 100644 index 0000000..834bbd1 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/aai/config/ActiveInventorySslConfigTest.java @@ -0,0 +1,268 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.dal.aai.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Properties; + +import org.eclipse.jetty.util.security.Password; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.openecomp.sparky.util.Encryptor; + +//import com.att.aai.util.EncryptedConfiguration; + +public class ActiveInventorySslConfigTest { + + private Encryptor encryptorMock = Mockito.mock(Encryptor.class); + + /** + * Test case initialization + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + System.setProperty("javax.net.debug", "invalid"); + } + + private Properties buildExpectedPropertyDefinition() throws Exception { + Encryptor encryptor = new Encryptor(); + Properties props = new Properties(); + + props.put("aai.ssl.enableDebug", "false"); + props.put("aai.ssl.validateServerHostName", "false"); + props.put("aai.ssl.validateServiceCertificateChain", "false"); + props.put("aai.ssl.keystore.type", "pkcs12"); + props.put("aai.ssl.keystore.filename", "/opt/app/applocal/etc/cert.crt"); + /*props.put("aai.ssl.keystore.pass", encryptor.decryptValue(value)EncryptedConfiguration.encryptToTriple("AES", + Long.toString(123456789 % 10000), "aa1admin", "password"));*/ + props.put("aai.ssl.truststore.type", "jks"); + props.put("aai.ssl.truststore.filename", "/opt/app/applocal/etc/cert.crt"); + props.put("aai.ssl.basicAuth.username", "username"); + props.put("aai.ssl.basicAuth.password", Password.obfuscate("password")); + + return props; + } + + private Properties buildInvalidPropertyDefinition() { + Properties props = new Properties(); + + props.put("aai.ssl.enableDebug", "true"); + props.put("aai.ssl.validateServerHostName", "invalid"); + props.put("aai.ssl.validateServiceCertificateChain", "invalid"); + props.put("aai.ssl.keystore.type", "invalid"); + // props.put("aai.ssl.keystore.filename", ); + props.put("aai.ssl.keystore.pass", "invalid"); + props.put("aai.ssl.truststore.type", "invalid"); + // props.put("aai.ssl.truststore.filename", "/opt/app/applocal/etc/cert.crt"); + props.put("aai.ssl.basicAuth.username", "invalid"); + props.put("aai.ssl.basicAuth.password", "invalid"); + + return props; + } + + private String generateAuthorizationHeaderValue(String username, String password) { + String usernameAndPassword = username + ":" + password; + return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes()); + } + + /** + * Success path initialization and validation of accessors + * + * @throws Exception + */ + @Test + public void successfulInitialization() throws Exception { + + /* + * Setup encryptor expectations + */ + Mockito.when(encryptorMock.decryptValue(Mockito.anyString())).thenReturn("password"); + + ActiveInventorySslConfig config = + new ActiveInventorySslConfig(buildExpectedPropertyDefinition(), encryptorMock); + + /* + * Now verify that all the internal members have been set to default values + */ + + assertEquals(System.getProperty("javax.net.debug"), ""); + assertFalse(config.isValidateServerHostName()); + assertFalse(config.isValidateServerCertificateChain()); + + assertEquals(config.getKeystoreType(), "pkcs12"); + assertTrue(config.getKeystoreFilename().contains("/opt/app/applocal/etc/cert.crt")); + assertEquals(config.getKeystorePassword(), "password"); + + assertEquals(config.getTruststoreType(), "jks"); + assertTrue(config.getTruststoreFilename().contains("/opt/app/applocal/etc/cert.crt")); + + assertEquals(config.getBasicAuthUsername(), "username"); + assertEquals(config.getBasicAuthPassword(), "password"); + assertEquals(config.getBasicAuthenticationCredentials(), + generateAuthorizationHeaderValue("username", "password")); + + } + + /** + * Failed path initialization + * + * @throws Exception + */ + @Test + public void validateInitializationWithNullProperties() throws Exception { + + /* + * Setup encryptor expectations + */ + Mockito.when(encryptorMock.decryptValue(Mockito.anyString())).thenReturn(""); + + ActiveInventorySslConfig config = new ActiveInventorySslConfig(null, encryptorMock); + + /* + * Now verify that all the internal members have been set to default values + */ + + assertEquals(System.getProperty("javax.net.debug"), "invalid"); + assertFalse(config.isValidateServerHostName()); + assertFalse(config.isValidateServerCertificateChain()); + + assertNull(config.getKeystoreType()); + assertNull(config.getKeystoreFilename()); + assertNull(config.getKeystorePassword()); + + assertNull(config.getTruststoreType()); + assertNull(config.getTruststoreFilename()); + + assertNull(config.getBasicAuthUsername()); + assertNull(config.getBasicAuthPassword()); + assertEquals(config.getBasicAuthenticationCredentials(), + generateAuthorizationHeaderValue("null", "null")); + + } + + /** + * Failed path initialization + * + * @throws Exception + */ + @Test + public void validateInitializationWithInvalidProperties() throws Exception { + + /* + * Setup encryptor expectations + */ + Mockito.when(encryptorMock.decryptValue(Mockito.anyString())).thenReturn(""); + + ActiveInventorySslConfig config = + new ActiveInventorySslConfig(buildInvalidPropertyDefinition(), encryptorMock); + + /* + * Now verify that all the internal members have been set to default values + */ + + assertEquals(System.getProperty("javax.net.debug"), "ssl"); + assertFalse(config.isValidateServerHostName()); + assertFalse(config.isValidateServerCertificateChain()); + + assertEquals(config.getKeystoreType(),"invalid"); + assertTrue(config.getKeystoreFilename().contains("null")); + assertEquals(config.getKeystorePassword(),""); + + assertEquals(config.getTruststoreType(),"invalid"); + assertTrue(config.getTruststoreFilename().contains("null")); + + assertEquals(config.getBasicAuthUsername(),"invalid"); + assertEquals(config.getBasicAuthPassword(),"invalid"); + assertEquals(config.getBasicAuthenticationCredentials(), + generateAuthorizationHeaderValue("invalid", "invalid")); + + } + + /** + * Class accessor validator + * + * @throws Exception + */ + @Test + public void validateClassAccessors() throws Exception { + + /* + * Setup encryptor expectations + */ + Mockito.when(encryptorMock.decryptValue(Mockito.anyString())).thenReturn("password"); + + ActiveInventorySslConfig config = + new ActiveInventorySslConfig(buildInvalidPropertyDefinition(), encryptorMock); + + /* + * Now verify that all the internal members have been set to default values + */ + + config.setBasicAuthPassword("test"); + config.setBasicAuthUsername("test"); + config.setKeystoreFilename("test"); + config.setKeystorePassword("test"); + config.setKeystoreType("test"); + config.setTruststoreFilename("test"); + config.setTruststoreType("test"); + config.setEncryptor(encryptorMock); + config.setValidateServerCertificateChain(true); + config.setValidateServerHostName(true); + + assertEquals(System.getProperty("javax.net.debug"), "ssl"); + assertTrue(config.isValidateServerHostName()); + assertTrue(config.isValidateServerCertificateChain()); + + assertEquals(config.getKeystoreType(),"test"); + assertTrue(config.getKeystoreFilename().contains("test")); + assertEquals(config.getKeystorePassword(),"test"); + + assertEquals(config.getTruststoreType(),"test"); + assertTrue(config.getTruststoreFilename().contains("test")); + + assertEquals(config.getBasicAuthUsername(),"test"); + assertEquals(config.getBasicAuthPassword(),"test"); + assertEquals(config.getBasicAuthenticationCredentials(), + generateAuthorizationHeaderValue("test", "test")); + + assertNotNull(config.getEncryptor()); + + assertTrue(config.toString().contains("ActiveInventorySslConfig")); + + + } + + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchConfigTest.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchConfigTest.java new file mode 100644 index 0000000..c9d071f --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/ElasticSearchConfigTest.java @@ -0,0 +1,272 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.dal.elasticsearch; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.exception.ElasticSearchOperationException; + + +import ch.qos.logback.classic.Level; + +/** + * The Class ElasticSearchConfigTest. + */ +public class ElasticSearchConfigTest { + + private static final String GOOD_MAPPINGS_FILE = + "{" + "\"properties\": {" + "\"entityType\": {" + "\"type\": \"string\"" + "}," + + "\"edgeTagQueryEntityFieldName\": {" + "\"type\": \"string\"," + "\"index\": \"no\"" + + "}," + "\"edgeTagQueryEntityFieldValue\": {" + "\"type\": \"string\"," + + "\"index\": \"no\"" + "}," + "\"searchTagIDs\" : {" + "\"type\" : \"string\"" + "}," + + "\"searchTags\": {" + "\"type\": \"string\"," + "\"analyzer\": \"nGram_analyzer\"," + + "\"search_analyzer\": \"whitespace_analyzer\"}" + "}" + "}"; + + private static final String GOOD_SETTINGS_FILE = "{\"analysis\": {" + "\"filter\": {" + + "\"nGram_filter\": {" + "\"type\": \"nGram\"," + "\"min_gram\": 1," + "\"max_gram\": 50," + + "\"token_chars\": [" + "\"letter\"," + "\"digit\"," + "\"punctuation\"," + "\"symbol\"" + + "]}}," + "\"analyzer\": {" + "\"nGram_analyzer\": {" + "\"type\": \"custom\"," + + "\"tokenizer\": \"whitespace\"," + "\"filter\": [" + "\"lowercase\"," + "\"asciifolding\"," + + "\"nGram_filter\"]}," + "\"whitespace_analyzer\": {" + "\"type\": \"custom\"," + + "\"tokenizer\": \"whitespace\"," + "\"filter\": [" + "\"lowercase\"," + + "\"asciifolding\"]}}}}"; + + private static final String BAD_SETTINGS_FILE = "{\"analysis\": {" + "\"filter\": {" + + "\"nGram_filter\": {" + "\"type\": \"nGram\"," + "\"min_gram\": 1," + "\"max_gram\": 50," + + "\"token_chars\": [" + "\"letter\"," + "\"digit\"," + "\"punctuation\"," + "\"symbol\"" + + "]}}," + "\"analyzer\": {" + "\"nGram_analyzer\": {" + "\"type\": \"custom\"," + + "\"tokenizer\": \"whitespace\"," + "\"filter\": [" + "\"lowercase\"," + "\"asciifolding\"," + + "\"nGram_filter\"]}," + "\"whitespace_analyzer\": {" + "\"type\": \"custom\"," + + "\"tokenizer\": \"whitespace\"," + "\"filter\": [" + "\"lowercase\"," + + "\"asciifolding\"]}}"; + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + } + + /** + * Failure to initialize properties results in config defaults. + */ + @Test + public void failureToInitializePropertiesResultsInConfigDefaults() { + try { + ElasticSearchConfig config = ElasticSearchConfig.getConfig(); + + /* + * Now verify that all the internal members have been set to default values + */ + + assertEquals(config.getIpAddress(), "localhost"); + assertEquals(config.getHttpPort(), "" + 9200); + assertEquals(config.getJavaApiPort(), "" + 9300); + assertEquals(config.getIndexName(), "entitySearchIndex"); + assertEquals(config.getType(), "aaiEntities"); + assertEquals(config.getClusterName(), "elasticsearch"); + assertEquals(config.getMappingsFileName(), null); + assertEquals(config.getSettingsFileName(), null); + assertEquals(config.getAuditIndexName(), "auditdataindex"); + + } catch (Exception exc) { + assertEquals("null", exc.getLocalizedMessage()); + } + } + + + /** + * Validate accessors. + * + * @throws IOException Signals that an I/O exception has occurred. + * @throws ServletException the servlet exception + * @throws Exception the exception + */ + @Test + public void validateAccessors() throws IOException, ServletException, Exception { + + ElasticSearchConfig esConfig = new ElasticSearchConfig(); + + esConfig.setIpAddress("47.248.10.127"); + esConfig.setHttpPort("8123"); + esConfig.setJavaApiPort("9123"); + esConfig.setIndexName("myIndexName"); + esConfig.setType("myIndexTableType"); + esConfig.setClusterName("ES_AAI_DEV"); + esConfig.setMappingsFileName("d:\\1\\mappings.json"); + esConfig.setSettingsFileName("d:\\1\\settings.json"); + esConfig.setAuditIndexName("auditIndexName"); + + ElasticSearchConfig.setConfig(esConfig); + + assertEquals(esConfig.getIpAddress(), "47.248.10.127"); + assertEquals(esConfig.getHttpPort(), "8123"); + assertEquals(esConfig.getJavaApiPort(), "9123"); + assertEquals(esConfig.getIndexName(), "myIndexName"); + assertEquals(esConfig.getType(), "myIndexTableType"); + assertEquals(esConfig.getClusterName(), "ES_AAI_DEV"); + assertEquals(esConfig.getMappingsFileName(), "d:\\1\\mappings.json"); + assertEquals(esConfig.getSettingsFileName(), "d:\\1\\settings.json"); + assertEquals(esConfig.getAuditIndexName(), "auditIndexName"); + + String output = esConfig.toString(); + + assertNotEquals(output, null); + + } + + /** + * Gets the elastic search settings expect valid config. + * + * @return the elastic search settings expect valid config + * @throws IOException Signals that an I/O exception has occurred. + * @throws ElasticSearchOperationException the elastic search operation exception + * Need to revisit this test case and change the way this class works + */ + @Ignore + public void getElasticSearchSettings_expectValidConfig() + throws IOException, ElasticSearchOperationException { + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + ElasticSearchConfig esConfig = new ElasticSearchConfig(); + + esConfig.setSettingsFileName("src/main/config/es_settings.json"); + + assertNotNull(esConfig.getElasticSearchSettings()); + } + + /** + * Gets the elastic search settings expect file not found exception. + * + * @return the elastic search settings expect file not found exception + * @throws IOException Signals that an I/O exception has occurred. + * @throws ElasticSearchOperationException the elastic search operation exception + * + * Need to revisit this test case and change the way this class works + */ + @Ignore + public void getElasticSearchSettings_expectFileNotFoundException() + throws IOException, ElasticSearchOperationException { + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + ElasticSearchConfig esConfig = new ElasticSearchConfig(); + + esConfig.setSettingsFileName("src/main/config/es_setting.json"); + + esConfig.getElasticSearchSettings(); + + } + + /** + * Gets the elastic search mappings expect valid config. + * + * @return the elastic search mappings expect valid config + * @throws IOException Signals that an I/O exception has occurred. + * @throws ElasticSearchOperationException the elastic search operation exception + * + * Need to revisit this test case and change the way this class works + */ + @Ignore + public void getElasticSearchMappings_expectValidConfig() + throws IOException, ElasticSearchOperationException { + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + ElasticSearchConfig esConfig = new ElasticSearchConfig(); + + esConfig.setMappingsFileName("src/main/config/es_mappings.json"); + + assertNotNull(esConfig.getElasticSearchMappings()); + } + + /** + * Gets the elastic search mappings expect file not found exception. + * + * @return the elastic search mappings expect file not found exception + * @throws IOException Signals that an I/O exception has occurred. + * @throws ElasticSearchOperationException the elastic search operation exception + */ + @Test(expected = ElasticSearchOperationException.class) + public void getElasticSearchMappings_expectFileNotFoundException() + throws IOException, ElasticSearchOperationException { + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + ElasticSearchConfig esConfig = new ElasticSearchConfig(); + + esConfig.setSettingsFileName("src/main/config/es_setting.json"); + + esConfig.getElasticSearchMappings(); + + } + + /** + * Builds the elastic search table config expect valid result. + * + * @throws ElasticSearchOperationException the elastic search operation exception + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test + public void buildElasticSearchTableConfig_expectValidResult() + throws ElasticSearchOperationException, IOException { + ElasticSearchConfig spyEsConfig = Mockito.spy(new ElasticSearchConfig()); + Mockito.doReturn(GOOD_MAPPINGS_FILE).when(spyEsConfig).getElasticSearchMappings(); + Mockito.doReturn(GOOD_SETTINGS_FILE).when(spyEsConfig).getElasticSearchSettings(); + Mockito.doReturn("myIndexTableType").when(spyEsConfig).getType(); + + assertNotNull(spyEsConfig.buildElasticSearchTableConfig()); + } + + /** + * Builds the elastic search table config expect exception. + * + * @throws ElasticSearchOperationException the elastic search operation exception + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test(expected = ElasticSearchOperationException.class) + public void buildElasticSearchTableConfig_expectException() + throws ElasticSearchOperationException, IOException { + ElasticSearchConfig spyEsConfig = Mockito.spy(new ElasticSearchConfig()); + Mockito.doReturn(GOOD_MAPPINGS_FILE).when(spyEsConfig).getElasticSearchMappings(); + Mockito.doReturn(BAD_SETTINGS_FILE).when(spyEsConfig).getElasticSearchSettings(); + Mockito.doReturn("myIndexTableType").when(spyEsConfig).getType(); + + spyEsConfig.buildElasticSearchTableConfig(); + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestDocumentEntity.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestDocumentEntity.java new file mode 100644 index 0000000..b3abd14 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestDocumentEntity.java @@ -0,0 +1,44 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AutoSuggestDocumentEntity { + + @JsonProperty("entity_suggest") + AutoSuggestDocumentEntityFields fields; + + public AutoSuggestDocumentEntityFields getFields() { + return fields; + } + + public void setFields(AutoSuggestDocumentEntityFields fields) { + this.fields = fields; + } + + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestDocumentEntityFields.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestDocumentEntityFields.java new file mode 100644 index 0000000..db0dc8c --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestDocumentEntityFields.java @@ -0,0 +1,81 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class AutoSuggestDocumentEntityFields { + + private String output; + private List input; + private PayloadEntity payload; + private int weight; + + public AutoSuggestDocumentEntityFields() { + input = new ArrayList(); + } + + public String getOutput() { + return output; + } + + public void setOutput(String output) { + this.output = output; + } + + public List getInput() { + return input; + } + + public void setInput(List input) { + this.input = input; + } + + public PayloadEntity getPayload() { + return payload; + } + + public void setPayload(PayloadEntity payload) { + this.payload = payload; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } + + public void addInput(String input) { + this.input.add(input); + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticHitEntity.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticHitEntity.java new file mode 100644 index 0000000..60ac538 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticHitEntity.java @@ -0,0 +1,87 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AutoSuggestElasticHitEntity { + + @JsonProperty("_index") + private String index; + + @JsonProperty("_type") + private String type; + + @JsonProperty("_id") + private String id; + + @JsonProperty("_score") + private String score; + + @JsonProperty("_source") + private AutoSuggestDocumentEntity source; + + public String getIndex() { + return index; + } + + public void setIndex(String index) { + this.index = index; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getScore() { + return score; + } + + public void setScore(String score) { + this.score = score; + } + + public AutoSuggestDocumentEntity getSource() { + return source; + } + + public void setSource(AutoSuggestDocumentEntity source) { + this.source = source; + } + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticHitsEntity.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticHitsEntity.java new file mode 100644 index 0000000..af74485 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticHitsEntity.java @@ -0,0 +1,50 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import java.util.ArrayList; +import java.util.List; + +public class AutoSuggestElasticHitsEntity { + + private List hits; + + public AutoSuggestElasticHitsEntity() { + hits = new ArrayList(); + } + + public List getHits() { + return hits; + } + + public void setHits(List hits) { + this.hits = hits; + } + + public void addHit(AutoSuggestElasticHitEntity hit) { + this.hits.add(hit); + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticSearchResponse.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticSearchResponse.java new file mode 100644 index 0000000..9b6c9f5 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/AutoSuggestElasticSearchResponse.java @@ -0,0 +1,85 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class AutoSuggestElasticSearchResponse { + + private int took; + + @JsonProperty("timed_out") + private boolean timedOut; + + @JsonProperty("_shards") + private Map shards; + + private AutoSuggestElasticHitsEntity hits; + + public AutoSuggestElasticSearchResponse(){ + this.shards = new HashMap(); + } + + public int getTook() { + return took; + } + + public void setTook(int took) { + this.took = took; + } + + public boolean isTimedOut() { + return timedOut; + } + + public void setTimedOut(boolean timedOut) { + this.timedOut = timedOut; + } + + public Map getShards() { + return shards; + } + + public void setShards(Map shards) { + this.shards = shards; + } + + public void addShard(String name, String value) { + shards.put(name, value); + } + + public AutoSuggestElasticHitsEntity getHits() { + return hits; + } + + public void setHits(AutoSuggestElasticHitsEntity hits) { + this.hits = hits; + } + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/BucketEntity.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/BucketEntity.java new file mode 100644 index 0000000..0a1a133 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/BucketEntity.java @@ -0,0 +1,61 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BucketEntity { + private String key; + + @JsonProperty("doc_count") + private int docCount; + + public BucketEntity() { + + } + + public BucketEntity(String name, int value) { + this.key = name; + this.docCount = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public int getDocCount() { + return docCount; + } + + public void setDocCount(int docCount) { + this.docCount = docCount; + } + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticHit.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticHit.java new file mode 100644 index 0000000..32dc17a --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticHit.java @@ -0,0 +1,29 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +public class ElasticHit { + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticHitsEntity.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticHitsEntity.java new file mode 100644 index 0000000..b10532e --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticHitsEntity.java @@ -0,0 +1,74 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ElasticHitsEntity { + + private int total; + @JsonProperty("max_score") + private int maxScore; + + private List hits; + + public ElasticHitsEntity() { + this.hits = new ArrayList(); + } + + public void addHit(ElasticHit hit) { + this.hits.add(hit); + } + + public int getTotal() { + return total; + } + + public void setTotal(int total) { + this.total = total; + } + + public int getMaxScore() { + return maxScore; + } + + public void setMaxScore(int maxScore) { + this.maxScore = maxScore; + } + + public List getHits() { + return hits; + } + + public void setHits(List hits) { + this.hits = hits; + } + + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchAggegrationResponse.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchAggegrationResponse.java new file mode 100644 index 0000000..54c9278 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchAggegrationResponse.java @@ -0,0 +1,109 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ElasticSearchAggegrationResponse { + + private int took; + + @JsonProperty("timed_out") + private boolean timedOut; + + @JsonProperty("_shards") + private Map shards; + + private ElasticHitsEntity hits; + + private Map aggregations; + + public ElasticSearchAggegrationResponse() { + this.shards = new HashMap(); + this.aggregations = new HashMap(); + } + + + public int getTook() { + return took; + } + + + public void setTook(int took) { + this.took = took; + } + + + public boolean isTimedOut() { + return timedOut; + } + + + public void setTimedOut(boolean timedOut) { + this.timedOut = timedOut; + } + + + public Map getShards() { + return shards; + } + + + public void setShards(Map shards) { + this.shards = shards; + } + + + public ElasticHitsEntity getHits() { + return hits; + } + + + public void setHits(ElasticHitsEntity hits) { + this.hits = hits; + } + + public void addShard(String key, String value) { + this.shards.put(key,value); + } + + + public Map getAggregations() { + return aggregations; + } + + + public void setAggregations(Map aggregations) { + this.aggregations = aggregations; + } + + public void addAggregation(String key, ElasticSearchAggregation agg) { + this.aggregations.put(key, agg); + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchAggregation.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchAggregation.java new file mode 100644 index 0000000..ea954d9 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchAggregation.java @@ -0,0 +1,74 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ElasticSearchAggregation { + + @JsonProperty("doc_count_error_upper_bound") + private int docCountErrorUpperBound; + + @JsonProperty("sum_other_doc_count") + private int sumOtherDocCount; + + private List buckets; + + public ElasticSearchAggregation() { + buckets = new ArrayList(); + } + + public int getDocCountErrorUpperBound() { + return docCountErrorUpperBound; + } + + public void setDocCountErrorUpperBound(int docCountErrorUpperBound) { + this.docCountErrorUpperBound = docCountErrorUpperBound; + } + + public int getSumOtherDocCount() { + return sumOtherDocCount; + } + + public void setSumOtherDocCount(int sumOtherDocCount) { + this.sumOtherDocCount = sumOtherDocCount; + } + + public List getBuckets() { + return buckets; + } + + public void setBuckets(List buckets) { + this.buckets = buckets; + } + + public void addBucket(BucketEntity bucket) { + buckets.add(bucket); + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchCountResponse.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchCountResponse.java new file mode 100644 index 0000000..4b11f8c --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/ElasticSearchCountResponse.java @@ -0,0 +1,60 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +import java.util.HashMap; +import java.util.Map; + +public class ElasticSearchCountResponse { + + private int count; + private Map shards; + + public ElasticSearchCountResponse() { + this.shards = new HashMap(); + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public Map getShards() { + return shards; + } + + public void setShards(Map shards) { + this.shards = shards; + } + + public void addShard(String key, String value) { + this.shards.put(key, value); + } + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/PayloadEntity.java b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/PayloadEntity.java new file mode 100644 index 0000000..360abdf --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/elasticsearch/entity/PayloadEntity.java @@ -0,0 +1,32 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.elasticsearch.entity; + +public class PayloadEntity { + + public PayloadEntity() { + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/rest/RestClientBuilderTest.java b/src/test/java/org/openecomp/sparky/dal/rest/RestClientBuilderTest.java new file mode 100644 index 0000000..80ee21f --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/rest/RestClientBuilderTest.java @@ -0,0 +1,180 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.dal.rest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.client.urlconnection.HTTPSProperties; + +import javax.net.ssl.SSLContext; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.openecomp.sparky.security.SecurityContextFactory; +import org.powermock.modules.junit4.PowerMockRunner; + +import ch.qos.logback.classic.Level; + +/** + * The Class RestClientBuilderTest. + */ +@RunWith(PowerMockRunner.class) +public class RestClientBuilderTest { + + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + } + + /** + * Basic construction test. + * + * @throws Exception the exception + */ + @Test + public void basicConstructionTest() throws Exception { + + RestClientBuilder clientBuilder = new RestClientBuilder(); + + // test constructor defaults + + assertFalse(clientBuilder.isValidateServerHostname()); + assertEquals(60000L, clientBuilder.getConnectTimeoutInMs()); + assertEquals(60000L, clientBuilder.getReadTimeoutInMs()); + assertTrue(clientBuilder.isUseHttps()); + + } + + /** + * Validate accessors. + * + * @throws Exception the exception + */ + @Test + public void validateAccessors() throws Exception { + + RestClientBuilder clientBuilder = new RestClientBuilder(); + + clientBuilder.setConnectTimeoutInMs(12345); + clientBuilder.setReadTimeoutInMs(54321); + clientBuilder.setUseHttps(true); + clientBuilder.setValidateServerHostname(true); + + assertEquals(12345, clientBuilder.getConnectTimeoutInMs()); + assertEquals(54321, clientBuilder.getReadTimeoutInMs()); + assertTrue(clientBuilder.isUseHttps()); + assertTrue(clientBuilder.isValidateServerHostname()); + + } + + /** + * Validate simple client construction. + * + * @throws Exception the exception + */ + @Test + public void validateSimpleClientConstruction() throws Exception { + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(false); + Client client = clientBuilder.getClient(); + + /* + * Simple client context should not contain HTTPS properties + */ + assertNull(client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES)); + + } + + /** + * Validate secure client construction without host name validation. + * + * @throws Exception the exception + */ + @Test + public void validateSecureClientConstruction_WithoutHostNameValidation() throws Exception { + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(true); + + SecurityContextFactory sslContextFactory = Mockito.mock(SecurityContextFactory.class); + clientBuilder.setSslContextFactory(sslContextFactory); + + SSLContext sslContext = Mockito.mock(SSLContext.class); + doReturn(sslContext).when(sslContextFactory).getSecureContext(); + + Client client = clientBuilder.getClient(); + + /* + * Secure client context should contain HTTPS properties + */ + assertNotNull(client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES)); + assertNotNull(clientBuilder.getSslContextFactory()); + + } + + /** + * Validate secure client construction with host name validation. + * + * @throws Exception the exception + */ + @Test + public void validateSecureClientConstruction_WithHostNameValidation() throws Exception { + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(true); + clientBuilder.setValidateServerHostname(true); + + SecurityContextFactory sslContextFactory = Mockito.mock(SecurityContextFactory.class); + clientBuilder.setSslContextFactory(sslContextFactory); + + SSLContext sslContext = Mockito.mock(SSLContext.class); + doReturn(sslContext).when(sslContextFactory).getSecureContext(); + + Client client = clientBuilder.getClient(); + + /* + * Secure client context should contain HTTPS properties + */ + assertNotNull(client.getProperties().get(HTTPSProperties.PROPERTY_HTTPS_PROPERTIES)); + assertNotNull(clientBuilder.getSslContextFactory()); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/rest/RestfulDataAccessorTest.java b/src/test/java/org/openecomp/sparky/dal/rest/RestfulDataAccessorTest.java new file mode 100644 index 0000000..b898d90 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/rest/RestfulDataAccessorTest.java @@ -0,0 +1,226 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.dal.rest; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; + +/** + * The Class RestfulDataAccessorTest. + */ +@RunWith(PowerMockRunner.class) +public class RestfulDataAccessorTest { + + private RestClientBuilder clientBuilderMock; + private Client mockClient; + private ClientResponse mockClientResponse; + private WebResource mockWebResource; + private Builder mockBuilder; + + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + + /* + * common collaborator mocking setup + */ + + clientBuilderMock = mock(RestClientBuilder.class); + mockClient = mock(Client.class); + mockClientResponse = mock(ClientResponse.class); + mockWebResource = mock(WebResource.class); + mockBuilder = mock(Builder.class); + + doReturn(mockClient).when(clientBuilderMock).getClient(); + doReturn(mockWebResource).when(mockClient).resource(anyString()); + doReturn(mockBuilder).when(mockWebResource).accept(anyString()); + doReturn(mockBuilder).when(mockBuilder).header(anyString(), anyObject()); + + doReturn(mockClientResponse).when(mockBuilder).get(same(ClientResponse.class)); + doReturn(mockClientResponse).when(mockBuilder).put(same(ClientResponse.class), anyObject()); + doReturn(mockClientResponse).when(mockBuilder).post(same(ClientResponse.class), anyObject()); + doReturn(mockClientResponse).when(mockBuilder).delete(same(ClientResponse.class)); + } + + /** + * Successful do put. + * + * @throws Exception the exception + */ + @Test + public void successfulDoPut() throws Exception { + + /* + * set test mocking expectations + */ + + doReturn(200).when(mockClientResponse).getStatus(); + doReturn("Success").when(mockClientResponse).getEntity(String.class); + + // test code + RestfulDataAccessor dataAccessor = new RestfulDataAccessor(clientBuilderMock); + OperationResult actualResult = dataAccessor.doPut("myUrl", "jsonPayload", "acceptContentType"); + + assertEquals("Unexpected result", 200, actualResult.getResultCode()); + } + + /** + * Successful do get. + * + * @throws Exception the exception + */ + @Test + public void successfulDoGet() throws Exception { + + /* + * set test mocking expectations + */ + + doReturn(200).when(mockClientResponse).getStatus(); + doReturn("Success").when(mockClientResponse).getEntity(String.class); + + // test code + RestfulDataAccessor dataAccessor = new RestfulDataAccessor(clientBuilderMock); + OperationResult actualResult = dataAccessor.doGet("myUrl", "anyContentType"); + + assertEquals("Unexpected result", 200, actualResult.getResultCode()); + + } + + /** + * Successful do post. + * + * @throws Exception the exception + */ + @Test + public void successfulDoPost() throws Exception { + + /* + * set test mocking expectations + */ + + doReturn(200).when(mockClientResponse).getStatus(); + doReturn("Success").when(mockClientResponse).getEntity(String.class); + + // test code + RestfulDataAccessor dataAccessor = new RestfulDataAccessor(clientBuilderMock); + OperationResult actualResult = dataAccessor.doPost("myUrl", "jsonPayload", "anyContentType"); + + assertEquals("Unexpected result", 200, actualResult.getResultCode()); + + } + + /** + * Successful do delete. + * + * @throws Exception the exception + */ + @Test + public void successfulDoDelete() throws Exception { + + /* + * set test mocking expectations + */ + + doReturn(200).when(mockClientResponse).getStatus(); + doReturn("Success").when(mockClientResponse).getEntity(String.class); + + // test code + RestfulDataAccessor dataAccessor = new RestfulDataAccessor(clientBuilderMock); + OperationResult actualResult = dataAccessor.doDelete("myUrl", "anyContentType"); + + assertEquals("Unexpected result", 200, actualResult.getResultCode()); + + } + + /** + * Operation results in null pointer exception. + * + * @throws Exception the exception + */ + @Test + public void operationResultsInNullPointerException() throws Exception { + + /* + * set test mocking expectations + */ + + + doThrow(new NullPointerException("Parameter can't be null")).when(clientBuilderMock) + .getClient(); + + // test code + RestfulDataAccessor dataAccessor = new RestfulDataAccessor(clientBuilderMock); + OperationResult actualResult = dataAccessor.doDelete("myUrl", "anyContentType"); + + assertEquals("Unexpected result", 500, actualResult.getResultCode()); + + } + + /** + * Operation results in null client response. + * + * @throws Exception the exception + */ + @Test + public void operationResultsInNullClientResponse() throws Exception { + + /* + * set test mocking expectations + */ + // return null client response + doReturn(null).when(mockBuilder).delete(same(ClientResponse.class)); + + // test code + RestfulDataAccessor dataAccessor = new RestfulDataAccessor(clientBuilderMock); + OperationResult actualResult = dataAccessor.doDelete("myUrl", "anyContentType"); + + assertEquals("Unexpected result", 500, actualResult.getResultCode()); + + } + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/sas/entity/DocumentEntity.java b/src/test/java/org/openecomp/sparky/dal/sas/entity/DocumentEntity.java new file mode 100644 index 0000000..6285e9c --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/sas/entity/DocumentEntity.java @@ -0,0 +1,68 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.sas.entity; + +import java.util.HashMap; +import java.util.Map; + +public class DocumentEntity { + private String etag; + private String url; + private Map content; + + public DocumentEntity() { + content = new HashMap(); + } + + + public String getEtag() { + return etag; + } + + public void setEtag(String etag) { + this.etag = etag; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Map getContent() { + return content; + } + + public void setContent(Map content) { + this.content = content; + } + + public void addContent(String key, String value) { + content.put(key, value); + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/sas/entity/EntityCountResponse.java b/src/test/java/org/openecomp/sparky/dal/sas/entity/EntityCountResponse.java new file mode 100644 index 0000000..3940b28 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/sas/entity/EntityCountResponse.java @@ -0,0 +1,55 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.sas.entity; + +import java.util.HashMap; +import java.util.Map; + +public class EntityCountResponse { + + private Map shards; + private int count; + + public EntityCountResponse() { + this.shards = new HashMap(); + } + + public Map getShards() { + return shards; + } + + public void setShards(Map shards) { + this.shards = shards; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/sas/entity/GroupByAggregationEntity.java b/src/test/java/org/openecomp/sparky/dal/sas/entity/GroupByAggregationEntity.java new file mode 100644 index 0000000..3ab5e30 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/sas/entity/GroupByAggregationEntity.java @@ -0,0 +1,60 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.sas.entity; + +import java.util.ArrayList; +import java.util.List; + +import org.openecomp.sparky.dal.elasticsearch.entity.BucketEntity; + +public class GroupByAggregationEntity { + private int totalChartHits; + List buckets; + + public GroupByAggregationEntity() { + this.buckets = new ArrayList(); + } + + public int getTotalChartHits() { + return totalChartHits; + } + + public void setTotalChartHits(int totalChartHits) { + this.totalChartHits = totalChartHits; + } + + public List getBuckets() { + return buckets; + } + + public void setBuckets(List buckets) { + this.buckets = buckets; + } + + public void addBucket(BucketEntity bucket) { + this.buckets.add(bucket); + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/sas/entity/GroupByAggregationResponseEntity.java b/src/test/java/org/openecomp/sparky/dal/sas/entity/GroupByAggregationResponseEntity.java new file mode 100644 index 0000000..4ef3be1 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/sas/entity/GroupByAggregationResponseEntity.java @@ -0,0 +1,48 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.sas.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class GroupByAggregationResponseEntity { + + @JsonProperty("groupby_aggregation") + private GroupByAggregationEntity aggEntity; + + public GroupByAggregationResponseEntity() { + + } + + public GroupByAggregationEntity getAggEntity() { + return aggEntity; + } + + public void setAggEntity(GroupByAggregationEntity aggEntity) { + this.aggEntity = aggEntity; + } + + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/sas/entity/HitEntity.java b/src/test/java/org/openecomp/sparky/dal/sas/entity/HitEntity.java new file mode 100644 index 0000000..f5036e7 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/sas/entity/HitEntity.java @@ -0,0 +1,48 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.sas.entity; + +public class HitEntity { + + private String score; + private DocumentEntity document; + + public String getScore() { + return score; + } + + public void setScore(String score) { + this.score = score; + } + + public DocumentEntity getDocument() { + return document; + } + + public void setDocument(DocumentEntity document) { + this.document = document; + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchAbstractionEntityBuilder.java b/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchAbstractionEntityBuilder.java new file mode 100644 index 0000000..fa3d463 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchAbstractionEntityBuilder.java @@ -0,0 +1,295 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.sas.entity; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class SearchAbstractionEntityBuilder { + + + public static HitEntity getHitSample1() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("17.073963"); + + doc.addContent("entityPrimaryKeyValue", "example-vnf-id-val-4394"); + doc.addContent("entityType", "vpe"); + doc.addContent("searchTags", "example-vnf-id-val-4394;example-vnf-name-val-4394;example-vnf-name2-val-4394"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/vpes/vpe/example-vnf-id-val-4394"); + doc.addContent("searchTagIDs", "0;1;2"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:20:48.072-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/e317a35256717f10e88d1b2c995efcdddfc911bf350c73e37e8afca6dfb11553"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample2() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("17.073963"); + + doc.addContent("entityPrimaryKeyValue", "vpe-vnf-id-team4-11"); + doc.addContent("entityType", "vpe"); + doc.addContent("searchTags", "vpe-vnf-id-team4-11;example-vnf-name-val-9512;example-vnf-name2-val-9512"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/vpes/vpe/vpe-vnf-id-team4-11"); + doc.addContent("searchTagIDs", "0;1;2"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:20:48.175-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/80f6d1a252e047e50e0adbeb90ad30876bb5b63cf70c9dd53f3fe46aeb50c74b"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample3() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("17.030035"); + + doc.addContent("entityPrimaryKeyValue", "example-vnf-id-val-6176"); + doc.addContent("entityType", "generic-vnf"); + doc.addContent("searchTags", "example-vnf-id-val-6176;example-vnf-name-val-6176;example-vnf-name2-val-6176"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/generic-vnfs/generic-vnf/example-vnf-id-val-6176"); + doc.addContent("searchTagIDs", "0;1;2"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:29:39.889-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/8dfd1136f943296508fee11efcda35a0719aa490aa60e9abffecce0b220d8c94"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample4() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("17.01174"); + + doc.addContent("entityPrimaryKeyValue", "vnf-id-team4-11"); + doc.addContent("entityType", "newvce"); + doc.addContent("searchTags", "vnf-id-team4-11;example-vnf-name-val-5313;example-vnf-name2-val-5313"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/newvces/newvce/vnf-id-team4-11"); + doc.addContent("searchTagIDs", "0;1;2"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:21:08.142-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/83dcab92d75b20eb94578039c8cec5e7b6b4717791e3c367d8af5069ce76dc90"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample5() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("17.01174"); + + doc.addContent("entityPrimaryKeyValue", "example-vnf-id2-val-9501"); + doc.addContent("entityType", "newvce"); + doc.addContent("searchTags", "example-vnf-id2-val-9501;example-vnf-name-val-9501;example-vnf-name2-val-9501"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/newvces/newvce/example-vnf-id2-val-9501"); + doc.addContent("searchTagIDs", "0;1;2"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:21:23.323-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/461816ba8aa94d01f2c978999b843dbaf10e0509db58d1945d6f5999d6db8f5e"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample6() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("17.01174"); + + doc.addContent("entityPrimaryKeyValue", "vnf-id-dm-auto-10"); + doc.addContent("entityType", "vce"); + doc.addContent("searchTags", "vpe-id-dm-auto-10;vnf-id-dm-auto-10;vnf-name-dm-auto-10;vnf-name2-dm-auto-10"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/vces/vce/vnf-id-dm-auto-10"); + doc.addContent("searchTagIDs", "0;1;2;3"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:24:57.209-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/1ead4512e65ee0eafb24e0156cc1abdf97368f08dfe065f02580aa09661bbcd8"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample7() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("13.940832"); + + doc.addContent("entityPrimaryKeyValue", "e3e59c5b-ad48-44d0-b3e4-80eacdcee4c7"); + doc.addContent("entityType", "generic-vnf"); + doc.addContent("searchTags", "e3e59c5b-ad48-44d0-b3e4-80eacdcee4c7;VNF_Test_vNF_modules_01"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/generic-vnfs/generic-vnf/e3e59c5b-ad48-44d0-b3e4-80eacdcee4c7"); + doc.addContent("searchTagIDs", "0;1"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:26:34.603-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/1462582e8fd7786f72f26548e4247b72ab6cd101cca0bbb68a60dd3ad16500d0"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample8() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("13.940832"); + + doc.addContent("entityPrimaryKeyValue", "fusion-jitsi-vnf-001"); + doc.addContent("entityType", "generic-vnf"); + doc.addContent("searchTags", "fusion-jitsi-vnf-001;fusion-jitsi-vnf"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/generic-vnfs/generic-vnf/fusion-jitsi-vnf-001"); + doc.addContent("searchTagIDs", "0;1"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:28:14.293-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/b79ddfec9a00184445174c91e7490a0d407f351983bba4ae53bfec0584f73ee3"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample9() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("13.940832"); + + doc.addContent("entityPrimaryKeyValue", "vnfm0003v"); + doc.addContent("entityType", "generic-vnf"); + doc.addContent("searchTags", "vnfm0003v;vnfm0003v"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/generic-vnfs/generic-vnf/vnfm0003v"); + doc.addContent("searchTagIDs", "0;1"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:29:39.594-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/52ae232ea5506d6de8ef35c4f46a1ceafe35f3717ff578b83531bc7615870b12"); + doc.setEtag("1"); + + + return hitEntity; + + } + + public static HitEntity getHitSample10() { + + HitEntity hitEntity = new HitEntity(); + DocumentEntity doc = new DocumentEntity(); + + hitEntity.setDocument(doc); + hitEntity.setScore("13.928098"); + + doc.addContent("entityPrimaryKeyValue", "amist456vnf"); + doc.addContent("entityType", "generic-vnf"); + doc.addContent("searchTags", "amist456vnf;amist456vnf"); + doc.addContent("link", "https://aai-ext1.test.att.com:8443/aai/v9/network/generic-vnfs/generic-vnf/amist456vnf"); + doc.addContent("searchTagIDs", "0;1"); + doc.addContent("lastmodTimestamp", "2017-04-18T17:28:28.163-0400"); + + doc.setUrl("services/search-data-service/v1/search/indexes/entitysearchindex-localhost-ist-apr18/documents/3424afea5963696380a0fdc78ee5320cf5fa9bc0459f1f9376db208d31196434"); + doc.setEtag("1"); + + + return hitEntity; + + } + + + + public static SearchAbstractionResponse getSuccessfulEntitySearchResponse() { + + SearchAbstractionResponse sasResponse = new SearchAbstractionResponse(); + + SearchResult searchResult = new SearchResult(); + sasResponse.setSearchResult(searchResult); + + searchResult.setTotalHits(3257); + + List hits = new ArrayList(); + + hits.add(getHitSample1()); + hits.add(getHitSample2()); + hits.add(getHitSample3()); + hits.add(getHitSample4()); + hits.add(getHitSample5()); + hits.add(getHitSample6()); + hits.add(getHitSample7()); + hits.add(getHitSample8()); + hits.add(getHitSample9()); + hits.add(getHitSample10()); + + searchResult.setHits(hits); + + return sasResponse; + + } + + +} diff --git a/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchAbstractionResponse.java b/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchAbstractionResponse.java new file mode 100644 index 0000000..0e6398f --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchAbstractionResponse.java @@ -0,0 +1,39 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.sas.entity; + +public class SearchAbstractionResponse { + + private SearchResult searchResult; + + public SearchResult getSearchResult() { + return searchResult; + } + + public void setSearchResult(SearchResult searchResult) { + this.searchResult = searchResult; + } + +} diff --git a/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchResult.java b/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchResult.java new file mode 100644 index 0000000..992d5b5 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/dal/sas/entity/SearchResult.java @@ -0,0 +1,49 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ +package org.openecomp.sparky.dal.sas.entity; + +import java.util.List; + +public class SearchResult { + + private int totalHits; + private List hits; + + public int getTotalHits() { + return totalHits; + } + public void setTotalHits(int totalHits) { + this.totalHits = totalHits; + } + public List getHits() { + return hits; + } + public void setHits(List hits) { + this.hits = hits; + } + + + +} diff --git a/src/test/java/org/openecomp/sparky/inventory/GeoIndexDocumentTest.java b/src/test/java/org/openecomp/sparky/inventory/GeoIndexDocumentTest.java new file mode 100644 index 0000000..9274c30 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/inventory/GeoIndexDocumentTest.java @@ -0,0 +1,121 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.inventory; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openecomp.sparky.inventory.entity.GeoIndexDocument; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * The Class GeoIndexDocumentTest. + */ +@RunWith(PowerMockRunner.class) +public class GeoIndexDocumentTest { + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception {} + + /** + * Checks if is valid geo index document success path. + */ + @Test + public void isValidGeoIndexDocument_successPath() { + + GeoIndexDocument geoDoc = new GeoIndexDocument(); + + geoDoc.setEntityPrimaryKeyName("pkeyName"); + geoDoc.setEntityPrimaryKeyValue("pkeyValue"); + geoDoc.setEntityType("type"); + geoDoc.setId("12312"); + geoDoc.setLatitude("-45.123"); + geoDoc.setLongitude("181.321"); + geoDoc.setSelfLink("https://server.somewhere.com:8443/aai/v7/id"); + + assertTrue(geoDoc.isValidGeoDocument()); + + } + + /** + * Checks if is valid geo index document fail no geo coordinates. + */ + @Test + public void isValidGeoIndexDocument_fail_no_geoCoordinates() { + + GeoIndexDocument geoIndexDoc = new GeoIndexDocument(); + + geoIndexDoc.setEntityPrimaryKeyName("pkeyName"); + geoIndexDoc.setEntityPrimaryKeyValue("pkeyValue"); + geoIndexDoc.setEntityType("type"); + geoIndexDoc.setId("12312"); + geoIndexDoc.setSelfLink("https://server.somewhere.com:8443/aai/v7/id"); + + assertFalse(geoIndexDoc.isValidGeoDocument()); + + } + + /** + * Checks if is valid geo index document fail invalid geo coordinates. + */ + @Test + public void isValidGeoIndexDocument_fail_invalid_geoCoordinates() { + + GeoIndexDocument geoIndexDoc = new GeoIndexDocument(); + + geoIndexDoc.setEntityPrimaryKeyName("pkeyName"); + geoIndexDoc.setEntityPrimaryKeyValue("pkeyValue"); + geoIndexDoc.setEntityType("type"); + geoIndexDoc.setId("12312"); + geoIndexDoc.setLatitude("not_a_valid"); + geoIndexDoc.setLongitude("geo point"); + + geoIndexDoc.setSelfLink("https://server.somewhere.com:8443/aai/v7/id"); + + assertFalse(geoIndexDoc.isValidGeoDocument()); + + } + + /** + * Checks if is valid geo index document fail nothing set. + */ + @Test + public void isValidGeoIndexDocument_fail_nothing_set() { + + GeoIndexDocument geoIndexDoc = new GeoIndexDocument(); + + assertFalse(geoIndexDoc.isValidGeoDocument()); + + } +} diff --git a/src/test/java/org/openecomp/sparky/security/SecurityContextFactoryImplTest.java b/src/test/java/org/openecomp/sparky/security/SecurityContextFactoryImplTest.java new file mode 100644 index 0000000..c387e49 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/security/SecurityContextFactoryImplTest.java @@ -0,0 +1,141 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.security; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.FileInputStream; + +import javax.net.ssl.SSLContext; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.openecomp.sparky.util.LogValidator; + +import ch.qos.logback.classic.Level; + +/** + * The Class SecurityContextFactoryImplTest. + */ +public class SecurityContextFactoryImplTest { + + private LogValidator logValidator; + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + logValidator = new LogValidator(); + logValidator.initializeLogger(Level.WARN); + } + + /** + * Basic construction test. + * + * @throws Exception the exception + */ + @Test + public void basicConstructionTest() throws Exception { + + SecurityContextFactory sslContextFactory = new SecurityContextFactoryImpl(); + + assertEquals("TLS", sslContextFactory.getSslAlgorithm()); + assertEquals("SunX509", sslContextFactory.getKeyManagerAlgortihm()); + assertEquals("PKCS12", sslContextFactory.getKeyStoreType()); + assertEquals(false, sslContextFactory.isServerCertificationChainValidationEnabled()); + assertEquals(null, sslContextFactory.getClientCertFileInputStream()); + } + + /** + * Validate secure context. + * + * @throws Exception the exception + */ + @Test + public void validateSecureContext() throws Exception { + + SecurityContextFactory sslContextFactory = new SecurityContextFactoryImpl(); + + SSLContext sslContext = sslContextFactory.getSecureContext(); + + assertNotNull(sslContext); + } + + /** + * Validate secure context with server cert chain validation. + * + * @throws Exception the exception + */ + @Test + public void validateSecureContext_withServerCertChainValidation() throws Exception { + + SecurityContextFactory sslContextFactory = new SecurityContextFactoryImpl(); + sslContextFactory.setServerCertificationChainValidationEnabled(true); + sslContextFactory.setTrustStoreFileName("filename"); + + sslContextFactory.setClientCertFileName(null); + + SSLContext sslContext = sslContextFactory.getSecureContext(); + + assertNotNull(sslContext); + } + + /** + * Validate accessors. + * + * @throws Exception the exception + */ + @Test + public void validateAccessors() throws Exception { + + SecurityContextFactory sslContextFactory = new SecurityContextFactoryImpl(); + + FileInputStream mockInputStream = Mockito.mock(FileInputStream.class); + + sslContextFactory.setSslAlgorithm("sslAlgorithm"); + sslContextFactory.setKeyManagerAlgortihm("keyManagerAlgorithm"); + sslContextFactory.setKeyStoreType("keyStoreType"); + sslContextFactory.setClientCertFileInputStream(mockInputStream); + sslContextFactory.setServerCertificationChainValidationEnabled(true); + sslContextFactory.setTrustStoreFileName("truststoreFileName"); + sslContextFactory.setClientCertPassword("password"); + + assertEquals("sslAlgorithm", sslContextFactory.getSslAlgorithm()); + assertEquals("keyManagerAlgorithm", sslContextFactory.getKeyManagerAlgortihm()); + assertEquals("keyStoreType", sslContextFactory.getKeyStoreType()); + assertEquals(mockInputStream, sslContextFactory.getClientCertFileInputStream()); + assertEquals(true, sslContextFactory.isServerCertificationChainValidationEnabled()); + assertEquals("truststoreFileName", sslContextFactory.getTrustStoreFileName()); + assertEquals("password", sslContextFactory.getClientCertPassword()); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/security/portal/TestPortalRestAPIServiceImpl.java b/src/test/java/org/openecomp/sparky/security/portal/TestPortalRestAPIServiceImpl.java new file mode 100644 index 0000000..a39c19d --- /dev/null +++ b/src/test/java/org/openecomp/sparky/security/portal/TestPortalRestAPIServiceImpl.java @@ -0,0 +1,269 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.security.portal; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.openecomp.portalsdk.core.onboarding.exception.PortalAPIException; +import org.openecomp.portalsdk.core.restful.domain.EcompRole; +import org.openecomp.portalsdk.core.restful.domain.EcompUser; +import org.openecomp.sparky.security.portal.config.PortalAuthenticationConfig; +import org.openecomp.sparky.security.portal.config.RolesConfig; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +@PowerMockIgnore({ "javax.crypto.*" }) +@RunWith(PowerMockRunner.class) +@PrepareForTest({ PortalAuthenticationConfig.class, RolesConfig.class }) +public class TestPortalRestAPIServiceImpl { + + private static File testUsersFile; + private static final String LOGINID_1 = "200"; + private static final String LOGINID_2 = "201"; + private static final String VIEW_ROLE = "View"; + + enum TestData { + // @formatter:off + TEST_USERS ("src/test/resources/portal/test-users.config"), + PORTAL_AUTHENTICATION_PROPERTIES ("src/test/resources/portal/portal-authentication.properties"), + ROLES_CONFIG_FILE ("src/test/resources/portal/roles.config"); + + private String filename; + TestData(String filename) {this.filename = filename;} + public String getFilename() {return this.filename;} + // @formatter:on + } + + @Mock + private UserManager userManager = new UserManager(testUsersFile); + + @InjectMocks + private PortalRestAPIServiceImpl portalApi = new PortalRestAPIServiceImpl(); + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + testUsersFile = Paths.get(TestData.TEST_USERS.getFilename()).toFile(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + Files.deleteIfExists(testUsersFile.toPath()); + } + + @Before + public void setUp() throws Exception { + Whitebox.setInternalState(RolesConfig.class, "ROLES_CONFIG_FILE", + TestData.ROLES_CONFIG_FILE.getFilename()); + } + + @After + public void tearDown() throws Exception { + Files.deleteIfExists(testUsersFile.toPath()); + } + + @Test + public void testPushAndGetUser() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + + portalApi.pushUser(user); + EcompUser storedUser = portalApi.getUser(user.getLoginId()); + + assertThat(storedUser.getLoginId(), is(user.getLoginId())); + } + + @Test(expected = PortalAPIException.class) + public void testCannotPushUserTwice() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + + portalApi.pushUser(user); + portalApi.pushUser(user); + } + + @Test(expected = PortalAPIException.class) + public void testGetUnknownUser() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + portalApi.pushUser(user); + + portalApi.getUser("does-not-exist"); + } + + @Test + public void testGetUsers() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + + EcompUser user2 = new EcompUser(); + user2.setLoginId(LOGINID_2); + + portalApi.pushUser(user); + portalApi.pushUser(user2); + + List users = portalApi.getUsers(); + + assertThat(users.size(), is(2)); + assertThat(users.get(0).getLoginId(), is(LOGINID_1)); + assertThat(users.get(1).getLoginId(), is(LOGINID_2)); + } + + @Test + public void testEditUser() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + user.setFirstName("Bob"); + + portalApi.pushUser(user); + + user.setFirstName("Jen"); + portalApi.editUser(LOGINID_1, user); + + assertThat(portalApi.getUser(LOGINID_1).getFirstName(), is("Jen")); + } + + @Test(expected = PortalAPIException.class) + public void testEditUnknowUser() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + portalApi.pushUser(user); + + portalApi.editUser("does-no-exist", new EcompUser()); + } + + @Test + public void testGetRoles() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + user.setRoles(new HashSet<>(portalApi.getAvailableRoles())); + + portalApi.pushUser(user); + + List userRoles = portalApi.getUserRoles(LOGINID_1); + + assertThat(userRoles.size(), is(1)); + assertThat(userRoles.get(0).getId(), is(1L)); + assertThat(userRoles.get(0).getName(), is(VIEW_ROLE)); + } + + @Test + public void testPushUserRoles() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + portalApi.pushUser(user); + + EcompUser storedUser = portalApi.getUser(LOGINID_1); + assertThat(storedUser.getRoles(), nullValue()); + + portalApi.pushUserRole(LOGINID_1, UserManager.getRoles()); + + Set storedUserRoles = portalApi.getUser(LOGINID_1).getRoles(); + ArrayList rolesList = new ArrayList<>(storedUserRoles); + + assertThat(rolesList.size(), is(1)); + assertThat(rolesList.get(0).getId(), is(1L)); + assertThat(rolesList.get(0).getName(), is(VIEW_ROLE)); + } + + @Test + public void testCannotPushRoleTwice() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + portalApi.pushUser(user); + + EcompUser storedUser = portalApi.getUser(LOGINID_1); + assertThat(storedUser.getRoles(), nullValue()); + + portalApi.pushUserRole(LOGINID_1, UserManager.getRoles()); + portalApi.pushUserRole(LOGINID_1, UserManager.getRoles()); + + Set storedUserRoles = portalApi.getUser(LOGINID_1).getRoles(); + ArrayList rolesList = new ArrayList<>(storedUserRoles); + + assertThat(rolesList.size(), is(1)); + assertThat(rolesList.get(0).getId(), is(1L)); + assertThat(rolesList.get(0).getName(), is(VIEW_ROLE)); + } + + @Test + public void testDeleteUserRoles() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + user.setFirstName("Bob"); + List availableRoles = portalApi.getAvailableRoles(); + user.setRoles(new LinkedHashSet(availableRoles)); + + portalApi.pushUser(user); + + portalApi.pushUserRole(LOGINID_1, new ArrayList()); + + EcompUser userWithNoRoles = portalApi.getUser(LOGINID_1); + + assertThat(userWithNoRoles.getRoles(), empty()); + } + + @Test + public void testPushNullRoles() throws Exception { + EcompUser user = new EcompUser(); + user.setLoginId(LOGINID_1); + user.setFirstName("Bob"); + List availableRoles = portalApi.getAvailableRoles(); + user.setRoles(new LinkedHashSet(availableRoles)); + + portalApi.pushUser(user); + portalApi.pushUserRole(LOGINID_1, null); + + EcompUser userWithNoRoles = portalApi.getUser(LOGINID_1); + + assertThat(userWithNoRoles.getRoles(), empty()); + } +} \ No newline at end of file diff --git a/src/test/java/org/openecomp/sparky/security/portal/TestUserManager.java b/src/test/java/org/openecomp/sparky/security/portal/TestUserManager.java new file mode 100644 index 0000000..1a8a9e9 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/security/portal/TestUserManager.java @@ -0,0 +1,205 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.security.portal; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openecomp.portalsdk.core.restful.domain.EcompUser; +import org.openecomp.sparky.util.NodeUtils; +import org.powermock.modules.junit4.PowerMockRunner; + +import com.google.gson.Gson; + +@RunWith(PowerMockRunner.class) +//@PrepareForTest(RolesConfig.class) +public class TestUserManager { + + private static final String LOGINID_3 = "3"; + private static File noFile; + private static File concurrentUsers; + private static File concurrentEditUsers; + + private static final Gson GSON = new Gson(); + private static final String LOGINID_1 = "1"; + private static final String LOGINID_2 = "2"; + + enum TestData { + // @formatter:off + NO_FILE ("src/test/resources/portal/no-users.config"), + CONCURRENT_USERS ("src/test/resources/portal/concurrent-users.config"), + CONCURRENT_EDIT_USERS ("src/test/resources/portal/concurrent-edit-users.config"); +// ROLES_CONFIG_FILE ("src/test/resources/portal/roles.config"); + + private String filename; + TestData(String filename) {this.filename = filename;} + public String getFilename() {return this.filename;} + // @formatter:on + } + + @BeforeClass + public static void setUpBeforeClass() throws Exception { + noFile = Paths.get(TestData.NO_FILE.getFilename()).toFile(); + concurrentUsers = Paths.get(TestData.CONCURRENT_USERS.getFilename()).toFile(); + concurrentEditUsers = Paths.get(TestData.CONCURRENT_EDIT_USERS.getFilename()).toFile(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + Files.deleteIfExists(concurrentUsers.toPath()); + Files.deleteIfExists(concurrentEditUsers.toPath()); + } + + @Before + public void setUp() throws Exception { + EcompUser user1 = new EcompUser(); + user1.setLoginId(LOGINID_1); + + EcompUser user2 = new EcompUser(); + user2.setLoginId(LOGINID_2); + + List users = Arrays.asList(user1, user2); + Files.write(concurrentEditUsers.toPath(), GSON.toJson(users).getBytes()); + +// Whitebox.setInternalState(RolesConfig.class, "ROLES_CONFIG_FILE", +// TestData.ROLES_CONFIG_FILE.getFilename()); + } + + @After + public void tearDown() throws Exception { + Files.deleteIfExists(concurrentUsers.toPath()); + Files.deleteIfExists(concurrentEditUsers.toPath()); + } + + @Test + public void testGetUsersNoFile() throws Exception { + UserManager userManager = new UserManager(noFile); + List users = userManager.getUsers(); + + assertThat(users, empty()); + } + + @Test + public void testConcurrentPush() throws Exception { + Callable pushTask = () -> { + return pushTask(concurrentUsers, String.valueOf(NodeUtils.getRandomTxnId())); + }; + + List> callables = Arrays.asList(pushTask, pushTask, pushTask, pushTask, + pushTask); + + ExecutorService executor = Executors.newWorkStealingPool(); + executor.invokeAll(callables).stream().map(future -> { + try { + return future.get(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + + UserManager userManager = new UserManager(concurrentUsers); + assertThat(userManager.getUsers().size(), is(5)); + } + + @Test + public void testConcurrentPushAndEdit() throws Exception { + Callable pushTaskRandomId = () -> { + return pushTask(concurrentEditUsers, String.valueOf(NodeUtils.getRandomTxnId())); + }; + + Callable pushTaskId3 = () -> { + return pushTask(concurrentEditUsers, LOGINID_3); + }; + + Callable editTaskId1 = () -> { + return editTask(LOGINID_1, "Bob"); + }; + + Callable editTaskId2 = () -> { + return editTask(LOGINID_2, "Jen"); + }; + + Callable editTaskId3 = () -> { + return editTask(LOGINID_3, "Amy"); + }; + + List> callables = Arrays.asList(pushTaskRandomId, pushTaskRandomId, + pushTaskId3, editTaskId1, pushTaskRandomId, pushTaskRandomId, editTaskId3, editTaskId2, + pushTaskRandomId); + + ExecutorService executor = Executors.newWorkStealingPool(); + List userTasks = executor.invokeAll(callables).stream().map(future -> { + try { + return future.get(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }).collect(Collectors.toList()); + + assertThat(userTasks.size(), is(9)); + + UserManager userManager = new UserManager(concurrentEditUsers); + assertThat(userManager.getUsers().size(), is(8)); + assertThat(userManager.getUser(LOGINID_1).get().getFirstName(), is("Bob")); + assertThat(userManager.getUser(LOGINID_2).get().getFirstName(), is("Jen")); + assertThat(userManager.getUser(LOGINID_3).get().getFirstName(), is("Amy")); + } + + private EcompUser pushTask(File fileStore, String loginId) throws IOException { + UserManager userManager = new UserManager(fileStore); + EcompUser user = new EcompUser(); + user.setLoginId(loginId); + userManager.pushUser(user); + return user; + } + + private EcompUser editTask(String loginId, String firstName) throws IOException { + UserManager userManager = new UserManager(concurrentEditUsers); + EcompUser user = new EcompUser(); + user.setLoginId(loginId); + user.setFirstName(firstName); + userManager.editUser(loginId, user); + return user; + } +} \ No newline at end of file diff --git a/src/test/java/org/openecomp/sparky/synchronizer/AsyncRateControlTester.java b/src/test/java/org/openecomp/sparky/synchronizer/AsyncRateControlTester.java new file mode 100644 index 0000000..e52995d --- /dev/null +++ b/src/test/java/org/openecomp/sparky/synchronizer/AsyncRateControlTester.java @@ -0,0 +1,245 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.synchronizer; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.openecomp.sparky.synchronizer.config.TaskProcessorConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The Class AsyncRateControlTester. + */ +public class AsyncRateControlTester { + + private static Logger logger = LoggerFactory.getLogger(AsyncRateControlTester.class); + + private long startTimeInMs; + + private AtomicInteger counter; + + protected boolean syncInProgress; + + /** + * Instantiates a new async rate control tester. + * + * @throws Exception the exception + */ + public AsyncRateControlTester() throws Exception { + + TaskProcessorConfig tpc = new TaskProcessorConfig(); + + tpc.setMaxConcurrentWorkers(1); + tpc.setTransactionRateControllerEnabled(false); + tpc.setNumSamplesPerThreadForRunningAverage(100); + tpc.setTargetTps(0.25); + + tpc.setBytesHistogramLabel("bytesHistoLabel"); + tpc.setBytesHistogramMaxYAxis(1000000); + tpc.setBytesHistogramNumBins(20); + tpc.setBytesHistogramNumDecimalPoints(2); + + tpc.setQueueLengthHistogramLabel("queueHistoLabel"); + tpc.setQueueLengthHistogramMaxYAxis(1000000); + tpc.setQueueLengthHistogramNumBins(20); + tpc.setQueueLengthHistogramNumDecimalPoints(2); + + // ZeroDelayProcessor zdp = new ZeroDelayProcessor(LinkProcessorType.AAI, tpc); + // zdp.setStatCollector(this.aaiStatCollector); + /* + * zdp.setTaskProcessorConfig(tpc); + * + * this.resolver.registerProcessor(zdp); this.resolver.registerEventListener(this); this.counter + * = new AtomicInteger(0); this.syncInProgress = false; } + * + * @Override public void handleEvent(AsyncEvent event) { + * + * if(event.getEventType() == AsyncEventType.RESOLVER_IDLE) { + * + * if(syncInProgress) { long duration = System.currentTimeMillis() - startTimeInMs; + * System.out.println(getStatReport(duration)); syncInProgress = false; } + * + * // shutdown(); } else if(event.getEventType() == AsyncEventType.TRANSACTION_PROCESSED) { + * + * this.syncInProgress = true; + * + * ExternalResource resource = (ExternalResource)event.getPayload(); + * + * //aaiStatCollector.updateCounters(resource); + * + * counter.incrementAndGet(); + * + * } + * + * }; + * + * public void shutdown() { resolver.shutdown(); } + * + * private int getCounterValue(AtomicInteger counter) { + * + * if(counter == null) { return 0; } + * + * return counter.get(); } + * + * private void addActiveInventoryStatReport(StringBuilder sb) { + * + * if(sb == null) { return; } + * + * sb.append("\n\n ").append(LinkProcessorType.AAI.name()); + * + * sb.append("\n\n ").append("REST Operational Stats:"); + * + * /* Map procOperationalCounters = + * aaiStatCollector.getActiveInventoryOperationalCounters(); + * + * if(procOperationalCounters != null) { + * + * int _1XX = + * getCounterValue(procOperationalCounters.get(ActiveInventoryStatCollector.GET_1XX)); int _2XX + * = getCounterValue(procOperationalCounters.get(ActiveInventoryStatCollector.GET_2XX)); int + * _3XX = getCounterValue(procOperationalCounters.get(ActiveInventoryStatCollector.GET_3XX)); + * int _4XX = + * getCounterValue(procOperationalCounters.get(ActiveInventoryStatCollector.GET_4XX)); int _5XX + * = getCounterValue(procOperationalCounters.get(ActiveInventoryStatCollector.GET_5XX)); int + * _6XX = getCounterValue(procOperationalCounters.get(ActiveInventoryStatCollector.GET_6XX)); + * + * sb.append("\n ").append(String.format( + * "%-12s 1XX: %-12d 2XX: %-12d 3XX: %-12d 4XX: %-12d 5XX: %-12d 6XX: %-12d ", HttpMethod.GET, + * _1XX, _2XX, _3XX, _4XX, _5XX, _6XX)); } + */ + + // sb.append("\n\n ").append("Entity Stats:"); + + /* + * sort entities, then sort nested op codes + */ + + /* + * TreeMap> activeInventoryEntitySortedTreeMap = new + * TreeMap>( new Comparator() { + * + * public int compare(String o1, String o2) { return + * o1.toLowerCase().compareTo(o2.toLowerCase()); } }); + */ + + /* + * activeInventoryEntitySortedTreeMap.putAll(aaiStatCollector.getActiveInventoryEntityCounters() + * ); + * + * for(String counterEntityKey : activeInventoryEntitySortedTreeMap.keySet()) { + * + * HashMap entityCounters = + * activeInventoryEntitySortedTreeMap.get(counterEntityKey); + * + * AtomicInteger total = entityCounters.get(ActiveInventoryStatCollector.TOTAL); AtomicInteger + * found = entityCounters.get(ActiveInventoryStatCollector.FOUND); AtomicInteger notFound = + * entityCounters.get(ActiveInventoryStatCollector.NOT_FOUND); AtomicInteger error = + * entityCounters.get(ActiveInventoryStatCollector.ERROR); + * + * int totalValue = (total == null) ? 0 : total.get(); int foundValue = (found == null) ? 0 : + * found.get(); int notFoundValue = (found == null) ? 0 : notFound.get(); int errorValue = + * (error == null) ? 0 : error.get(); + * + * sb.append("\n ").append(String.format( + * "%-30s TOTAL: %-12d FOUND: %-12d NOT_FOUND: %-12d ERROR: %-12d", counterEntityKey, + * totalValue, foundValue, notFoundValue, errorValue)); + * + * } + */ + + // sb.append("\n\n ").append("Task Processor Stats:"); + + // int totalRetries = + // getCounterValue(procOperationalCounters.get(ActiveInventoryStatCollector.NUM_RETRIES)); + // int currentQueueLength = resolver.getCurrentQueueLength(LinkProcessorType.AAI.name()); + + /* + * sb.append("\n " + * ).append(resolver.getProcessorTaskAgeStats(LinkProcessorType.AAI.name(), false, " " + * )); sb.append("\n " + * ).append(resolver.getProcessorResponseStats(LinkProcessorType.AAI.name(), false, " " + * )); sb.append("\n") + * .append(resolver.getQueueItemLengthHistogram(LinkProcessorType.AAI.name(), false, + * " ")); sb.append("\n") + * .append(resolver.getResponseByteSizeHistogram(LinkProcessorType.AAI.name(), false, + * " ")); sb.append("\n " + * ).append("TPS=").append(resolver.getTPS(LinkProcessorType.AAI.name())).append(", NumRetries=" + * ).append(totalRetries) .append(", CurrentQueueLength=").append(currentQueueLength); + */ + /* + * } + * + * private String getStatReport(long syncOpTimeInMs) { + * + * StringBuilder sb = new StringBuilder(128); + * + * sb.append("\n").append("Async Resolver Statistics: ( Sync Operation Duration = " + + * NodeUtils.getDurationBreakdown(syncOpTimeInMs) + " )"); + * + * addActiveInventoryStatReport(sb); + * + * return sb.toString(); + * + * } + * + * public void loadResolver(int numItems) { + * + * if(numItems <= 0) { return; } + * + * startTimeInMs = System.currentTimeMillis(); + * + * DummyPerformanceTask dpt = null; + * + * for(int i = 0; i < numItems; i++) { + * + * dpt = new DummyPerformanceTask(); dpt.setLinkProcessorType(LinkProcessorType.AAI); + * dpt.setResourceEntityType("DummyPerformanceEntity"); dpt.setOperationType(HttpMethod.GET); + * + * resolver.resolve(dpt); + * + * } + * + * } + * + * public static void main(String[] args) throws Exception { + * + * System.getProperties().setProperty("AJSC_HOME", "x:\\aaiui\\"); + * + * System.out.println("Available processors = " + Runtime.getRuntime().availableProcessors()); + * + * AsyncRateControlTester arcTester = new AsyncRateControlTester(); + * + * // give us time to instrument the jvm with jvisualvm // Thread.sleep(30000); + * Thread.sleep(5000); + * + * arcTester.loadResolver(1000); + * + * + * } + */ + } +} diff --git a/src/test/java/org/openecomp/sparky/synchronizer/IndexDocumentTest.java b/src/test/java/org/openecomp/sparky/synchronizer/IndexDocumentTest.java new file mode 100644 index 0000000..0f03f81 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/synchronizer/IndexDocumentTest.java @@ -0,0 +1,107 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.synchronizer; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.util.LogValidator; +import org.powermock.modules.junit4.PowerMockRunner; + +import ch.qos.logback.classic.Level; + + +/** + * The Class IndexDocumentTest. + */ +@RunWith(PowerMockRunner.class) +public class IndexDocumentTest { + + private LogValidator logValidator; + private OxmModelLoader oxmModelLoader; + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + logValidator = new LogValidator(); + logValidator.initializeLogger(Level.WARN); + oxmModelLoader = Mockito.mock(OxmModelLoader.class); + } + + /** + * Validate basic construction. + * + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test + public void validateBasicConstruction() throws NoSuchAlgorithmException, IOException { + + /* + * String testDate = "2016-12-21 00:00:00.00"; OxmEntityDescriptor d = new + * OxmEntityDescriptor(); d.setEntityName("service-instance"); + * d.setPrimaryKeyAttributeName(Arrays.asList("service-instance-id")); + * d.setSearchableAttributes(Arrays.asList("service-instance-id")); + * + * Mockito.when(oxmModelLoader.getEntityDescriptor(anyString())).thenReturn(d); + * + * SearchableEntity id1 = new SearchableEntity(oxmModelLoader); + * + * id1.setEntityType("service-instance"); id1.setEntityPrimaryKeyValue("DUP2"); + * id1.addSearchTagWithIdx("DUP2", String.valueOf(1)); + * + * id1.deriveFields(); id1.setEntityTimeStamp(testDate); ObjectMapper mapper = new + * ObjectMapper(); + * + * String objStr = id1.getIndexDocumentJson(); + * + * JsonNode indexDocNode = mapper.readTree(objStr); + * + * /// + * + * ObjectNode expectedNode = mapper.createObjectNode(); expectedNode.put("entityType", + * "service-instance"); expectedNode.put("entityPrimaryKeyValue", "DUP2"); + * expectedNode.put("searchTagIDs", "1"); expectedNode.put("searchTags", "DUP2"); + * expectedNode.put("crossEntityReferenceValues", ""); expectedNode.put("lastmodTimestamp", + * testDate); + * + * assertTrue(NodeUtils.isEqual(expectedNode, indexDocNode)); // Test if the timestamp is + * calculated when the node is being created + * assertTrue(NodeUtils.getNodeFieldAsText(indexDocNode, "lastmodTimestamp") != null); + */ + + } + +} diff --git a/src/test/java/org/openecomp/sparky/synchronizer/SyncControllerBuilder.java b/src/test/java/org/openecomp/sparky/synchronizer/SyncControllerBuilder.java new file mode 100644 index 0000000..45c272b --- /dev/null +++ b/src/test/java/org/openecomp/sparky/synchronizer/SyncControllerBuilder.java @@ -0,0 +1,581 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.synchronizer; + +import org.openecomp.sparky.dal.aai.ActiveInventoryAdapter; +import org.openecomp.sparky.dal.aai.config.ActiveInventoryConfig; +import org.openecomp.sparky.dal.cache.InMemoryEntityCache; +import org.openecomp.sparky.dal.cache.PersistentEntityCache; +import org.openecomp.sparky.dal.elasticsearch.ElasticSearchAdapter; +import org.openecomp.sparky.dal.elasticsearch.ElasticSearchDataProvider; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestClientBuilder; +import org.openecomp.sparky.dal.rest.RestfulDataAccessor; +import org.openecomp.sparky.synchronizer.SyncController.SyncActions; +import org.openecomp.sparky.synchronizer.enumeration.SynchronizerState; +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; + +/** + * The Class SyncControllerBuilder. + */ +public class SyncControllerBuilder { + + /** + * Do master entity sync. + */ + public void doMasterEntitySync() { + + } + + /** + * Test elastic search update api. + */ + public void testElasticSearchUpdateApi() { + try { + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(false); + + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(clientBuilder); + + ElasticSearchConfig esConfig = ElasticSearchConfig.getConfig(); + ElasticSearchDataProvider elasticSearchDataProvider = + new ElasticSearchAdapter(nonCachingRestProvider, esConfig); + + String payload = + "{ \"entityType\": \"complex\", \"pkey\": \"MORRISTOWN0075\", \"location\": { \"lat\": \"40.793414\", \"lon\": \"-74.480432\" }, \"selfLink\": \"https://aai-int1.test.att.com:8443/aai/v8/cloud-infrastructure/complexes/complex/MORRISTOWN0075?nodes-only\" }\n"; + + String updateRequest = elasticSearchDataProvider.buildBulkImportOperationRequest( + "topographysearchindex-localhost", "default", + "1e2a6ba9e09d5e1bcb016b3a0b8d50273b42828e47957bd2a2f3ce1854744f5f", "6", payload); + + OperationResult or = + elasticSearchDataProvider.doBulkOperation("http://localhost:9200/_bulk", updateRequest); + + System.out.println(or.toString()); + + /* + * String BULK_IMPORT_INDEX_TEMPLATE = + * "{\"index\":{\"_index\":\"%s\",\"_type\":\"%s\",\"_id\":\"%s\", \"_version\":\"%s\"}}\n"; + * + * StringBuilder updateRequestPayload = new StringBuilder(128); + * updateRequestPayload.append(String.format(BULK_IMPORT_INDEX_TEMPLATE, + * "topographysearchindex-localhost", "default", + * "1e2a6ba9e09d5e1bcb016b3a0b8d50273b42828e47957bd2a2f3ce1854744f5f", "5")); + * + * + * updateRequestPayload.append(payload); + * + * OperationResult or = nonCachingRestProvider.doRestfulOperation(HttpMethod.PUT, + * "http://localhost:9200/_bulk", updateRequestPayload.toString(), + * RestfulDataAccessor.APPLICATION_X_WWW_FORM_URL_ENCODED, + * RestfulDataAccessor.APPLICATION_JSON); + */ + + + + } catch (Exception exc) { + exc.printStackTrace(); + System.out.println("Error: failed to sync with message = " + exc.getMessage()); + } + } + + /** + * Do historical entity sync. + */ + public void doHistoricalEntitySync() { + try { + SyncController syncController = new SyncController("historicalEntityTestController"); + + ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + aaiAdapter.setCacheEnabled(false); + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(false); + + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(clientBuilder); + + ElasticSearchConfig esConfig = ElasticSearchConfig.getConfig(); + + ElasticSearchAdapter esAdapter = new ElasticSearchAdapter(nonCachingRestProvider,esConfig); + + + IndexIntegrityValidator entityCounterHistoryValidator = + new IndexIntegrityValidator(nonCachingRestProvider, esConfig.getEntityCountHistoryIndex(), + esConfig.getType(), esConfig.getIpAddress(), esConfig.getHttpPort(), + esConfig.buildElasticSearchEntityCountHistoryTableConfig()); + + syncController.registerIndexValidator(entityCounterHistoryValidator); + + + ////// + + + + HistoricalEntitySummarizer historicalSummarizer = + new HistoricalEntitySummarizer(esConfig.getEntityCountHistoryIndex()); + historicalSummarizer.setAaiDataProvider(aaiAdapter); + historicalSummarizer.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(historicalSummarizer); + + //// + + /* + * IndexIntegrityValidator entitySearchIndexValidator = new IndexIntegrityValidator(new + * RestClientBuilder()); + * + * entitySearchIndexValidator.setIndexName("topographysearchindex-localhost"); + * entitySearchIndexValidator.setIndexType("default"); + * entitySearchIndexValidator.setIndexSettings(""); + * entitySearchIndexValidator.setIndexSettings(""); + * + * syncController.registerIndexValidator(entitySearchIndexValidator); + */ + + //// + + /* + * IndexCleaner index1Cleaner = new ElasticSearchIndexCleaner(nonCachingRestProvider, + * "topographysearchindex-localhost", "default", "127.0.0.1", "9200", 5, 5000); + */ + + // syncController.registerIndexCleaner(index1Cleaner); + + /// + + for (int x = 0; x < 10; x++) { + + syncController.performAction(SyncActions.SYNCHRONIZE); + + while (syncController.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + + System.out.println("sync controller state = " + syncController.getState()); + + Thread.sleep(1000); + } + } + + syncController.shutdown(); + + } catch (Exception exc) { + exc.printStackTrace(); + System.out.println("Error: failed to sync with message = " + exc.getMessage()); + } + } + + + + /** + * Do geo entity sync. + */ + public void doGeoEntitySync() { + try { + + ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + + aaiAdapter.setCacheEnabled(true); + + InMemoryEntityCache aaiInMemoryCache = new InMemoryEntityCache(); + aaiAdapter.setEntityCache(aaiInMemoryCache); + + /* + * PersistentEntityCache aaiDiskCache = new PersistentEntityCache(); + * aaiAdapter.setEntityCache(aaiDiskCache); + */ + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(false); + + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(clientBuilder); + ElasticSearchConfig esConfig = ElasticSearchConfig.getConfig(); + + ElasticSearchAdapter esAdapter = new ElasticSearchAdapter(nonCachingRestProvider,esConfig); + + IndexIntegrityValidator entitySearchIndexValidator = + new IndexIntegrityValidator(nonCachingRestProvider, esConfig.getIndexName(), + esConfig.getType(), esConfig.getIpAddress(), esConfig.getHttpPort(), + esConfig.buildElasticSearchTableConfig()); + + SyncController syncController = new SyncController("geoEntitySyncTestController"); + syncController.registerIndexValidator(entitySearchIndexValidator); + + + ////// + + GeoSynchronizer geoSync = new GeoSynchronizer("topographysearchindex-localhost"); + geoSync.setAaiDataProvider(aaiAdapter); + geoSync.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(geoSync); + + //// + + /* + * IndexIntegrityValidator entitySearchIndexValidator = new IndexIntegrityValidator(new + * RestClientBuilder()); + * + * entitySearchIndexValidator.setIndexName("topographysearchindex-localhost"); + * entitySearchIndexValidator.setIndexType("default"); + * entitySearchIndexValidator.setIndexSettings(""); + * entitySearchIndexValidator.setIndexSettings(""); + * + * syncController.registerIndexValidator(entitySearchIndexValidator); + */ + + //// + + /* + * IndexCleaner index1Cleaner = new ElasticSearchIndexCleaner(nonCachingRestProvider, + * "topographysearchindex-localhost", "default", "127.0.0.1", "9200", 5, 5000); + */ + + // syncController.registerIndexCleaner(index1Cleaner); + + /// + + syncController.performAction(SyncActions.SYNCHRONIZE); + + while (syncController.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + Thread.sleep(1000); + } + + syncController.shutdown(); + + } catch (Exception exc) { + exc.printStackTrace(); + System.out.println("Error: failed to sync with message = " + exc.getMessage()); + } + } + + /** + * Do searchable entitysync. + */ + public void doSearchableEntitysync() { + try { + + + ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + + aaiAdapter.setCacheEnabled(true); + + /* + * InMemoryEntityCache aaiInMemoryCache = new InMemoryEntityCache(); + * aaiAdapter.setEntityCache(aaiInMemoryCache); + */ + + PersistentEntityCache aaiDiskCache = new PersistentEntityCache(); + aaiAdapter.setEntityCache(aaiDiskCache); + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(false); + + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(clientBuilder); + ElasticSearchConfig esConfig = ElasticSearchConfig.getConfig(); + + ElasticSearchAdapter esAdapter = new ElasticSearchAdapter(nonCachingRestProvider,esConfig); + + ////// + + SyncController syncController = new SyncController("searchtableEntityTestController"); + + SearchableEntitySynchronizer ses = + new SearchableEntitySynchronizer("entitysearchindex-localhost"); + ses.setAaiDataProvider(aaiAdapter); + ses.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(ses); + + //// + + /* + * IndexIntegrityValidator entitySearchIndexValidator = new IndexIntegrityValidator(new + * RestClientBuilder()); + * + * entitySearchIndexValidator.setIndexName("esi-sync2-localhost"); + * entitySearchIndexValidator.setIndexType("default"); + * + * syncController.registerIndexValidator(entitySearchIndexValidator); + */ + + //// + + /* + * IndexCleaner index1Cleaner = new ElasticSearchIndexCleaner(nonCachingRestProvider, + * "entitysearchindex-localhost", "default", "127.0.0.1", "9200", 5, 5000); + * + * syncController.registerIndexCleaner(index1Cleaner); + */ + + /// + + syncController.performAction(SyncActions.SYNCHRONIZE); + + while (syncController.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + Thread.sleep(1000); + } + + syncController.shutdown(); + + } catch (Exception exc) { + exc.printStackTrace(); + System.out.println("Error: failed to sync with message = " + exc.getMessage()); + } + } + + /** + * Do cross entity reference sync. + */ + public void doCrossEntityReferenceSync() { + try { + + + ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + + aaiAdapter.setCacheEnabled(true); + + /* + * InMemoryEntityCache aaiInMemoryCache = new InMemoryEntityCache(); + * aaiAdapter.setEntityCache(aaiInMemoryCache); + */ + + PersistentEntityCache aaiDiskCache = new PersistentEntityCache(); + aaiAdapter.setEntityCache(aaiDiskCache); + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(false); + + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(clientBuilder); + ElasticSearchConfig esConfig = ElasticSearchConfig.getConfig(); + + ElasticSearchAdapter esAdapter = new ElasticSearchAdapter(nonCachingRestProvider,esConfig); + + SyncController syncController = new SyncController("crossEntityRefSyncController"); + + CrossEntityReferenceSynchronizer cers = + new CrossEntityReferenceSynchronizer("entitysearchindex-localhost", ActiveInventoryConfig.getConfig()); + cers.setAaiDataProvider(aaiAdapter); + cers.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(cers); + + SearchableEntitySynchronizer ses = + new SearchableEntitySynchronizer("entitysearchindex-localhost"); + ses.setAaiDataProvider(aaiAdapter); + ses.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(ses); + + ElasticSearchConfig config = ElasticSearchConfig.getConfig(); + + IndexIntegrityValidator entitySearchIndexValidator = new IndexIntegrityValidator( + nonCachingRestProvider, config.getIndexName(), config.getType(), config.getIpAddress(), + config.getHttpPort(), config.buildElasticSearchTableConfig()); + + syncController.registerIndexValidator(entitySearchIndexValidator); + + //// + + IndexCleaner index1Cleaner = + new ElasticSearchIndexCleaner(nonCachingRestProvider, config.getIndexName(), + config.getType(), config.getIpAddress(), config.getHttpPort(), 5, 5000); + + syncController.registerIndexCleaner(index1Cleaner); + + /// + + syncController.performAction(SyncActions.SYNCHRONIZE); + + while (syncController.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + Thread.sleep(1000); + } + + syncController.shutdown(); + + } catch (Exception exc) { + exc.printStackTrace(); + System.out.println("Error: Failed to sync with message = " + exc.getMessage()); + } + } + + /** + * Do suggestion entitysync. + */ + public void doSuggestionEntitySync() { + try { + + + ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + + aaiAdapter.setCacheEnabled(true); + + /* + * InMemoryEntityCache aaiInMemoryCache = new InMemoryEntityCache(); + * aaiAdapter.setEntityCache(aaiInMemoryCache); + */ + + PersistentEntityCache aaiDiskCache = new PersistentEntityCache(); + aaiAdapter.setEntityCache(aaiDiskCache); + + RestClientBuilder clientBuilder = new RestClientBuilder(); + clientBuilder.setUseHttps(false); + + RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(clientBuilder); + ElasticSearchConfig esConfig = ElasticSearchConfig.getConfig(); + + ElasticSearchAdapter esAdapter = new ElasticSearchAdapter(nonCachingRestProvider,esConfig); + + SyncController syncController = new SyncController("suggestionEntityTestController"); + + AutosuggestionSynchronizer ses = + new AutosuggestionSynchronizer("suggestionentityindex-localhost"); + ses.setAaiDataProvider(aaiAdapter); + ses.setEsDataProvider(esAdapter); + syncController.registerEntitySynchronizer(ses); + + syncController.performAction(SyncActions.SYNCHRONIZE); + + while (syncController.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + Thread.sleep(1000); + } + + syncController.shutdown(); + + } catch (Exception exc) { + exc.printStackTrace(); + System.out.println("Error: failed to sync with message = " + exc.getMessage()); + } + } + + /* + * Do no op sync. + */ + public void doNoOpSync() { + try { + SyncController syncController = new SyncController("noopSyncTestController"); + + /* + * ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); + * + * aaiAdapter.setCacheEnabled(true); + * + * /*InMemoryEntityCache aaiInMemoryCache = new InMemoryEntityCache(); + * aaiAdapter.setEntityCache(aaiInMemoryCache); + */ + + /* + * PersistentEntityCache aaiDiskCache = new PersistentEntityCache(); + * aaiAdapter.setEntityCache(aaiDiskCache); + * + * ElasticSearchConfig config = ElasticSearchConfig.getConfig(); OXMModelLoader loader = + * OXMModelLoader.getInstance(); SyncAdapter syncAdapter = new SyncAdapter(new + * RestClientBuilder(), config, loader); + * + * ////// + * + * SearchableEntitySynchronizer ses = new SearchableEntitySynchronizer(); + * ses.setAaiDataProvider(aaiAdapter); ses.setEsDataProvider(syncAdapter); + * syncController.registerEntitySynchronizer(ses); + * + * //// + * + * IndexIntegrityValidator entitySearchIndexValidator = new IndexIntegrityValidator(new + * RestClientBuilder()); + * + * entitySearchIndexValidator.setIndexName("esi-sync2-localhost"); + * entitySearchIndexValidator.setIndexType("default"); + * entitySearchIndexValidator.setIndexSettings(""); + * entitySearchIndexValidator.setIndexSettings(""); + * + * syncController.registerIndexValidator(entitySearchIndexValidator); + * + * //// + * + * ElasticSearchEntityPurger p1 = new ElasticSearchEntityPurger(new RestClientBuilder()); + * p1.setIndexName("esi-blal-blah"); + * + * ElasticSearchEntityPurger p2 = new ElasticSearchEntityPurger(new RestClientBuilder()); + * p2.setIndexName("esi-topo-blah"); + */ + /// + + syncController.performAction(SyncActions.SYNCHRONIZE); + + while (syncController.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + Thread.sleep(1000); + } + + syncController.shutdown(); + + } catch (Exception exc) { + System.out.println("Error: failed to sync with message = " + exc.getMessage()); + } + } + + + /** + * The main method. + * + * @param args the arguments + */ + public static void main(String[] args) { + boolean runSearchableEntitySync = false; + boolean runGeoEntitySync = true; + + System.setProperty("AJSC_HOME", "e:\\dev"); + // System.getProperties().setProperty("AJSC_HOME", + // "c:\\rpo\\tier-support-ui\\target\\swm\\package\\nix\\" + // + "dist_files\\opt\\app\\ajsc-tier-support-ui"); + System.setProperty("AJSC_HOME", "d:\\AAI\\tier_support_ui\\tier-support-ui\\target\\swm\\package\\nix\\dist_files\\appl\\inventory-ui-service\\1.0-SNAPSHOT"); + + ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory + .getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); + // root.detachAndStopAllAppenders(); + // logger = new CaptureLoggerAppender(); + root.setLevel(Level.INFO); + // root.addAppender(logger); + + + SyncControllerBuilder syncBuilder = new SyncControllerBuilder(); + + /* + * if (runSearchableEntitySync) syncBuilder.doSearchableEntitysync(); + */ + + //syncBuilder.doSearchableEntitysync(); + // syncBuilder.doCrossEntityReferenceSync(); + // syncBuilder.doHistoricalEntitySync(); + // syncBuilder.doGeoEntitySync(); + syncBuilder.doSuggestionEntitySync(); + + // syncBuilder.testElasticSearchUpdateAPI(); + + /* + * if (runGeoEntitySync) { syncBuilder.doGeoEntitySync(); } + */ + + + + } +} diff --git a/src/test/java/org/openecomp/sparky/util/CaptureLoggerAppender.java b/src/test/java/org/openecomp/sparky/util/CaptureLoggerAppender.java new file mode 100644 index 0000000..ec23544 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/CaptureLoggerAppender.java @@ -0,0 +1,247 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedDeque; + +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.LogbackException; +import ch.qos.logback.core.filter.Filter; +import ch.qos.logback.core.spi.FilterReply; +import ch.qos.logback.core.status.Status; + +/** + * A test class used to provide a concrete log stub of the Log4j API interface. The goal is to + * transparently capture logging paths so we can add log validation during the junit validation + * without post-analyzing on-disk logs. + * + * @author DAVEA + * + */ +@SuppressWarnings("rawtypes") +public class CaptureLoggerAppender implements Appender { + + private Deque capturedLogs; + + /** + * Instantiates a new capture logger appender. + */ + public CaptureLoggerAppender() { + capturedLogs = new ConcurrentLinkedDeque(); + } + + /** + * Drain all logs. + * + * @return the list + */ + public List drainAllLogs() { + List loggingEvents = new ArrayList(); + + LoggingEvent event = null; + + while (capturedLogs.peek() != null) { + event = capturedLogs.pop(); + loggingEvents.add(event); + } + + return loggingEvents; + } + + /** + * Clears the capture logs double-ended queue and returns the size of the queue before it was + * cleared. + * + * @return int numCapturedLogs + */ + public int clearAllLogs() { + int numCapturedLogs = capturedLogs.size(); + capturedLogs.clear(); + return numCapturedLogs; + } + + + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.LifeCycle#start() + */ + @Override + public void start() {} + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.LifeCycle#stop() + */ + @Override + public void stop() {} + + @Override + public boolean isStarted() { + // TODO Auto-generated method stub + System.out.println("isStarted"); + return false; + } + + @Override + public void setContext(Context context) { + // TODO Auto-generated method stub + System.out.println("setContext"); + + } + + @Override + public Context getContext() { + // TODO Auto-generated method stub + System.out.println("getContext"); + return null; + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.ContextAware#addStatus(ch.qos.logback.core.status.Status) + */ + @Override + public void addStatus(Status status) { + // TODO Auto-generated method stub + System.out.println("addStatus"); + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.ContextAware#addInfo(java.lang.String) + */ + @Override + public void addInfo(String msg) { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.ContextAware#addInfo(java.lang.String, java.lang.Throwable) + */ + @Override + public void addInfo(String msg, Throwable ex) { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.ContextAware#addWarn(java.lang.String) + */ + @Override + public void addWarn(String msg) { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.ContextAware#addWarn(java.lang.String, java.lang.Throwable) + */ + @Override + public void addWarn(String msg, Throwable ex) { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.ContextAware#addError(java.lang.String) + */ + @Override + public void addError(String msg) { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.ContextAware#addError(java.lang.String, java.lang.Throwable) + */ + @Override + public void addError(String msg, Throwable ex) { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.FilterAttachable#addFilter(ch.qos.logback.core.filter.Filter) + */ + @Override + public void addFilter(Filter newFilter) { + // TODO Auto-generated method stub + + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.FilterAttachable#clearAllFilters() + */ + @Override + public void clearAllFilters() { + // TODO Auto-generated method stub + + } + + @Override + public List getCopyOfAttachedFiltersList() { + // TODO Auto-generated method stub + return null; + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.spi.FilterAttachable#getFilterChainDecision(java.lang.Object) + */ + @Override + public FilterReply getFilterChainDecision(Object event) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getName() { + // TODO Auto-generated method stub + System.out.println("getName"); + return "MOCK"; + } + + /* (non-Javadoc) + * @see ch.qos.logback.core.Appender#doAppend(java.lang.Object) + */ + @Override + public void doAppend(Object event) throws LogbackException { + // TODO Auto-generated method stub + // System.out.println("doAppend(), event = " + event); + // System.out.println("event class = " + event.getClass().getSimpleName()); + capturedLogs.add((LoggingEvent) event); + } + + @Override + public void setName(String name) { + // TODO Auto-generated method stub + System.out.println("setName() name = " + name); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/util/ElasticEntitySummarizer.java b/src/test/java/org/openecomp/sparky/util/ElasticEntitySummarizer.java new file mode 100644 index 0000000..9709bb8 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/ElasticEntitySummarizer.java @@ -0,0 +1,173 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.exception.ElasticSearchOperationException; +import org.openecomp.sparky.dal.rest.RestDataProvider; +import org.openecomp.sparky.synchronizer.config.TaskProcessorConfig; + +/** + * The Class ElasticEntitySummarizer. + */ +public class ElasticEntitySummarizer { + + private RestDataProvider syncAdapter; + private ElasticSearchConfig elasticConfig; + private Map entityCounters; + + /** + * Instantiates a new elastic entity summarizer. + * + * @param loader the loader + * @throws Exception the exception + */ + public ElasticEntitySummarizer(OxmModelLoader loader) throws Exception { + + + elasticConfig = new ElasticSearchConfig(); + TaskProcessorConfig tpc = new TaskProcessorConfig(); + elasticConfig.setProcessorConfig(tpc); + + elasticConfig.setIndexName("entitysearchindex-localhost"); + elasticConfig.setIpAddress("127.0.0.1"); + elasticConfig.setHttpPort("9200"); + elasticConfig.setType("default"); + + // syncAdapter = new SyncAdapter(new RestClientBuilder(), elasticConfig, loader); + + entityCounters = new HashMap(); + + } + + /** + * Peg counter. + * + * @param entityName the entity name + */ + private synchronized void pegCounter(String entityName) { + + if (entityName == null || entityName.length() == 0) { + return; + } + + AtomicInteger counter = entityCounters.get(entityName); + + if (counter == null) { + counter = new AtomicInteger(0); + entityCounters.put(entityName, counter); + } + + counter.incrementAndGet(); + + } + + + /** + * Enumerate entities. + */ + public void enumerateEntities() { + + try { + + Map preSyncObjectIdsAndTypes = new HashMap(); + + /* + * Map preSyncObjectIdsAndTypes = + * syncAdapter.retrieveAllDocumentIdentifiers(elasticConfig.getIndexName(), + * elasticConfig.getType(), 5, 5000); + */ + + if (preSyncObjectIdsAndTypes != null) { + + Collection entityTypes = preSyncObjectIdsAndTypes.values(); + for (String t : entityTypes) { + pegCounter(t); + } + } + + TreeMap elasticEntitySortedTreeMap = + new TreeMap(new Comparator() { + + @Override + public int compare(String o1, String o2) { + return o1.toLowerCase().compareTo(o2.toLowerCase()); + } + }); + + elasticEntitySortedTreeMap.putAll(entityCounters); + + int totalEntities = 0; + + System.out.println("\n"); + + for (String counterEntityKey : elasticEntitySortedTreeMap.keySet()) { + + AtomicInteger counter = elasticEntitySortedTreeMap.get(counterEntityKey); + totalEntities += counter.get(); + System.out.println(String.format("%-30s %-12d", counterEntityKey, counter.get())); + } + + System.out.println(String.format("\n%-30s %-12d", "Total", totalEntities)); + + } catch (Exception exc) { + System.out.println( + "An error occurred while attempting to collect pre-sync elastic" + + " search document ids with an error cause = " + + exc.getLocalizedMessage()); + } + + + } + + + /** + * The main method. + * + * @param args the arguments + * @throws ElasticSearchOperationException the elastic search operation exception + */ + public static void main(String[] args) throws ElasticSearchOperationException { + + + // ElasticEntitySummarizer summarizer = new ElasticEntitySummarizer(); + // summarizer.enumerateEntities(); + + + + } + + + +} diff --git a/src/test/java/org/openecomp/sparky/util/ElasticGarbageInjector.java b/src/test/java/org/openecomp/sparky/util/ElasticGarbageInjector.java new file mode 100644 index 0000000..dc47713 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/ElasticGarbageInjector.java @@ -0,0 +1,170 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.openecomp.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.openecomp.sparky.dal.rest.RestDataProvider; +import org.openecomp.sparky.synchronizer.config.TaskProcessorConfig; + +/** + * The Class ElasticGarbageInjector. + */ +public class ElasticGarbageInjector { + + + private AtomicInteger counter; + private long startTimeInMs; + private int progressStep; + + /** + * The Enum ActiveInventoryEntities. + */ + private enum ActiveInventoryEntities { + + COMPLEX("complex"), CUSTOMER("customer"), GENERIC_VNF("generic-vnf"), NEWVCE("newvce"), PSERVER( + "pserver"), SERVICE_INSTANCE("service-instance"), VCE("vce"), VPE("vpe"), VSERVER( + "vserver"); + + private final String entityName; + + /** + * Instantiates a new active inventory entities. + * + * @param name the name + */ + private ActiveInventoryEntities(String name) { + this.entityName = name; + } + + public String getEntityName() { + return entityName; + } + + } + + /** + * Instantiates a new elastic garbage injector. + * + * @throws Exception the exception + */ + public ElasticGarbageInjector() throws Exception { + + this.counter = new AtomicInteger(0); + + ElasticSearchConfig elasticConfig = new ElasticSearchConfig(); + + TaskProcessorConfig tpc = new TaskProcessorConfig(); + + tpc.setMaxConcurrentWorkers(5); + tpc.setTransactionRateControllerEnabled(false); + tpc.setNumSamplesPerThreadForRunningAverage(100); + tpc.setTargetTps(100.0); + + tpc.setBytesHistogramLabel("bytesHistoLabel"); + tpc.setBytesHistogramMaxYAxis(1000000); + tpc.setBytesHistogramNumBins(20); + tpc.setBytesHistogramNumDecimalPoints(2); + + tpc.setQueueLengthHistogramLabel("queueHistoLabel"); + tpc.setQueueLengthHistogramMaxYAxis(1000000); + tpc.setQueueLengthHistogramNumBins(20); + tpc.setQueueLengthHistogramNumDecimalPoints(2); + + RestDataProvider syncAdapter = null; + // syncAdapter.setTaskProcessorConfig(tpc); + + } + + // @Override + /* + * public void handleEvent(AsyncEvent event) { + * + * if(event.getEventType() == AsyncEventType.RESOLVER_IDLE) { System.out.println("All Done!"); + * resolver.shutdown(); } + * + * + * + * if(event.getEventType() == AsyncEventType.TRANSACTION_PROCESSED) { + * + * + * if ( event.getPayload() instanceof SyncTask) { + * + * counter.incrementAndGet(); + * + * SyncTask ers = (SyncTask)event.getPayload(); + * + * OperationResult or = ers.getResult(); + * + * if ( or.wasSuccessful() ) { //System.out.println("Garbaged injected successfully"); }else { + * System.out.println(ers.getResult().toString()); } + * + * if ( counter.get() % progressStep == 0) { + * + * long duration = System.currentTimeMillis() - startTimeInMs; double tps = ( duration / + * counter.get() ); System.out.println("Currently inserting doc at index = " + counter.get() + + * ", current TPS = " + tps ); } + * + * } + * + * } } + * + * public void injectGarbage(int numGarbageDocs, String baseUrl) { + * + * IndexDocument d = null; SyncTask syncTask = null; Random r = new Random(); + * + * startTimeInMs = System.currentTimeMillis(); this.progressStep = (numGarbageDocs/5); if ( + * this.progressStep == 0 ) { this.progressStep = 1; } int numEntities = + * ActiveInventoryEntities.values().length; + * + * for(int i = 0; i < numGarbageDocs; i++) { d = new IndexDocument(OXMModelLoader.getInstance()); + * d.setId(UUID.randomUUID().toString()); + * d.setEntityType(ActiveInventoryEntities.values()[r.nextInt(numEntities)].getEntityName()); + * + * String link = baseUrl + d.getId(); syncTask = new SyncTask(d, link); + * syncTask.setResourceEntityType(d.getEntityType()); + * syncTask.setPayload(d.getIndexDocumentJson()); + * + * resolver.resolve(syncTask); } + * + * } + * + * public static void main(String[] args) throws Exception { + * + * //System.getProperties().setProperty("AJSC_HOME", "X:\\aaiui\\"); + * + * ElasticGarbageInjector sync = new ElasticGarbageInjector(); + * + * //int numEntries = Integer.parseInt(args[0]); //String baseUrl = args[1]; + * + * //sync.injectGarbage(numEntries,baseUrl); + * sync.injectGarbage(10000,"http://localhost:9200/entitysearchindex-localhost/default/"); + * + * } + */ + +} diff --git a/src/test/java/org/openecomp/sparky/util/ExceptionHelper.java b/src/test/java/org/openecomp/sparky/util/ExceptionHelper.java new file mode 100644 index 0000000..6f647c7 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/ExceptionHelper.java @@ -0,0 +1,62 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +/** + * The Class ExceptionHelper. + */ +public class ExceptionHelper { + + /** + * Extract stack trace elements. + * + * @param maxNumberOfElementsToCapture the max number of elements to capture + * @param exc the exc + * @return the string + */ + public static String extractStackTraceElements(int maxNumberOfElementsToCapture, Exception exc) { + StringBuilder sb = new StringBuilder(128); + + StackTraceElement[] stackTraceElements = exc.getStackTrace(); + + if (stackTraceElements != null) { + + /* + * We want to avoid an index out-of-bounds error, so we will make sure to only extract the + * number of frames from the stack trace that actually exist. + */ + + int numFramesToExtract = Math.min(maxNumberOfElementsToCapture, stackTraceElements.length); + + for (int x = 0; x < numFramesToExtract; x++) { + sb.append(stackTraceElements[x]).append("\n"); + } + + } + + return sb.toString(); + } +} diff --git a/src/test/java/org/openecomp/sparky/util/HttpServletHelper.java b/src/test/java/org/openecomp/sparky/util/HttpServletHelper.java new file mode 100644 index 0000000..cf3933a --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/HttpServletHelper.java @@ -0,0 +1,161 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import static org.junit.Assert.fail; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.mockito.Mockito; + +/** + * The Class HttpServletHelper. + */ +public class HttpServletHelper { + + public static HttpServletRequest getMockHttpServletRequest() { + return Mockito.mock(HttpServletRequest.class); + } + + /** + * Sets the request payload. + * + * @param request the request + * @param mimeType the mime type + * @param payloadContent the payload content + */ + public static void setRequestPayload(HttpServletRequest request, String mimeType, + String payloadContent) { + + try { + Mockito.when(request.getContentType()).thenReturn(mimeType); + + + final ByteArrayInputStream bais = + new ByteArrayInputStream(payloadContent.getBytes(StandardCharsets.UTF_8)); + + ServletInputStream servletInputStream = new ServletInputStream() { + + @Override + public int read() throws IOException { + return bais.read(); + } + + @Override + public boolean isFinished() { + return true; + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setReadListener(ReadListener readListener) { + // TODO Auto-generated method stub + + } + }; + + Mockito.when(request.getInputStream()).thenReturn(servletInputStream); + Mockito.when(request.getReader()).thenReturn(new BufferedReader(new StringReader(payloadContent))); + + } catch (IOException ioe) { + fail(ExceptionHelper.extractStackTraceElements(5, ioe)); + } + + } + + /** + * Gets the mock http servlet response. + * + * @param printWriter the print writer + * @return the mock http servlet response + */ + public static HttpServletResponse getMockHttpServletResponse(PrintWriter printWriter) { + HttpServletResponse commonResponse = Mockito.mock(HttpServletResponse.class); + + /* + * Use the StringWriter wrapped in a PrintWriter to redirect output stream to an in-memory + * buffer instead of an on-disk file. + */ + + try { + Mockito.when(commonResponse.getWriter()).thenReturn(printWriter); + } catch (IOException ioe) { + fail(ExceptionHelper.extractStackTraceElements(5, ioe)); + } + + return commonResponse; + } + + /** + * Assign request uri. + * + * @param req the req + * @param requestUri the request uri + */ + public static void assignRequestUri(HttpServletRequest req, String requestUri) { + Mockito.when(req.getRequestURI()).thenReturn(requestUri); + } + + /** + * Assign request parameter name map. + * + * @param req the req + * @param paramNameValueMap the param name value map + */ + public static void assignRequestParameterNameMap(HttpServletRequest req, + Map paramNameValueMap) { + if (paramNameValueMap != null) { + Mockito.when(req.getParameterNames()) + .thenReturn(Collections.enumeration(paramNameValueMap.keySet())); + + for (String key : paramNameValueMap.keySet()) { + Mockito.when(req.getParameter(key)).thenReturn(paramNameValueMap.get(key)); + } + + } + } + + public static void assignRequestHeader(HttpServletRequest req, String headerName, String headerValue) { + Mockito.when(req.getHeader(headerName)).thenReturn(headerValue); + } + +} diff --git a/src/test/java/org/openecomp/sparky/util/LogValidator.java b/src/test/java/org/openecomp/sparky/util/LogValidator.java new file mode 100644 index 0000000..0771ff1 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/LogValidator.java @@ -0,0 +1,85 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import java.util.List; + +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.LoggingEvent; + +/** + * The Class LogValidator. + */ +public class LogValidator { + + protected CaptureLoggerAppender logger = null; + + /** + * Initialize logger. + * + * @param level the level + */ + @SuppressWarnings("unchecked") + public void initializeLogger(Level level) { + ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory + .getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); + root.detachAndStopAllAppenders(); + logger = new CaptureLoggerAppender(); + root.setLevel(level); + root.addAppender(logger); + } + + public CaptureLoggerAppender getLogger() { + return logger; + } + + /** + * Dump and count logs. + * + * @param logToConsole the log to console + * @return the int + */ + public int dumpAndCountLogs(boolean logToConsole) { + + List logs = logger.drainAllLogs(); + + if (logs == null) { + return 0; + } + + if (logToConsole) { + for (LoggingEvent e : logs) { + System.out.println(e); + } + } + + return logs.size(); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/util/ModelLoaderTester.java b/src/test/java/org/openecomp/sparky/util/ModelLoaderTester.java new file mode 100644 index 0000000..6e2406c --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/ModelLoaderTester.java @@ -0,0 +1,46 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import org.openecomp.sparky.config.oxm.OxmModelLoader; + +/** + * The Class ModelLoaderTester. + */ +public class ModelLoaderTester { + + /** + * The main method. + * + * @param args the arguments + */ + public static void main(String[] args) { + System.getProperties().put("AJSC_HOME", "d:\\oxm\\"); + + OxmModelLoader loader = OxmModelLoader.getInstance(); + } + +} diff --git a/src/test/java/org/openecomp/sparky/util/NodeUtilsTest.java b/src/test/java/org/openecomp/sparky/util/NodeUtilsTest.java new file mode 100644 index 0000000..23d9df3 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/NodeUtilsTest.java @@ -0,0 +1,489 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.stream.XMLStreamConstants; + +import org.json.JSONException; +import org.junit.Before; +import org.junit.Test; +import org.openecomp.sparky.dal.rest.OperationResult; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * The Class NodeUtilsTest. + */ +public class NodeUtilsTest { + + + private static final String TEST_LINK1 = + "https://aai-ext1.test.att.com:9292/aai/v7/network/generic-vnfs/generic-vnf/cafaeb02-b54d-4918-bd06-85406dad19e7/l-interfaces/l-interface/WAN1_1123_GAMMA2016.04_PWT/l3-interface-ipv4-address-list/155.196.36.1/"; + private static final String TEST_LINK2 = + "https://aai-ext1.test.att.com:9292/aai/v7/network/generic-vnfs/generic-vnf/cafaeb02-b54d-4918-bd06-85406dad19e7/l-interfaces/l-interface/WAN1_1123_GAMMA2016.04_PWT/l3-interface-ipv4-address-list/155.196.36.1"; + private static final String TEST_LINK3 = + "https://aai-ext1.test.att.com:9292/aai/v7/network/generic-vnfs/generic-vnf/cafaeb02-b54d-4918-bd06-85406dad19e7/l-interfaces/l-interface/WAN1_1123_GAMMA2016.04_PWT/l3-interface-ipv4-address-list/ge-0%2f1%2f0"; + private static final String TEST_LINK4 = + "https://aai-ext1.test.att.com:9292/aai/v7/network/generic-vnfs/generic-vnf/cafaeb02-b54d-4918-bd06-85406dad19e7/l-interfaces/l-interface/WAN1_1123_GAMMA2016.04_PWT/l3-interface-ipv4-address-list/ge-%bad%wolf%timelord"; + private static final String TEST_LINK5_NO_RESOURCE_ID = + "https://aai-ext1.test.att.com:9292/aai/v7/network/generic-vnfs/generic-vnf/cafaeb02-b54d-4918-bd06-85406dad19e7/l-interfaces/l-interface/WAN1_1123_GAMMA2016.04_PWT/l3-interface-ipv4-address-list//"; + private static final int NODE_UTILS_TAB_WIDTH = 3; + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception {} + + /* + * String buildDepthPadding(int depth) + */ + + /** + * Builds the depth padding with negative depth. + */ + @Test + public void buildDepthPaddingWithNegativeDepth() { + String paddingString = NodeUtils.buildDepthPadding(-1); + assertEquals(paddingString.length(), 0); + } + + /** + * Builds the depth padding with zero depth. + */ + @Test + public void buildDepthPaddingWithZeroDepth() { + String paddingString = NodeUtils.buildDepthPadding(0); + assertEquals(paddingString.length(), 0); + } + + /** + * Builds the depth padding with small depth. + */ + @Test + public void buildDepthPaddingWithSmallDepth() { + String paddingString = NodeUtils.buildDepthPadding(1); + assertEquals(paddingString.length(), NODE_UTILS_TAB_WIDTH * 1); + } + + /** + * Builds the depth padding with large depth. + */ + @Test + public void buildDepthPaddingWithLargeDepth() { + String paddingString = NodeUtils.buildDepthPadding(100); + assertEquals(paddingString.length(), NODE_UTILS_TAB_WIDTH * 100); + } + + /* + * String buildEntityResourceKey(String entityType, String resourceId) + */ + + /* + * TODO: we should probably throw an IllegalArgumentExecption or just return null if a required + * parameter is passed to us with a null. + */ + + /** + * Builds the entity resource key with null entity type. + */ + @Test + public void buildEntityResourceKeyWithNullEntityType() { + String resourceId = NodeUtils.buildEntityResourceKey(null, "generic-vnf-123"); + assertEquals(resourceId, "null.generic-vnf-123"); + } + + /** + * Builds the entity resource key with null resource id. + */ + @Test + public void buildEntityResourceKeyWithNullResourceId() { + String resourceId = NodeUtils.buildEntityResourceKey("generic-vnf", null); + assertEquals(resourceId, "generic-vnf.null"); + } + + /** + * Builds the entity resource key success path. + */ + @Test + public void buildEntityResourceKeySuccessPath() { + String resourceId = NodeUtils.buildEntityResourceKey("generic-vnf", "generic-vnf-123"); + assertEquals(resourceId, "generic-vnf.generic-vnf-123"); + } + + /* + * String extractResourceIdFromLink(String link) + */ + + /** + * Id extraction when url has trailing forward slash. + */ + @Test + public void idExtractionWhenUrlHasTrailingForwardSlash() { + + String resourceId = NodeUtils.extractResourceIdFromLink(TEST_LINK1); + + if (!"155.196.36.1".equals(resourceId)) { + fail("Failed to extract expected resourceId"); + } + } + + /** + * Id extraction when url does not have trailing forward slash. + */ + @Test + public void idExtractionWhenUrlDoesNotHaveTrailingForwardSlash() { + + String resourceId = NodeUtils.extractResourceIdFromLink(TEST_LINK2); + + if (!"155.196.36.1".equals(resourceId)) { + fail("Failed to extract expected resourceId"); + } + } + + /** + * Id extraction when url contains url encoded hex characters. + */ + @Test + public void idExtractionWhenUrlContainsUrlEncodedHexCharacters() { + + String resourceId = NodeUtils.extractResourceIdFromLink(TEST_LINK3); + + if (!"ge-0/1/0".equals(resourceId)) { + fail("Failed to extract expected resourceId"); + } + + } + + /** + * Id extraction when url contains non standard hex characters. + */ + @Test + public void idExtractionWhenUrlContainsNonStandardHexCharacters() { + + String resourceId = NodeUtils.extractResourceIdFromLink(TEST_LINK4); + + /* + * This is not an expected hex encoding, so the decode will fail and the original parameter will + * be returned instead. + */ + + if (!"ge-%bad%wolf%timelord".equals(resourceId)) { + fail("Failed to extract expected resourceId"); + } + + } + + /** + * Id extraction when url is null. + */ + @Test + public void idExtractionWhenUrlIsNull() { + String resourceId = NodeUtils.extractResourceIdFromLink(null); + assertEquals(null, resourceId); + } + + /** + * Id extraction when url is empty string. + */ + @Test + public void idExtractionWhenUrlIsEmptyString() { + String resourceId = NodeUtils.extractResourceIdFromLink(""); + assertEquals(null, resourceId); + } + + /* + * String getXMLStreamConstantAsStr(int c) + */ + + /** + * Test string conversion of xml stream constants. + */ + @Test + public void testStringConversionOfXmlStreamConstants() { + + /* + * Range of enum is 0 - 256 + */ + + for (int id = 0; id <= 256; id++) { + + switch (id) { + case XMLStreamConstants.ATTRIBUTE: { + assertEquals("ATTRIBUTE", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.CDATA: { + assertEquals("CDATA", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.CHARACTERS: { + assertEquals("CHARACTERS", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.COMMENT: { + assertEquals("COMMENT", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.DTD: { + assertEquals("DTD", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.END_DOCUMENT: { + assertEquals("END_DOCUMENT", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.END_ELEMENT: { + assertEquals("END_ELEMENT", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.ENTITY_DECLARATION: { + assertEquals("ENTITY_DECLARATION", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.ENTITY_REFERENCE: { + assertEquals("ENTITY_REFERENCE", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.NAMESPACE: { + assertEquals("NAMESPACE", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.NOTATION_DECLARATION: { + assertEquals("NOTATION_DECLARATION", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.PROCESSING_INSTRUCTION: { + assertEquals("PROCESSING_INSTRUCTION", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.SPACE: { + assertEquals("SPACE", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.START_DOCUMENT: { + assertEquals("START_DOCUMENT", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + case XMLStreamConstants.START_ELEMENT: { + assertEquals("START_ELEMENT", NodeUtils.getXmlStreamConstantAsStr(id)); + break; + } + + default: + String result = NodeUtils.getXmlStreamConstantAsStr(id); + assertNotNull(result); + if (!result.startsWith("Unknown")) { + fail("Unexecpted XML Stream Constant definition for id = " + id); + } + + } + + } + } + + /** + * Convert object to json successful. + * + * @throws JsonProcessingException the json processing exception + */ + @Test + public void convertObjectToJsonSuccessful() throws JsonProcessingException { + + OperationResult opResult = new OperationResult(200, "op result"); + String asJson = NodeUtils.convertObjectToJson(opResult, false); + + assertTrue("Doesn't contain result field", asJson.contains("result")); + assertTrue("Doesn't contain resultCode field", asJson.contains("resultCode")); + assertTrue("Doesn't contain resolvedLinkFailure field", asJson.contains("resolvedLinkFailure")); + + } + + /** + * Convert object to json successful pretty. + * + * @throws JsonProcessingException the json processing exception + */ + @Test + public void convertObjectToJsonSuccessful_pretty() throws JsonProcessingException { + + OperationResult opResult = new OperationResult(200, "op result"); + String asJson = NodeUtils.convertObjectToJson(opResult, true); + + assertTrue("Doesn't contain result field", asJson.contains("result")); + assertTrue("Doesn't contain resultCode field", asJson.contains("resultCode")); + assertTrue("Doesn't contain resolvedLinkFailure field", asJson.contains("resolvedLinkFailure")); + + } + + /** + * Convert object to json failure caused by null. + * + * @throws JsonProcessingException the json processing exception + */ + @Test() + public void convertObjectToJsonFailure_causedBy_null() throws JsonProcessingException { + + String asJson = NodeUtils.convertObjectToJson(null, true); + + assertTrue("Doesn't contain result field", !asJson.contains("result")); + assertTrue("Doesn't contain resultCode field", !asJson.contains("resultCode")); + assertTrue("Doesn't contain resolvedLinkFailure field", + !asJson.contains("resolvedLinkFailure")); + + } + + /** + * Convert object to xml successful. + * + * @throws JsonProcessingException the json processing exception + */ + @Test + public void convertObjectToXmlSuccessful() throws JsonProcessingException { + + OperationResult opResult = new OperationResult(200, "op result"); + String asXml = NodeUtils.convertObjectToXml(opResult); + + assertTrue("Doesn't contain result field", asXml.contains("result")); + assertTrue("Doesn't contain resultCode field", asXml.contains("resultCode")); + assertTrue("Doesn't contain resolvedLinkFailure field", asXml.contains("resolvedLinkFailure")); + + } + + /** + * Convert object to xml failure caused by null. + * + * @throws JsonProcessingException the json processing exception + */ + @Test(expected = JSONException.class) + public void convertObjectToXmlFailure_causedBy_null() throws JsonProcessingException { + + String asXml = NodeUtils.convertObjectToXml(null); + assertNull("Output should be null", asXml); + + } + + /** + * Validate concatonate list empty list. + * + * @throws JsonProcessingException the json processing exception + */ + @Test + public void validateConcatonateList_EmptyList() throws JsonProcessingException { + + String[] array = null; + String result = NodeUtils.concatArray(array); + assertEquals("", result); + + List emptyList = Collections.emptyList(); + result = NodeUtils.concatArray(emptyList); + assertEquals("", result); + } + + /** + * Validate concatonate list multiple values. + * + * @throws JsonProcessingException the json processing exception + */ + @Test + public void validateConcatonateList_MultipleValues() throws JsonProcessingException { + + List numberList = new ArrayList(); + + numberList.add("1"); + numberList.add("2"); + numberList.add("3"); + + String result = NodeUtils.concatArray(numberList); + assertEquals("1 2 3", result); + } + + /** + * Test format timestamp expect valid result. + */ + @Test + public void test_formatTimestamp_expectValidResult() { + String validTimeStamp = "20170111T123116Z"; + String result = NodeUtils.formatTimestamp(validTimeStamp); + + assertEquals("2017-01-11T12:31:16Z", result); + } + + /** + * Test format timestamp expect invalid result. + */ + @Test + public void test_formatTimestamp_expectInvalidResult() { + String validTimeStamp = "#20170011T123116Z"; + String result = NodeUtils.formatTimestamp(validTimeStamp); + + assertEquals(validTimeStamp, result); + } + + /** + * test calculate edit attributes urls + */ + @Test + public void validateCalculateEditAttributeLogic() { + + assertEquals(NodeUtils.calculateEditAttributeUri("https://localhost:9000/aai/v7/pservers/pserver/12345"),"pservers/pserver/12345"); + assertEquals(NodeUtils.calculateEditAttributeUri("https://localhost:9000/aai/v1/pservers/pserver/12345"),"pservers/pserver/12345"); + assertEquals(NodeUtils.calculateEditAttributeUri("https://localhost:9000/aai/v21/pservers/pserver/12345"),"pservers/pserver/12345"); + assertEquals(NodeUtils.calculateEditAttributeUri("https://localhost:9000/aai/v211/pservers/pserver/12345"),"pservers/pserver/12345"); + assertEquals(NodeUtils.calculateEditAttributeUri("https://localhost:9000/aai/v5252/pservers/pserver/12345"),"pservers/pserver/12345"); + assertNull(NodeUtils.calculateEditAttributeUri(null)); + assertNull(NodeUtils.calculateEditAttributeUri("https://localhost:9000/aai/noVersionTag/pservers/pserver/12345")); + + } + + +} diff --git a/src/test/java/org/openecomp/sparky/util/OxmModelLoaderTest.java b/src/test/java/org/openecomp/sparky/util/OxmModelLoaderTest.java new file mode 100644 index 0000000..a830175 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/OxmModelLoaderTest.java @@ -0,0 +1,166 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.io.File; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mockito; +import org.openecomp.sparky.config.oxm.OxmModelLoader; + +/** + * The Class OxmModelLoaderTest. + */ +public class OxmModelLoaderTest { + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + OxmModelLoader loader; + + /** + * Inits the. + * + * @throws IOException Signals that an I/O exception has occurred. + */ + @Before + public void init() throws IOException { + + + } + + /** + * Test find latest oxm version expectv 9. + * + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test + public void test_findLatestOxmVersion_expectv9() throws IOException { + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + folder.newFile("aai_oxm_v7.xml"); + folder.newFile("aai_oxm_v8.xml"); + folder.newFile("aai_oxm_v9.xml"); + folder.newFile("randomTest.xml"); + + loader = Mockito.spy(new OxmModelLoader()); + Mockito.when(loader.loadOxmFolder()).thenReturn(folder.getRoot()); + + String version = loader.findLatestOxmVersion(); + + assertEquals("v9", version); + } + + /** + * Test find latest oxm version expect null when folder is empty. + * + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test + public void test_findLatestOxmVersion_expectNullWhenFolderIsEmpty() throws IOException { + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + loader = Mockito.spy(new OxmModelLoader()); + Mockito.when(loader.loadOxmFolder()).thenReturn(folder.getRoot()); + + String version = loader.findLatestOxmVersion(); + + assertEquals(null, version); + } + + /** + * Test find latest oxm version expect null when files does not match expected pattern. + * + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test + public void test_findLatestOxmVersion_expectNullWhenFilesDoesNotMatchExpectedPattern() + throws IOException { + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + folder.newFile("file1.xml"); + folder.newFile("file2.xml"); + + loader = Mockito.spy(new OxmModelLoader()); + Mockito.when(loader.loadOxmFolder()).thenReturn(folder.getRoot()); + + String version = loader.findLatestOxmVersion(); + + assertEquals(null, version); + } + + /** + * Test load model expect success. + * + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test + public void test_loadModel_expectSuccess() throws IOException { + String version = "v9"; + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + loader = Mockito.spy(new OxmModelLoader()); + Mockito.when(loader.loadOxmFileName(version)).thenReturn( + System.getProperty("AJSC_HOME") + "/bundleconfig-local/oxm/aai_oxm_" + version + ".xml"); + + loader.loadModel(version); + + assertNotEquals(null, loader.getOxmModel()); + } + + /** + * Test load model expect oxm data as empty. + * + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test + public void test_loadModel_expectOxmDataAsEmpty() throws IOException { + String version = "v8"; + System.setProperty("AJSC_HOME", new File(".").getCanonicalPath().replace('\\', '/')); + + loader = Mockito.spy(new OxmModelLoader()); + Mockito.when(loader.loadOxmFileName(version)).thenReturn( + System.getProperty("AJSC_HOME") + "/bundleconfig-local/oxm/aai_oxm_" + version + ".xml"); + + loader.loadModel(version); + + assertEquals(0, loader.getOxmModel().size()); + assertEquals(true, loader.getSearchableEntityDescriptors().isEmpty()); + assertEquals(0, loader.getSearchableOxmModel().size()); + + + + assertNotEquals(null, loader.getOxmModel()); + } + +} diff --git a/src/test/java/org/openecomp/sparky/util/SuggestionsPermutationsTest.java b/src/test/java/org/openecomp/sparky/util/SuggestionsPermutationsTest.java new file mode 100644 index 0000000..dd5d7ca --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/SuggestionsPermutationsTest.java @@ -0,0 +1,35 @@ +package org.openecomp.sparky.util; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +public class SuggestionsPermutationsTest { + + @Test + public void isValidSuggestionPermutation_successPath() { + + List x = new ArrayList<>(Arrays.asList("A", "B", "C", "D")); + SuggestionsPermutation suggPermutation = new SuggestionsPermutation(); + + ArrayList> uniqueLists = suggPermutation.getSuggestionsPermutation(x); + + assertTrue(uniqueLists.get(0).toString().equals("[A]")); + assertTrue(uniqueLists.get(1).toString().equals("[A, B, C, D]")); + assertTrue(uniqueLists.get(2).toString().equals("[A, C, D]")); + assertTrue(uniqueLists.get(3).toString().equals("[A, D]")); + assertTrue(uniqueLists.get(4).toString().equals("[B]")); + assertTrue(uniqueLists.get(5).toString().equals("[B, C, D]")); + assertTrue(uniqueLists.get(6).toString().equals("[B, D]")); + assertTrue(uniqueLists.get(7).toString().equals("[C]")); + assertTrue(uniqueLists.get(8).toString().equals("[C, D]")); + assertTrue(uniqueLists.get(9).toString().equals("[D]")); + assertTrue(uniqueLists.size() == 10); + + } +} diff --git a/src/test/java/org/openecomp/sparky/util/TreeWalkerTest.java b/src/test/java/org/openecomp/sparky/util/TreeWalkerTest.java new file mode 100644 index 0000000..27eb0c0 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/util/TreeWalkerTest.java @@ -0,0 +1,563 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import ch.qos.logback.classic.Level; + +/** + * The Class TreeWalkerTest. + */ +public class TreeWalkerTest { + + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + } + + /** + * Validate json node conversion null input. + */ + @Test + public void validateJsonNodeConversionNullInput() { + + TreeWalker walker = new TreeWalker(); + + try { + JsonNode convertedNode = walker.convertJsonToNode(null); + assertNull("Converted node should have be null", convertedNode); + + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expeted + } + + } + + /** + * Validate json node conversion empty non json input. + */ + @Test + public void validateJsonNodeConversionEmptyNonJsonInput() { + + TreeWalker walker = new TreeWalker(); + + try { + JsonNode convertedNode = walker.convertJsonToNode(""); + assertNull("Converted node should have be null", convertedNode); + + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expeted + } + + } + + /** + * Validate json node conversion empty json input. + */ + @Test + public void validateJsonNodeConversionEmptyJsonInput() { + + TreeWalker walker = new TreeWalker(); + + try { + JsonNode convertedNode = walker.convertJsonToNode("{}"); + assertNotNull("Converted node should not be null", convertedNode); + + ObjectMapper objectMapper = new ObjectMapper(); + String convertedNodeAsStr = objectMapper.writeValueAsString(convertedNode); + + assertEquals("{}", convertedNodeAsStr); + + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expeted + } + + } + + /** + * Validate walk tree null input. + */ + @Test + public void validateWalkTreeNullInput() { + + TreeWalker walker = new TreeWalker(); + + List paths = new ArrayList(); + walker.walkTree(paths, null); + assertEquals(0, paths.size()); + + } + + /** + * Validate walk tree empty node. + */ + @Test + public void validateWalkTreeEmptyNode() { + + try { + TreeWalker walker = new TreeWalker(); + List paths = new ArrayList(); + walker.walkTree(paths, walker.convertJsonToNode("{}")); + assertEquals(0, paths.size()); + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expected + } + + } + + /** + * Validate walk tree one parent node. + */ + @Test + public void validateWalkTreeOneParentNode() { + + try { + TreeWalker walker = new TreeWalker(); + List paths = new ArrayList(); + walker.walkTree(paths, walker.convertJsonToNode("{ \"root\" : { } }")); + assertEquals(1, paths.size()); + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expected + } + + } + + /** + * Validate walk tree one parent node with object array. + */ + @Test + public void validateWalkTreeOneParentNodeWithObjectArray() { + + try { + String jsonStr = + "{\"Employee\":[{\"id\":\"101\",\"name\":\"Pushkar\",\"salary\":\"5000\"}," + + "{\"id\":\"102\",\"name\":\"Rahul\",\"salary\":\"4000\"}," + + "{\"id\":\"103\",\"name\":\"tanveer\",\"salary\":\"56678\"}]}"; + TreeWalker walker = new TreeWalker(); + List paths = new ArrayList(); + walker.walkTree(paths, walker.convertJsonToNode(jsonStr)); + assertEquals(9, paths.size()); + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expected + } + + } + + /** + * Validate walk tree one parent node with value array. + */ + @Test + public void validateWalkTreeOneParentNodeWithValueArray() { + + try { + String jsonStr = "{ \"colors\" : [ \"yellow\", \"blue\", \"red\" ] }"; + TreeWalker walker = new TreeWalker(); + List paths = new ArrayList(); + walker.walkTree(paths, walker.convertJsonToNode(jsonStr)); + + assertEquals(3, paths.size()); + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expected + } + + } + + /** + * Test walk for complex entity type aai entity node descriptors. + */ + @Test + public void testWalkForComplexEntityType_AaiEntityNodeDescriptors() { + + try { + String jsonStr = + "{ \"generalNodeClass\": { \"class\": \"aai-entity-node general-node\"," + + " \"visualElements\": [ { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }, {" + + " \"type\": \"circle\", \"class\": \"inner\", " + + " \"svgAttributes\": { \"r\": \"10\" " + + "} }, { \"type\": \"text\", " + + "\"class\": \"id-type-label\", \"displayKey\": \"itemType\", " + + " \"shapeAttributes\": { \"offset\": { " + + " \"x\": \"0\", \"y\": \"30\" } " + + " } }, { \"type\": \"text\", " + + " \"class\": \"id-value-label\", \"displayKey\":" + + " \"itemNameValue\", \"shapeAttributes\": { " + + " \"offset\": { \"x\": \"0\", " + + " \"y\": \"40\" } } } ] " + + " }, \"searchedNodeClass\": { \"class\": \"aai-entity-node search-node\"," + + " \"visualElements\": [ { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": { " + + " \"r\": \"20\" } }, { " + + " \"type\": \"circle\", \"class\": \"inner\", " + + " \"svgAttributes\": { \"r\": \"10\" }" + + " }, { \"type\": \"text\", " + + "\"class\": \"id-type-label\", \"displayKey\": \"itemType\", " + + " \"shapeAttributes\": { \"offset\": { " + + " \"x\": \"0\", \"y\": \"30\" }" + + " } }, { \"type\": \"text\", " + + " \"class\": \"id-value-label\", " + + "\"displayKey\": \"itemNameValue\", \"shapeAttributes\": {" + + " \"offset\": { \"x\": \"0\"," + + " \"y\": \"40\" } }" + + " } ] }, \"selectedSearchedNodeClass\": { " + + "\"class\": \"aai-entity-node selected-search-node\", \"visualElements\": [" + + " { \"type\": \"circle\", " + + "\"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }, {" + + " \"type\": \"circle\", \"class\": \"inner\"," + + " \"svgAttributes\": { \"r\": \"10\" " + + " } }, { \"type\": \"text\", " + + " \"class\": \"id-type-label\", \"displayKey\": \"itemType\"," + + " \"shapeAttributes\": { \"offset\": {" + + " \"x\": \"0\", \"y\": \"30\"" + + " } } }, { " + + " \"type\": \"text\", \"class\": \"id-value-label\", " + + " \"displayKey\": \"itemNameValue\", \"shapeAttributes\": {" + + " \"offset\": { \"x\": \"0\", " + + " \"y\": \"40\" } } } ]" + + " }, \"selectedNodeClass\": { \"class\":" + + " \"aai-entity-node selected-node\"," + + " \"visualElements\": [ { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }, {" + + " \"type\": \"circle\", \"class\": \"inner\"," + + " \"svgAttributes\": { \"r\": \"10\" " + + " } }, { \"type\": \"text\", " + + " \"class\": \"id-type-label\", \"displayKey\": \"itemType\"," + + " \"shapeAttributes\": { \"offset\": " + + "{ " + + " \"x\": \"0\", \"y\": \"30\" } " + + " } }, { \"type\": \"text\"," + + " \"class\": \"id-value-label\", \"displayKey\":" + + " \"itemNameValue\", \"shapeAttributes\": { " + + "\"offset\": { \"x\": \"0\", " + + "\"y\": \"40\" } } } ] }}"; + TreeWalker walker = new TreeWalker(); + List paths = new ArrayList(); + walker.walkTree(paths, walker.convertJsonToNode(jsonStr)); + + assertEquals(68, paths.size()); + + /* + * Example of expected value + * + * generalNodeClass.class=aai-entity-node general-node + * generalNodeClass.visualElements.type=circle generalNodeClass.visualElements.class=outer + * generalNodeClass.visualElements.svgAttributes.r=20 + * generalNodeClass.visualElements.type=circle generalNodeClass.visualElements.class=inner + * generalNodeClass.visualElements.svgAttributes.r=10 + * generalNodeClass.visualElements.type=text + * generalNodeClass.visualElements.class=id-type-label + * generalNodeClass.visualElements.displayKey=itemType + * generalNodeClass.visualElements.shapeAttributes.offset.x=0 + * generalNodeClass.visualElements.shapeAttributes.offset.y=30 + * generalNodeClass.visualElements.type=text + * generalNodeClass.visualElements.class=id-value-label + * generalNodeClass.visualElements.displayKey=itemNameValue + * generalNodeClass.visualElements.shapeAttributes.offset.x=0 + * generalNodeClass.visualElements.shapeAttributes.offset.y=40 + * searchedNodeClass.class=aai-entity-node search-node + * searchedNodeClass.visualElements.type=circle searchedNodeClass.visualElements.class=outer + * searchedNodeClass.visualElements.svgAttributes.r=20 + * searchedNodeClass.visualElements.type=circle searchedNodeClass.visualElements.class=inner + * searchedNodeClass.visualElements.svgAttributes.r=10 + * searchedNodeClass.visualElements.type=text + * searchedNodeClass.visualElements.class=id-type-label + * searchedNodeClass.visualElements.displayKey=itemType + * searchedNodeClass.visualElements.shapeAttributes.offset.x=0 + * searchedNodeClass.visualElements.shapeAttributes.offset.y=30 + * searchedNodeClass.visualElements.type=text + * searchedNodeClass.visualElements.class=id-value-label + * searchedNodeClass.visualElements.displayKey=itemNameValue + * searchedNodeClass.visualElements.shapeAttributes.offset.x=0 + * searchedNodeClass.visualElements.shapeAttributes.offset.y=40 + * selectedSearchedNodeClass.class=aai-entity-node selected-search-node + * selectedSearchedNodeClass.visualElements.type=circle + * selectedSearchedNodeClass.visualElements.class=outer + * selectedSearchedNodeClass.visualElements.svgAttributes.r=20 + * selectedSearchedNodeClass.visualElements.type=circle + * selectedSearchedNodeClass.visualElements.class=inner + * selectedSearchedNodeClass.visualElements.svgAttributes.r=10 + * selectedSearchedNodeClass.visualElements.type=text + * selectedSearchedNodeClass.visualElements.class=id-type-label + * selectedSearchedNodeClass.visualElements.displayKey=itemType + * selectedSearchedNodeClass.visualElements.shapeAttributes.offset.x=0 + * selectedSearchedNodeClass.visualElements.shapeAttributes.offset.y=30 + * selectedSearchedNodeClass.visualElements.type=text + * selectedSearchedNodeClass.visualElements.class=id-value-label + * selectedSearchedNodeClass.visualElements.displayKey=itemNameValue + * selectedSearchedNodeClass.visualElements.shapeAttributes.offset.x=0 + * selectedSearchedNodeClass.visualElements.shapeAttributes.offset.y=40 + * selectedNodeClass.class=aai-entity-node selected-node + * selectedNodeClass.visualElements.type=circle selectedNodeClass.visualElements.class=outer + * selectedNodeClass.visualElements.svgAttributes.r=20 + * selectedNodeClass.visualElements.type=circle selectedNodeClass.visualElements.class=inner + * selectedNodeClass.visualElements.svgAttributes.r=10 + * selectedNodeClass.visualElements.type=text + * selectedNodeClass.visualElements.class=id-type-label + * selectedNodeClass.visualElements.displayKey=itemType + * selectedNodeClass.visualElements.shapeAttributes.offset.x=0 + * selectedNodeClass.visualElements.shapeAttributes.offset.y=30 + * selectedNodeClass.visualElements.type=text + * selectedNodeClass.visualElements.class=id-value-label + * selectedNodeClass.visualElements.displayKey=itemNameValue + * selectedNodeClass.visualElements.shapeAttributes.offset.x=0 + * selectedNodeClass.visualElements.shapeAttributes.offset.y=40 + */ + + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expected + } + + } + + /** + * Test complex object inversion equality. + */ + @Test + public void testComplexObjectInversionEquality() { + + /** + * Dave Adams (1-Nov-2016): + * + * Ok.. I agree...weird title of the test-case. This test is focused on the isEqual equality + * test within the NodeUtils helper class which compares the sorted structural paths of two Json + * Object representations. I attempted to normalize unordered structures to produce an equality + * result, as there doesn't seem to be any natural equality test between two JsonNode objects + * that I could find to date. + * + * Basically, this test is confirming that if the same object values are present in different + * orders, they are effectively the same Json Object representation, and pass, at least my + * structural value equality test. + * + * I reordered the aaiEntityNodeDescriptors top level class types, and change the order of the + * x,y coordinates to be y,x. Same values different order. Once again, the expectation is that + * both representations are objectively equal, they just have different json representations. + */ + + try { + String n1Str = + "{ \"generalNodeClass\": { \"class\": \"aai-entity-node general-node\"," + + " \"visualElements\": [ { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }, {" + + " \"type\": \"circle\", \"class\": \"inner\"," + + " \"svgAttributes\": { \"r\": \"10\"" + + " } }, { \"type\": \"text\"," + + " \"class\": \"id-type-label\", \"displayKey\":" + + " \"itemType\", \"shapeAttributes\": { \"offset\":" + + " { \"x\": \"0\", \"y\": \"30\"" + + " } } }, {" + + " \"type\": \"text\", \"class\": \"id-value-label\"," + + " \"displayKey\": \"itemNameValue\"," + + " \"shapeAttributes\": { \"offset\":" + + " { \"x\": \"0\", \"y\": \"40\"" + + " } } } ] }," + + " \"searchedNodeClass\": { \"class\": \"aai-entity-node search-node\"," + + " \"visualElements\": [ { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }, {" + + " \"type\": \"circle\", \"class\": \"inner\"," + + " \"svgAttributes\": { \"r\": \"10\"" + + " } }, { \"type\": \"text\"," + + " \"class\": \"id-type-label\", \"displayKey\":" + + " \"itemType\", \"shapeAttributes\": { \"offset\": {" + + " \"x\": \"0\", \"y\": \"30\"" + + " } } }, {" + + " \"type\": \"text\", \"class\": \"id-value-label\"," + + " \"displayKey\": \"itemNameValue\"," + + " \"shapeAttributes\": { \"offset\": {" + + " \"x\": \"0\", \"y\": \"40\"" + + " } } } ] }," + + " \"selectedSearchedNodeClass\": { \"class\":" + + " \"aai-entity-node selected-search-node\", \"visualElements\": [" + + " { \"type\": \"circle\", \"class\":" + + " \"outer\", \"svgAttributes\": { \"r\": \"20\"" + + " } }, { \"type\": \"circle\"," + + " \"class\": \"inner\", \"svgAttributes\": {" + + " \"r\": \"10\" } }, {" + + " \"type\": \"text\", \"class\": \"id-type-label\"," + + " \"displayKey\": \"itemType\", \"shapeAttributes\": {" + + " \"offset\": { \"x\": \"0\"," + + " \"y\": \"30\" } }" + + " }, { \"type\": \"text\"," + + " \"class\": \"id-value-label\"," + + " \"displayKey\": \"itemNameValue\"," + + " \"shapeAttributes\": { \"offset\": {" + + " \"x\": \"0\", \"y\": \"40\"" + + " } } } ] }," + + " \"selectedNodeClass\": { \"class\": \"aai-entity-node selected-node\"," + + " \"visualElements\": [ { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }, {" + + " \"type\": \"circle\", \"class\": \"inner\"," + + " \"svgAttributes\": { \"r\": \"10\"" + + " } }, { \"type\": \"text\"," + + " \"class\": \"id-type-label\", \"displayKey\":" + + " \"itemType\", \"shapeAttributes\": {" + + " \"offset\": { \"x\": \"0\"," + + " \"y\": \"30\" }" + + " } }, { \"type\": \"text\"," + + " \"class\": \"id-value-label\", \"displayKey\":" + + " \"itemNameValue\", \"shapeAttributes\": {" + + " \"offset\": { \"x\": \"0\"," + + " \"y\": \"40\" } }" + + " } ] }}"; + String n2Str = + "{ \"searchedNodeClass\": { \"class\": \"aai-entity-node search-node\"," + + " \"visualElements\": [ { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }," + + " { \"type\": \"circle\"," + + " \"class\": \"inner\", \"svgAttributes\": {" + + " \"r\": \"10\" } }, {" + + " \"type\": \"text\", \"class\": \"id-type-label\"," + + " \"displayKey\": \"itemType\", \"shapeAttributes\": {" + + " \"offset\": { \"y\": \"30\"," + + " \"x\": \"0\" } }" + + " }, { \"type\": \"text\"," + + " \"class\": \"id-value-label\"," + + " \"displayKey\": \"itemNameValue\"," + + " \"shapeAttributes\": { \"offset\": {" + + " \"y\": \"40\", \"x\": \"0\"" + + " } } } ] }," + + " \"selectedSearchedNodeClass\": { \"class\":" + + " \"aai-entity-node selected-search-node\", \"visualElements\": [" + + " { \"type\": \"circle\", \"class\":" + + " \"outer\", \"svgAttributes\": { \"r\": \"20\"" + + " } }, { \"type\": \"circle\"," + + " \"class\": \"inner\", \"svgAttributes\": {" + + " \"r\": \"10\" } }, {" + + " \"type\": \"text\", \"class\": \"id-type-label\"," + + " \"displayKey\": \"itemType\", \"shapeAttributes\": {" + + " \"offset\": { \"y\": \"30\"," + + " \"x\": \"0\" } }" + + " }, { \"type\": \"text\"," + + " \"class\": \"id-value-label\"," + + " \"displayKey\": \"itemNameValue\"," + + " \"shapeAttributes\": { \"offset\": {" + + " \"y\": \"40\", \"x\": \"0\"" + + " } } } ] }," + + " \"selectedNodeClass\": { \"class\": \"aai-entity-node selected-node\"," + + " \"visualElements\": [ { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }, {" + + " \"type\": \"circle\", \"class\": \"inner\"," + + " \"svgAttributes\": { \"r\": \"10\"" + + " } }, { \"type\": \"text\"," + + " \"class\": \"id-type-label\"," + + " \"displayKey\": \"itemType\", \"shapeAttributes\": {" + + " \"offset\": { \"y\": \"30\"," + + " \"x\": \"0\" } }" + + " }, { \"type\": \"text\"," + + " \"class\": \"id-value-label\"," + + " \"displayKey\": \"itemNameValue\"," + + " \"shapeAttributes\": { \"offset\": {" + + " \"y\": \"40\", \"x\": \"0\"" + + " } } } ] }," + + " \"generalNodeClass\": { \"class\":" + + " \"aai-entity-node general-node\", \"visualElements\": [" + + " { \"type\": \"circle\"," + + " \"class\": \"outer\", \"svgAttributes\": {" + + " \"r\": \"20\" } }," + + " { \"type\": \"circle\"," + + " \"class\": \"inner\", \"svgAttributes\": {" + + " \"r\": \"10\" } }," + + " { \"type\": \"text\"," + + " \"class\": \"id-type-label\", \"displayKey\":" + + " \"itemType\", \"shapeAttributes\": {" + + " \"offset\": { \"y\": \"30\"," + + " \"x\": \"0\" }" + + " } }, {" + + " \"type\": \"text\"," + + " \"class\": \"id-value-label\", \"displayKey\":" + + " \"itemNameValue\", \"shapeAttributes\": {" + + " \"offset\": { \"y\": \"40\"," + + " \"x\": \"0\" }" + + " } } ] }}"; + + TreeWalker walker = new TreeWalker(); + List n1Paths = new ArrayList(); + List n2Paths = new ArrayList(); + + JsonNode n1 = walker.convertJsonToNode(n1Str); + JsonNode n2 = walker.convertJsonToNode(n2Str); + walker.walkTree(n1Paths, n1); + walker.walkTree(n2Paths, n2); + + assertEquals(68, n1Paths.size()); + assertEquals(68, n2Paths.size()); + + assertTrue(NodeUtils.isEqual(n1, n2)); + + } catch (JsonProcessingException exc) { + // expected + } catch (IOException exc) { + // expected + } + + } + + + +} diff --git a/src/test/java/org/openecomp/sparky/viewandinspect/ActiveInventoryNodeTester.java b/src/test/java/org/openecomp/sparky/viewandinspect/ActiveInventoryNodeTester.java new file mode 100644 index 0000000..bdacfe9 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/viewandinspect/ActiveInventoryNodeTester.java @@ -0,0 +1,379 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.viewandinspect; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; + +import java.io.IOException; +import java.util.Iterator; + +import org.openecomp.sparky.viewandinspect.config.VisualizationConfig; +import org.openecomp.sparky.viewandinspect.entity.ActiveInventoryNode; + +/** + * The Class ActiveInventoryNodeTester. + */ +public class ActiveInventoryNodeTester { + + /** + * Builds the tree 1. + * + * @return the active inventory node + */ + public ActiveInventoryNode buildTree1() { + + ActiveInventoryNode nodeA = new ActiveInventoryNode("A"); + nodeA.setSelfLink(String.format(selfLinkFormat, "A", "A")); + nodeA.addProperty("a1", "a1"); + nodeA.addProperty("a2", "a2"); + nodeA.addProperty("a3", "a3"); + + createChildNode("C", nodeA, "c1", "c2", "c3"); + createChildNode("D", nodeA, "d1", "d2", "d3"); + createChildNode("E", nodeA, "e1", "e2", "e3"); + + /* + * Assume key uniqueness within a single tree. Safe?? Can we say that every nodeId is unique? + */ + + + return nodeA; + + } + + /** + * Builds the tree 2. + * + * @return the active inventory node + */ + public ActiveInventoryNode buildTree2() { + + ActiveInventoryNode nodeA = new ActiveInventoryNode("A"); + nodeA.setSelfLink(String.format(selfLinkFormat, "A", "A")); + nodeA.addProperty("a4", "a4"); + + ActiveInventoryNode nodeD = createChildNode("D", nodeA, "d7", "d8"); + ActiveInventoryNode nodeW = createChildNode("W", nodeD, "w1", "w2", "w3"); + + createChildNode("H", nodeA, "h2", "h4", "h6"); + + return nodeA; + } + + private String selfLinkFormat = + "https://aai-ext1.test.att.com:9292/aai/v7/network/generic-vnfs/%s/%s"; + + + /** + * Creates the child node. + * + * @param key the key + * @param parent the parent + * @param propertyNames the property names + * @return the active inventory node + */ + private ActiveInventoryNode createChildNode(String key, ActiveInventoryNode parent, + String... propertyNames) { + // ActiveInventoryNode ain = parent.addNode(new ActiveInventoryNode(key)); + // ain.setSelfLink(String.format(SELF_LINK_FORMAT, key, key)); + /* + * if (propertyNames != null) { for (String p : propertyNames) { ain.addProperty(p, p); } } + */ + + ActiveInventoryNode ain = new ActiveInventoryNode(); + + return ain; + + } + + /** + * Builds the tree 3. + * + * @return the active inventory node + */ + public ActiveInventoryNode buildTree3() { + + ActiveInventoryNode nodeA = new ActiveInventoryNode("A"); + nodeA.setSelfLink(String.format(selfLinkFormat, "A", "A")); + nodeA.addProperty("a1", "a1"); + + createChildNode("B", nodeA, "b1"); + createChildNode("C", nodeA, "c1"); + createChildNode("D", nodeA, "d1"); + createChildNode("E", nodeA, "e1"); + createChildNode("F", nodeA, "f1"); + createChildNode("G", nodeA, "g1"); + + return nodeA; + } + + /** + * Builds the tree 4. + * + * @return the active inventory node + */ + public ActiveInventoryNode buildTree4() { + + ActiveInventoryNode nodeA = new ActiveInventoryNode("A"); + nodeA.setSelfLink(String.format(selfLinkFormat, "A", "A")); + nodeA.addProperty("a2", "a2"); + + ActiveInventoryNode nodeB = createChildNode("B", nodeA, "b2"); + ActiveInventoryNode nodeC = createChildNode("C", nodeB, "c2"); + ActiveInventoryNode nodeD = createChildNode("D", nodeC, "d2"); + ActiveInventoryNode nodeE = createChildNode("E", nodeD, "e2"); + ActiveInventoryNode nodeF = createChildNode("F", nodeE, "f2"); + ActiveInventoryNode nodeG = createChildNode("G", nodeF, "g2"); + + return nodeA; + } + + /** + * Do test 1. + */ + public void doTest1() { + + ActiveInventoryNode one = buildTree1(); + ActiveInventoryNode two = buildTree2(); + + one.dumpNodeTree(true); + System.out.println("---"); + two.dumpNodeTree(true); + + System.out.println("---"); + // one.merge(two); + one.dumpNodeTree(true); + + } + + /** + * Do test 2. + * + * @param showProps the show props + */ + public void doTest2(boolean showProps) { + + VisualizationConfig.getConfig().setVisualizationDebugEnabled(false); + + ActiveInventoryNode one = buildTree3(); + ActiveInventoryNode two = buildTree4(); + + System.out.println(one.dumpNodeTree(showProps)); + System.out.println("---"); + System.out.println(two.dumpNodeTree(showProps)); + + System.out.println("---"); + // MergeResult mr = one.merge(two); + // System.out.println("merge result = " + mr.name()); + System.out.println(one.dumpNodeTree(showProps)); + + } + + public static String DIRECT_COMPLEX_SELF_LINK_JSON_RESPONSE = + "{\"complex\":{\"physical-location-id\":\"MJ-1604-COMPLEX\",\"data-center-code\":\"DAYTONNJ\",\"complex-name\":\"complex-name-MDTWNJ23A4\",\"resource-version\":\"1470195143\",\"physical-location-type\":\"SBC/VHO and Mega Pop\",\"street1\":\"451 Western Ave\",\"street2\":\"CU-212\",\"city\":\"dayton\",\"state\":\"NJ\",\"postal-code\":\"08852\",\"country\":\"USA\",\"region\":\"Northeast\",\"latitude\":\"40.3896\",\"longitude\":\"-74.5463\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"pserver\",\"related-link\":\"https://aai-int1.test.att.com:8443/aai/v8/cloud-infrastructure/pservers/pserver/MJ-1604-PSERVER/\",\"relationship-data\":[{\"relationship-key\":\"pserver.hostname\",\"relationship-value\":\"MJ-1604-PSERVER\"}],\"related-to-property\":[{\"property-key\":\"pserver.pserver-name2\",\"property-value\":\"MJ-1604-PSERVER\"}]}]}}}"; + public static String DIRECT_PSERVER_SELF_LINK_JSON_RESPONSE = + "{\"pserver\":{\"hostname\":\"MJ-1604-PSERVER\",\"equip-type\":\"JUNIPER UCPE\",\"equip-vendor\":\"JUNIPER\",\"equip-model\":\"QFX5100-24P-AA\",\"ipv4-oam-address\":\"10.402.143.1\",\"serial-number\":\"VX371521MAHI\",\"pserver-id\":\"1C2B8D47-AVAE-4721-0110-E2C41A07MAHI\",\"in-maint\":false,\"resource-version\":\"1456765026\",\"pserver-name2\":\"MJ-1604-PSERVER\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"complex\",\"related-link\":\"https://aai-int1.test.att.com:8443/aai/v8/cloud-infrastructure/complexes/complex/MJ-1604-COMPLEX/\",\"relationship-data\":[{\"relationship-key\":\"complex.physical-location-id\",\"relationship-value\":\"MJ-1604-COMPLEX\"}]}]},\"p-interfaces\":{\"p-interface\":[{\"interface-name\":\"ge-0/2/0\",\"speed-value\":\"1\",\"speed-units\":\"GBPS\",\"resource-version\":\"1456723241\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"physical-link\",\"related-link\":\"https://aai-int1.test.att.com:8443/aai/v8/network/physical-links/physical-link/BBEC.112430..ATI/\",\"relationship-data\":[{\"relationship-key\":\"physical-link.link-name\",\"relationship-value\":\"BBEC.112430..ATI\"}]}]}},{\"interface-name\":\"ge-0/2/1\",\"speed-value\":\"1\",\"speed-units\":\"GBPS\",\"resource-version\":\"1456723241\",\"relationship-list\":{\"relationship\":[{\"related-to\":\"physical-link\",\"related-link\":\"https://aai-int1.test.att.com:8443/aai/v8/network/physical-links/physical-link/BBEC.112431..ATI/\",\"relationship-data\":[{\"relationship-key\":\"physical-link.link-name\",\"relationship-value\":\"BBEC.112431..ATI\"}]}]}}]}}}"; + + /** + * Parses the direct self link json response. + * + * @param selfLinkJsonResponse the self link json response + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + public void parseDirectSelfLinkJsonResponse(String selfLinkJsonResponse) + throws JsonProcessingException, IOException { + + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(Include.NON_EMPTY); + mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy()); + + + // try { + JsonNode jsonNodeArray = mapper.readTree(selfLinkJsonResponse); + + Iterator iterator = jsonNodeArray.fieldNames(); + JsonNode entityNode = null; + String entityTypeStr = null; + String entityNodeFieldName = null; + + while (iterator.hasNext()) { + entityTypeStr = iterator.next(); + entityNode = jsonNodeArray.get(entityTypeStr); + + Iterator entityNodeFields = entityNode.fieldNames(); + + while (entityNodeFields.hasNext()) { + entityNodeFieldName = entityNodeFields.next(); + System.out.println(String.format("%s.%s", entityTypeStr, entityNodeFieldName)); + } + } + + /* + * Iterator> fieldNames = jsonNode.fields(); Entry + * field = null; List entitiesToFilter = null; + */ + + /* + * try { entitiesToFilter = + * ActiveInventoryConfig.getConfig().getAaiRestConfig().getFilteredEntities(); } catch ( + * Exception e ) { LOG.error( + * "Caught an exception while retrieving filtered entities. Error Cause = " + + * e.getLocalizedMessage());; return; } + */ + + /* + * JsonNode entityNode = jsonNode. + * + * /*String entityType = entityNode.textValue(); fieldNames = entityNode.fields(); + * + * while ( fieldNames.hasNext() ) { + * + * field = fieldNames.next(); + * + * /* Is there a way to tell if the field is an aggregate or an atomic value? This is where our + * flattening code needs to live + */ + + /* + * String fieldName = field.getKey(); + * + * System.out.println( + * "processDirectSelfLinkResponse(), fieldName for current node with entityType = " + entityType + * + " and field name " + fieldName); + * + * + * /*if ( "relationship-list".equals( fieldName ) ) { + * + * /* Parse the relationship list like we were doing before, or at least navigate it so we can + * extract the relationship data + */ + + /* + * cloud-region is the only exception to this rule where we don't want to collect the + * relationship data from the self-link (for now). + */ + + /* + * if ( !entitiesToFilter.contains(entityType) ) { + * + * // if the current depth >= maxTraversal depth, stop analyzing relationships RelationshipList + * relationships = null; + * + * /* At each level we traverse, we want the properties + relationship-list, until we reach the + * max traversal depth, then we only the properties, and we want to ignore the relationship-list + * to avoid excessive traversal. + */ + + /* + * if ( linkDepth < VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()) { + * relationships = analyzeSelfLinkRelationshipList(field.getValue().toString()); + * addSelfLinkRelationshipChildren( relationships, linkDepth ); } else { LOG.warn( + * "Ignoring relationship-list for entity = " + entityType + " at traversal depth = " + + * linkDepth); } + * + * } else { LOG.warn(String.format( + * "Ignoring relationship-list attribute for '%s' based on configuration", entityType)); } + * + * } else { + * + * JsonNode nodeValue = field.getValue(); + * + * if ( nodeValue.isValueNode() ) { + * + * // current behavior, but we need to discover how to translate groups into flattened text by + * using the Jackson JsonNode API addProperty(fieldName, nodeValue.asText()); } else { // need + * special handling for collections + * + * if ( LOG.isDebugEnabled()) { LOG.debug("Complex field discovered = " + fieldName); } + * + * Iterator childFields = nodeValue.fieldNames(); StringBuilder sb = new + * StringBuilder(128); + * + * while ( childFields.hasNext() ) { String f= childFields.next(); + * + * if ( LOG.isDebugEnabled()) { LOG.debug("found field = " + f + " for parent field = " + + * fieldName); } sb.append(fieldName + "=" + nodeValue.get(f).asText()); } + * + * addProperty(fieldName, sb.toString()); + * + * } + * + * } + */ + + /* + * Conscious choice to not log the filtered out resources because it would dump on every node. + * We can always re-visit that choice and put a debug log here if need to / want to. + */ + + /* + * } + * + * + * } catch (IOException exc) { + * + * System.out.println("Argh an io exception occurred with message = " + + * e.getLocalizedMessage()); + * + * /*LOG.error("An error occurred while converting JSON into POJO = " + + * e.getLocalizedMessage()); + * + * this.setProcessingErrorOccurred(true); this.addErrorCause( + * "An error occurred while converting JSON into POJO = " + e.getLocalizedMessage()); + */ + // } + + } + + + + /** + * The main method. + * + * @param args the arguments + * @throws JsonProcessingException the json processing exception + * @throws IOException Signals that an I/O exception has occurred. + */ + public static void main(String[] args) throws JsonProcessingException, IOException { + + System.getProperties().setProperty("AJSC_HOME", "d:\\3\\"); + ActiveInventoryNodeTester tester = new ActiveInventoryNodeTester(); + // tester.doTest2(true); + + tester.parseDirectSelfLinkJsonResponse(DIRECT_COMPLEX_SELF_LINK_JSON_RESPONSE); + System.out.println("---"); + tester.parseDirectSelfLinkJsonResponse(DIRECT_PSERVER_SELF_LINK_JSON_RESPONSE); + + + + } + +} diff --git a/src/test/java/org/openecomp/sparky/viewandinspect/SearchAdapterTest.java b/src/test/java/org/openecomp/sparky/viewandinspect/SearchAdapterTest.java new file mode 100644 index 0000000..f9605e8 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/viewandinspect/SearchAdapterTest.java @@ -0,0 +1,90 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.viewandinspect; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; + +import org.junit.Before; +import org.junit.Test; +import org.openecomp.sparky.dal.elasticsearch.SearchAdapter; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.rest.RestClientBuilder; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; + + +/** + * The Class SearchAdapterTest. + */ +public class SearchAdapterTest { + + private RestClientBuilder clientBuilderMock; + private Client mockClient; + private ClientResponse mockClientResponse; + private WebResource mockWebResource; + private Builder mockBuilder; + + + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + + /* + * common collaborator mocking setup + */ + + clientBuilderMock = mock(RestClientBuilder.class); + mockClient = mock(Client.class); + mockClientResponse = mock(ClientResponse.class); + mockWebResource = mock(WebResource.class); + mockBuilder = mock(Builder.class); + + doReturn(mockClient).when(clientBuilderMock).getClient(); + doReturn(mockWebResource).when(mockClient).resource(anyString()); + doReturn(mockBuilder).when(mockWebResource).accept(anyString()); + doReturn(mockBuilder).when(mockBuilder).header(anyString(), anyObject()); + + doReturn(mockClientResponse).when(mockBuilder).get(same(ClientResponse.class)); + doReturn(mockClientResponse).when(mockBuilder).put(same(ClientResponse.class), anyObject()); + doReturn(mockClientResponse).when(mockBuilder).post(same(ClientResponse.class), anyObject()); + doReturn(mockClientResponse).when(mockBuilder).delete(same(ClientResponse.class)); + } + +} diff --git a/src/test/java/org/openecomp/sparky/viewandinspect/SearchResponseTest.java b/src/test/java/org/openecomp/sparky/viewandinspect/SearchResponseTest.java new file mode 100644 index 0000000..41db58c --- /dev/null +++ b/src/test/java/org/openecomp/sparky/viewandinspect/SearchResponseTest.java @@ -0,0 +1,92 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.viewandinspect; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openecomp.sparky.viewandinspect.entity.EntityEntry; +import org.openecomp.sparky.viewandinspect.entity.SearchResponse; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * The Class SearchResponseTest. + */ +@RunWith(PowerMockRunner.class) +public class SearchResponseTest { + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception {} + + /** + * Validate basic construction. + */ + @Test + public void validateBasicConstruction() { + + SearchResponse response = new SearchResponse(); + + //response.setNumReturned(1); + response.setProcessingTimeInMs(512); + //response.setTotalFound(50); + + List entities = new ArrayList(); + //response.setEntities(entities); + + EntityEntry e1 = new EntityEntry(); + e1.setEntityPrimaryKeyValue("e1"); + e1.setEntityType("e1"); + e1.setSearchTags("e1"); + + //response.addEntityEntry(e1); + + EntityEntry e2 = new EntityEntry(); + + e2.setEntityPrimaryKeyValue("e2"); + e2.setEntityType("e2"); + e2.setSearchTags("e2"); + + //response.addEntityEntry(e2); + + //assertEquals(1, response.getNumReturned()); + //assertEquals(512, response.getProcessingTimeInMs()); + //assertEquals(50, response.getTotalFound()); + + //List responseEntities = response.getEntities(); + + //assertEquals(2, responseEntities.size()); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/viewandinspect/SearchServletTest.java b/src/test/java/org/openecomp/sparky/viewandinspect/SearchServletTest.java new file mode 100644 index 0000000..9e14735 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/viewandinspect/SearchServletTest.java @@ -0,0 +1,786 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (inventory UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.viewandinspect; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyString; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.openecomp.sparky.config.oxm.OxmEntityDescriptor; +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.dal.elasticsearch.SearchAdapter; +import org.openecomp.sparky.dal.elasticsearch.entity.AutoSuggestDocumentEntity; +import org.openecomp.sparky.dal.elasticsearch.entity.AutoSuggestDocumentEntityFields; +import org.openecomp.sparky.dal.elasticsearch.entity.AutoSuggestElasticHitEntity; +import org.openecomp.sparky.dal.elasticsearch.entity.AutoSuggestElasticHitsEntity; +import org.openecomp.sparky.dal.elasticsearch.entity.AutoSuggestElasticSearchResponse; +import org.openecomp.sparky.dal.elasticsearch.entity.BucketEntity; +import org.openecomp.sparky.dal.elasticsearch.entity.ElasticHitsEntity; +import org.openecomp.sparky.dal.elasticsearch.entity.ElasticSearchAggegrationResponse; +import org.openecomp.sparky.dal.elasticsearch.entity.ElasticSearchAggregation; +import org.openecomp.sparky.dal.elasticsearch.entity.ElasticSearchCountResponse; +import org.openecomp.sparky.dal.elasticsearch.entity.PayloadEntity; +import org.openecomp.sparky.dal.rest.OperationResult; +import org.openecomp.sparky.dal.sas.config.SearchServiceConfig; +import org.openecomp.sparky.dal.sas.entity.EntityCountResponse; +import org.openecomp.sparky.dal.sas.entity.GroupByAggregationResponseEntity; +import org.openecomp.sparky.dal.sas.entity.SearchAbstractionEntityBuilder; +import org.openecomp.sparky.search.VnfSearchService; +import org.openecomp.sparky.search.config.SuggestionConfig; +import org.openecomp.sparky.suggestivesearch.SuggestionEntity; +import org.openecomp.sparky.util.ExceptionHelper; +import org.openecomp.sparky.util.HttpServletHelper; +import org.openecomp.sparky.util.NodeUtils; +import org.openecomp.sparky.viewandinspect.entity.QuerySearchEntity; +import org.openecomp.sparky.viewandinspect.entity.SearchResponse; +import org.openecomp.sparky.viewandinspect.services.SearchServiceWrapper; +import org.openecomp.sparky.viewandinspect.servlet.SearchServlet; +import org.slf4j.MDC; + +import org.openecomp.cl.mdc.MdcContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.net.MediaType; + + +/** + * The Class SearchServletTest. + */ + +public class SearchServletTest { + + private static final String VNF_ROUTE = "vnf"; + private static final String VIEW_INSPECT_ROUTE = "viewInspect"; + + private HttpServletRequest commonRequest = null; + private HttpServletResponse commonResponse = null; + private PrintWriter printWriter = null; + private StringWriter responseStringWriter = null; + private SearchServiceWrapper searchWrapper = null; + private SearchAdapter searchAdapter = null; + private VnfSearchService vnfSearchService = null; + private ObjectMapper mapper = null; + private SecureRandom rand = null; + private OxmModelLoader loader; + private Map descriptors = null; + private SuggestionConfig suggestionConfig = null; + private SearchServiceConfig esConfig = null; + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception { + + commonRequest = HttpServletHelper.getMockHttpServletRequest(); + responseStringWriter = new StringWriter(); + printWriter = new PrintWriter(responseStringWriter); + commonResponse = HttpServletHelper.getMockHttpServletResponse(printWriter); + mapper = new ObjectMapper(); + + // permit serialization of objects with no members + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + + rand = new SecureRandom(); + + loader = Mockito.mock(OxmModelLoader.class); + descriptors = new HashMap(); + + esConfig = new SearchServiceConfig(); + suggestionConfig = SuggestionConfig.getConfig(); + + // Use SearchServiceWrapper and VnfSearchService for suggestionConfig + Map svcs = new HashMap(); + svcs.put("autosuggestIndexname", "SearchServiceWrapper"); + svcs.put("indexName", "VnfSearchService"); + suggestionConfig.setSearchIndexToSearchService(svcs); + + esConfig.setIndexName("esi-localhost"); + esConfig.setType("default"); + + searchAdapter = Mockito.mock(SearchAdapter.class); + vnfSearchService = Mockito.mock(VnfSearchService.class); + + initializeEntityDescriptors(); + + searchWrapper = new SearchServiceWrapper(); + searchWrapper.setSasConfig(esConfig); + searchWrapper.setSearch(searchAdapter); + searchWrapper.setVnfSearch(vnfSearchService); + searchWrapper.setSuggestionConfig(suggestionConfig); + searchWrapper.setOxmModelLoader(loader); + } + + @Test + public void validateAccessors() { + assertNotNull("Vnf Search Service should not be null", searchWrapper.getVnfSearch()); + } + + @Test + public void validateInitializer() { + + try { + assertNotNull("Oxm Model loader should not be null", searchWrapper.getOxmModelLoader()); + assertNotNull("SearchAbstractionConfig should not be null", searchWrapper.getSasConfig()); + assertNotNull("SearchAdapter should not be null", searchWrapper.getSearch()); + assertNotNull("Suggestion Config should not be null", searchWrapper.getSuggestionConfig()); + assertNotNull("VnfSearchService should not be null", searchWrapper.getVnfSearch()); + + searchWrapper.setOxmModelLoader(null); + searchWrapper.setSasConfig(null); + searchWrapper.setSearch(null); + searchWrapper.setSuggestionConfig(null); + searchWrapper.setVnfSearch(null); + + assertNull("Oxm Model loader should be null", searchWrapper.getOxmModelLoader()); + assertNull("SearchAbstractionConfig should be null", searchWrapper.getSasConfig()); + assertNull("SearchAdapter should be null", searchWrapper.getSearch()); + assertNull("Suggestion Config should be null", searchWrapper.getSuggestionConfig()); + assertNull("VnfSearchService should be null", searchWrapper.getVnfSearch()); + + } catch (Exception exc) { + fail("Servlet Initialization Failed with error = " + exc.getMessage()); + } + + } + + /** + * Test doGet() and doPost() for a non-existent end-point. A test objective would be + * to either return a 404 Not Found. + */ + @Test + public void validateMdcContextLoggingVariablesWhenExplicitlySet() { + + final String transactionId = "1234"; + final String serviceName = "AAI_UI"; + final String partnerName = "SparkyApp"; + + HttpServletHelper.assignRequestHeader(commonRequest, "X-TransactionId", transactionId); + HttpServletHelper.assignRequestHeader(commonRequest, "X-FromAppId", partnerName); + + HttpServletHelper.assignRequestUri(commonRequest, "search/this/path/does/not/exist/"); + + try { + + /* + * Testing the doGet() operation will hit the doPost() operation in the servlet as well + */ + + OperationResult result = doEvaluationTestMDC(true, commonRequest, commonResponse); + + assertEquals(transactionId,MDC.get(MdcContext.MDC_REQUEST_ID)); + assertEquals(serviceName,MDC.get(MdcContext.MDC_SERVICE_NAME)); + assertEquals(partnerName,MDC.get(MdcContext.MDC_PARTNER_NAME)); + + } catch (Exception exc) { + exc.printStackTrace(); + fail("Unexpected exception = " + exc.getLocalizedMessage()); + } + + } + + /** + * Test doGet() and doPost() for a non-existent end-point. A test objective would be + * to either return a 404 Not Found. + */ + @Test + public void validateMdcContextLoggingVariablesWhenNotExplicitlySet() { + + /*final String transactionId = "1234"; + final String serviceName = "AAI-UI"; + final String partnerName = "SparkyApp"; + + HttpServletHelper.assignRequestHeader(commonRequest, "X-TransactionId", transactionId); + HttpServletHelper.assignRequestHeader(commonRequest, "X-FromAppId", serviceName);*/ + + HttpServletHelper.assignRequestUri(commonRequest, "search/this/path/does/not/exist/"); + + try { + + /* + * Testing the doGet() operation will hit the doPost() operation in the servlet as well + */ + + OperationResult result = doEvaluationTestMDC(true, commonRequest, commonResponse); + + assertNotNull(MDC.get(MdcContext.MDC_REQUEST_ID)); + assertNotNull(MDC.get(MdcContext.MDC_SERVICE_NAME)); + assertNotNull(MDC.get(MdcContext.MDC_PARTNER_NAME)); + + } catch (Exception exc) { + exc.printStackTrace(); + fail("Unexpected exception = " + exc.getLocalizedMessage()); + } + + } + + + + /** + * Test doGet() and doPost() for a non-existent end-point. + */ + @Test + public void validateViewAndInspectSearchError_invalidRequestUri() { + + HttpServletHelper.assignRequestUri(commonRequest, "search/this/path/does/not/exist/"); + + try { + + /* + * Testing the doGet() operation will hit the doPost() operation in the servlet as well + */ + + OperationResult result = doEvaluation(true, commonRequest, commonResponse); + assertEquals(404, result.getResultCode()); + assertTrue(result.getResult().contains("Ignored request-uri")); + + } catch (Exception exc) { + exc.printStackTrace(); + fail("Unexpected exception = " + exc.getLocalizedMessage()); + } + + } + + + /** + * Test doGet() and doPost() for Unified Query Search success path + */ + @Test + public void validateQuerySearch_successPath() { + + try { + + QuerySearchEntity searchEntity = new QuerySearchEntity(); + searchEntity.setMaxResults("10"); + searchEntity.setQueryStr("the quick brown fox"); + + HttpServletHelper.assignRequestUri(commonRequest, "search/querysearch"); + HttpServletHelper.setRequestPayload(commonRequest, MediaType.JSON_UTF_8.toString(), + NodeUtils.convertObjectToJson(searchEntity, false)); + + + // set search-abstraction-response that we expect to get back from real system, but stubbed through a mock + // to fulfill collaborator behavior + + OperationResult mockedEntitySearchResponse = new OperationResult(); + mockedEntitySearchResponse.setResultCode(200); + mockedEntitySearchResponse.setResult(NodeUtils.convertObjectToJson( + SearchAbstractionEntityBuilder.getSuccessfulEntitySearchResponse(), false)); + + // TODO: make parameters expect certain values to lock in invocation attempt against a specific input sequence + Mockito.when(searchAdapter.doPost(anyString(), anyString(), anyString())) + .thenReturn(mockedEntitySearchResponse); + + List autoSuggestions = new ArrayList(); + + autoSuggestions.add(new SuggestionEntity("vnf","1234", "VNFs")); + autoSuggestions.add(new SuggestionEntity("vnf","1111", "Created VNFs")); + autoSuggestions.add(new SuggestionEntity("vnf","1122", "ACTIVE VNFs")); + autoSuggestions.add(new SuggestionEntity("vnf","2233", "ACTIVE and Error VNFs")); + autoSuggestions.add(new SuggestionEntity("vnf","3344", "ACTIVE and NOT ORCHESTRATED VNFs")); + autoSuggestions.add(new SuggestionEntity("vnf","4455", "ACTIVE and Running VNFs")); + autoSuggestions.add(new SuggestionEntity("vnf","5566", "Activated VNFs")); + autoSuggestions.add(new SuggestionEntity("vnf","6677", "CAPPED VNFs")); + autoSuggestions.add(new SuggestionEntity("vnf","7788", "CAPPED and Created VNFs")); + + Mockito.when(vnfSearchService.getSuggestionsResults(Mockito.anyObject(), Mockito.anyInt())) + .thenReturn(autoSuggestions); + + /* + * Testing the doGet() operation will hit the doPost() operation in the servlet as well + */ + + OperationResult result = doEvaluation(true, commonRequest, commonResponse); + + + assertEquals(200, result.getResultCode()); + + SearchResponse searchResponse = mapper.readValue(result.getResult(), SearchResponse.class); + + assertEquals(10, searchResponse.getTotalFound()); + + int numVnf = 0; + int numViewInspect = 0; + + for ( SuggestionEntity suggestion : searchResponse.getSuggestions()) { + + if ( VNF_ROUTE.equals(suggestion.getRoute())) { + numVnf++; + } else if ( VIEW_INSPECT_ROUTE.equals(suggestion.getRoute())) { + numViewInspect++; + } + } + + assertEquals(5, numVnf); + assertEquals(5, numViewInspect); + + //assertTrue(result.getResult().contains("Ignored request-uri")); + + } catch (Exception exc) { + fail("Unexpected exception = " + exc.getLocalizedMessage()); + } + + } + + /** + * Test doGet() and doPost() for Unified Query Search success path + */ + @Test + public void validateSummaryByEntityTypeCount_successPath() { + + try { + + HttpServletHelper.assignRequestUri(commonRequest, "search/summarybyentitytype/count"); + + Map payloadFields = new HashMap(); + payloadFields.put("hashId", "662d1b57c31df70d7ef57ec53c0ace81578ec77b6bc5de055a57c7547ec122dd"); + payloadFields.put("groupby", "orchestration-status"); + + + HttpServletHelper.setRequestPayload(commonRequest, MediaType.JSON_UTF_8.toString(), NodeUtils.convertObjectToJson(payloadFields, false)); + + + /* + * In this test we don't want to mock the vnf search service, only it's collaborator + * interactions with a REST endpoint. + */ + vnfSearchService = new VnfSearchService(); + vnfSearchService.setSearch(searchAdapter); + searchWrapper.setVnfSearch(vnfSearchService); + + /* + * The first network response to mock is the one to elastic search to get the suggestion entity by hash id + * + * http://localhost:9200/entityautosuggestindex-localhost/_search + * {"query":{"term":{"_id":"2172a3c25ae56e4995038ffbc1f055692bfc76c0b8ceda1205bc745a9f7a805d"}}} + */ + + AutoSuggestElasticSearchResponse elasticResponse = new AutoSuggestElasticSearchResponse(); + + elasticResponse.setTook(1); + + elasticResponse.setTimedOut(false); + elasticResponse.addShard("total", "5"); + elasticResponse.addShard("successful", "5"); + elasticResponse.addShard("failed", "0"); + + AutoSuggestElasticHitEntity elasticHit = new AutoSuggestElasticHitEntity(); + elasticHit.setIndex("entityautosuggestindex-localhost"); + elasticHit.setType("default"); + elasticHit.setId("2172a3c25ae56e4995038ffbc1f055692bfc76c0b8ceda1205bc745a9f7a805d"); + elasticHit.setScore("1"); + + AutoSuggestDocumentEntityFields suggestDocFields = new AutoSuggestDocumentEntityFields(); + suggestDocFields.addInput("VNFs"); + suggestDocFields.addInput("generic-vnfs"); + suggestDocFields.setOutput("VNFs"); + suggestDocFields.setPayload(new PayloadEntity()); + suggestDocFields.setWeight(100); + + AutoSuggestDocumentEntity autoSuggestDoc = new AutoSuggestDocumentEntity(); + autoSuggestDoc.setFields(suggestDocFields); + + elasticHit.setSource(autoSuggestDoc); + + AutoSuggestElasticHitsEntity hits = new AutoSuggestElasticHitsEntity(); + hits.addHit(elasticHit); + + elasticResponse.setHits(hits); + + + OperationResult mockedSearchResponse = new OperationResult(); + mockedSearchResponse.setResultCode(200); + + mockedSearchResponse.setResult(NodeUtils.convertObjectToJson(elasticResponse, false)); + + + /* + * The second response is the count API dip to elastic search + */ + + ElasticSearchCountResponse countResponse = new ElasticSearchCountResponse(); + countResponse.setCount(3170); + countResponse.addShard("total", "5"); + countResponse.addShard("successful", "5"); + countResponse.addShard("failed", "0"); + + OperationResult searchResponseForCount = new OperationResult(); + searchResponseForCount.setResultCode(200); + + searchResponseForCount.setResult(NodeUtils.convertObjectToJson(countResponse, false)); + + // TODO: make parameters expect certain values to lock in invocation attempt against a specific input sequence + Mockito.when(searchAdapter.doPost(anyString(), anyString(), anyString())) + .thenReturn(mockedSearchResponse).thenReturn(searchResponseForCount); + + + /* + * Testing the doGet() operation will hit the doPost() operation in the servlet as well + */ + + OperationResult result = doEvaluation(true, commonRequest, commonResponse); + + + assertEquals(200, result.getResultCode()); + + // + //{"shards":{"total":"5","failed":"0","successful":"5"},"count":3170} + + EntityCountResponse entityCountResponse = mapper.readValue(result.getResult(), EntityCountResponse.class); + + assertEquals(3170, entityCountResponse.getCount()); + + } catch (Exception exc) { + fail("Unexpected exception = " + exc.getLocalizedMessage()); + } + + } + + + /** + * Test doGet() and doPost() for Unified Query Search success path + */ + @Test + public void validateSummaryByEntityType_successPath() { + + try { + + HttpServletHelper.assignRequestUri(commonRequest, "search/summarybyentitytype"); + + Map payloadFields = new HashMap(); + payloadFields.put("hashId", "662d1b57c31df70d7ef57ec53c0ace81578ec77b6bc5de055a57c7547ec122dd"); + payloadFields.put("groupby", "orchestration-status"); + + HttpServletHelper.setRequestPayload(commonRequest, MediaType.JSON_UTF_8.toString(), NodeUtils.convertObjectToJson(payloadFields, false)); + + /* + * In this test we don't want to mock the vnf search service, only it's collaborator + * interactions with a REST endpoint. + */ + vnfSearchService = new VnfSearchService(); + vnfSearchService.setSearch(searchAdapter); + searchWrapper.setVnfSearch(vnfSearchService); + + /* + * The first network response to mock is the one to elastic search to get the suggestion entity by hash id + * + * http://localhost:9200/entityautosuggestindex-localhost/_search + * {"query":{"term":{"_id":"2172a3c25ae56e4995038ffbc1f055692bfc76c0b8ceda1205bc745a9f7a805d"}}} + */ + + AutoSuggestElasticSearchResponse elasticResponse = new AutoSuggestElasticSearchResponse(); + + elasticResponse.setTook(1); + + elasticResponse.setTimedOut(false); + elasticResponse.addShard("total", "5"); + elasticResponse.addShard("successful", "5"); + elasticResponse.addShard("failed", "0"); + + AutoSuggestElasticHitEntity elasticHit = new AutoSuggestElasticHitEntity(); + elasticHit.setIndex("entityautosuggestindex-localhost"); + elasticHit.setType("default"); + elasticHit.setId("2172a3c25ae56e4995038ffbc1f055692bfc76c0b8ceda1205bc745a9f7a805d"); + elasticHit.setScore("1"); + + AutoSuggestDocumentEntityFields suggestDocFields = new AutoSuggestDocumentEntityFields(); + suggestDocFields.addInput("VNFs"); + suggestDocFields.addInput("generic-vnfs"); + suggestDocFields.setOutput("VNFs"); + suggestDocFields.setPayload(new PayloadEntity()); + suggestDocFields.setWeight(100); + + AutoSuggestDocumentEntity autoSuggestDoc = new AutoSuggestDocumentEntity(); + autoSuggestDoc.setFields(suggestDocFields); + + elasticHit.setSource(autoSuggestDoc); + + AutoSuggestElasticHitsEntity hits = new AutoSuggestElasticHitsEntity(); + hits.addHit(elasticHit); + + elasticResponse.setHits(hits); + + + OperationResult mockedSearchResponse = new OperationResult(); + mockedSearchResponse.setResultCode(200); + + mockedSearchResponse.setResult(NodeUtils.convertObjectToJson(elasticResponse, false)); + + + /* + * The second response is the aggregation API dip to elastic search + */ + + ElasticSearchAggegrationResponse aggResponse = new ElasticSearchAggegrationResponse(); + + aggResponse.setTook(20); + aggResponse.setTimedOut(false); + + aggResponse.addShard("total","5"); + aggResponse.addShard("successful","5"); + aggResponse.addShard("failed","0"); + + ElasticHitsEntity hitsEntity = new ElasticHitsEntity(); + + hitsEntity.setTotal(3170); + hitsEntity.setMaxScore(0); + + aggResponse.setHits(hitsEntity); + + ElasticSearchAggregation defaultAggregation = new ElasticSearchAggregation(); + + defaultAggregation.setDocCountErrorUpperBound(0); + defaultAggregation.setSumOtherDocCount(0); + defaultAggregation.addBucket(new BucketEntity("created",1876)); + defaultAggregation.addBucket(new BucketEntity("Created",649)); + defaultAggregation.addBucket(new BucketEntity("Activated",158)); + defaultAggregation.addBucket(new BucketEntity("active",59)); + defaultAggregation.addBucket(new BucketEntity("NOT ORCHESTRATED",42)); + defaultAggregation.addBucket(new BucketEntity("Pending-Create",10)); + defaultAggregation.addBucket(new BucketEntity("Running",9)); + defaultAggregation.addBucket(new BucketEntity("Configured",7)); + defaultAggregation.addBucket(new BucketEntity("pending-create",7)); + defaultAggregation.addBucket(new BucketEntity("Error",3)); + defaultAggregation.addBucket(new BucketEntity("planned",3)); + defaultAggregation.addBucket(new BucketEntity("PLANNED",2)); + defaultAggregation.addBucket(new BucketEntity("ERROR",1)); + defaultAggregation.addBucket(new BucketEntity("RUNNING",1)); + defaultAggregation.addBucket(new BucketEntity("example-orchestration-status-val-6176",1)); + + aggResponse.addAggregation("default", defaultAggregation); + + OperationResult searchResponseForAggregation = new OperationResult(); + searchResponseForAggregation.setResultCode(200); + + searchResponseForAggregation.setResult(NodeUtils.convertObjectToJson(aggResponse, false)); + + // TODO: make parameters expect certain values to lock in invocation attempt against a specific input sequence + Mockito.when(searchAdapter.doPost(anyString(), anyString(), anyString())) + .thenReturn(mockedSearchResponse).thenReturn(searchResponseForAggregation); + + + /* + * Testing the doGet() operation will hit the doPost() operation in the servlet as well + */ + + OperationResult result = doEvaluation(true, commonRequest, commonResponse); + + + assertEquals(200, result.getResultCode()); + + // + //{"shards":{"total":"5","failed":"0","successful":"5"},"count":3170} + + GroupByAggregationResponseEntity groupByResponse = mapper.readValue(result.getResult(), GroupByAggregationResponseEntity.class); + + assertEquals(2828, groupByResponse.getAggEntity().getTotalChartHits()); + assertEquals(15, groupByResponse.getAggEntity().getBuckets().size()); + + } catch (Exception exc) { + fail("Unexpected exception = " + exc.getLocalizedMessage()); + } + + } + + + + /** + * Builds the resource entity descriptor. + * + * @param entityType the entity type + * @param attributeNames the attribute names + * @param searchableAttributes the searchable attributes + * @return the oxm entity descriptor + */ + @SuppressWarnings("unchecked") + private OxmEntityDescriptor buildResourceEntityDescriptor(String entityType, + String attributeNames, String searchableAttributes) { + OxmEntityDescriptor descriptor = new OxmEntityDescriptor(); + descriptor.setEntityName(entityType); + + if (attributeNames != null) { + descriptor.setPrimaryKeyAttributeName(Arrays.asList(attributeNames.split(","))); + } + + if (searchableAttributes != null) { + descriptor.setSearchableAttributes(Arrays.asList(searchableAttributes.split(","))); + } + + return descriptor; + } + + /** + * Initialize entity descriptors. + */ + private void initializeEntityDescriptors() { + descriptors.put("customer", + buildResourceEntityDescriptor("customer", "service-instance-id", "f1,f2,f3")); + } + + /** + * Builds the view and inspect search request. + * + * @param maxResults the max results + * @param queryStr the query str + * @return the string + * @throws JsonProcessingException the json processing exception + */ + public String buildViewAndInspectSearchRequest(Integer maxResults, String queryStr) + throws JsonProcessingException { + + /* + * { "maxResults" : "10", "searchStr" : "" } + */ + + ObjectNode rootNode = mapper.createObjectNode(); + + if (maxResults != null) { + rootNode.put("maxResults", maxResults); + } + + if (queryStr != null) { + rootNode.put("queryStr", queryStr); + } + + return NodeUtils.convertObjectToJson(rootNode, true); + + } + + + /** + * Do evaluation. + * + * @param doGet the do get + * @param req the req + * @param res the res + * @return the string + */ + private OperationResult doEvaluationTestMDC(boolean doGet, HttpServletRequest req, HttpServletResponse res) { + + /* + * Test method invocation + */ + + SearchServlet searchServlet = new SearchServlet(); + try { + searchServlet.init(); + } catch (ServletException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + ArgumentCaptor responseCodeCaptor = ArgumentCaptor.forClass(Integer.class); + + try { + if (doGet) { + searchServlet.doGet(req, res); + } else { + searchServlet.doPost(req, res); + } + } catch (ServletException exc) { + fail(ExceptionHelper.extractStackTraceElements(5, exc)); + } catch (IOException exc) { + fail(ExceptionHelper.extractStackTraceElements(5, exc)); + } + + responseStringWriter.flush(); + Mockito.verify(commonResponse, Mockito.atLeast(1)).setStatus(responseCodeCaptor.capture()); + + OperationResult result = new OperationResult(); + + result.setResultCode(responseCodeCaptor.getValue()); + result.setResult(responseStringWriter.toString()); + + return result; + + } + + /** + * Do evaluation. + * + * @param doGet the do get + * @param req the req + * @param res the res + * @return the string + */ + private OperationResult doEvaluation(boolean doGet, HttpServletRequest req, HttpServletResponse res) { + + /* + * Test method invocation + */ + ArgumentCaptor responseCodeCaptor = ArgumentCaptor.forClass(Integer.class); + + try { + if (doGet) { + searchWrapper.doGet(req, res); + } else { + searchWrapper.doPost(req, res); + } + } catch (ServletException exc) { + fail(ExceptionHelper.extractStackTraceElements(5, exc)); + } catch (IOException exc) { + fail(ExceptionHelper.extractStackTraceElements(5, exc)); + } + + responseStringWriter.flush(); + Mockito.verify(commonResponse, Mockito.atLeast(1)).setStatus(responseCodeCaptor.capture()); + + OperationResult result = new OperationResult(); + + result.setResultCode(responseCodeCaptor.getValue()); + result.setResult(responseStringWriter.toString()); + + return result; + + } + + + +} diff --git a/src/test/java/org/openecomp/sparky/viewandinspect/SearchableGroupsTest.java b/src/test/java/org/openecomp/sparky/viewandinspect/SearchableGroupsTest.java new file mode 100644 index 0000000..13088ba --- /dev/null +++ b/src/test/java/org/openecomp/sparky/viewandinspect/SearchableGroupsTest.java @@ -0,0 +1,73 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.viewandinspect; + +/** + * The Class SearchableGroupsTest. + */ +public class SearchableGroupsTest { + + static final String TEST_RESOURCE_PATH = "/src/test/resources"; + + static final String GOOD_TEST_CONFIG = "{\"groups\": [" + "{" + "\"group-name\" : \"inventory\"," + + "\"search-paths\" : [\"cloud-infrastructure\", \"business\", \"network\"]" + "}," + "{" + + "\"group-name\" : \"cloud-infrastructure\"," + + "\"search-paths\" : [\"complexes\", \"cloud-regions\", \"pservers\"]" + "}" + "]" + "}"; + /* + * @Before public void init() throws NoSuchFieldException, SecurityException, + * IllegalArgumentException, IllegalAccessException { Field instance = + * SearchableGroups.class.getDeclaredField("instance"); instance.setAccessible(true); + * instance.set(null, null); } + * + * @Test public void test_FileNotFound() throws ElasticSearchOperationException { + * System.setProperty("AJSC_HOME", ""); SearchableGroups testGroups = + * SearchableGroups.getTestInstance(); assertTrue(testGroups.getGroups().isEmpty()); } + * + * @Test public void test_FileFoundWithProperlyFormatedConfig() throws + * ElasticSearchOperationException { ResolverUtils testUtils = + * Mockito.mock(ResolverUtils.class); + * Mockito.when(testUtils.getConfigSettings(anyString())).thenReturn(GOOD_TEST_CONFIG); + * SearchableGroups testGroups = SearchableGroups.getTestInstance(); + * + * testGroups.setUtils(testUtils); testGroups.initSearchableGroups(); + * + * assertFalse(testGroups.getGroups().isEmpty()); + * + * assertFalse(testGroups.getSearchableGroups("inventory").isEmpty()); } + * + * @Test public void test_FileFoundGroupDoesNotExist() throws + * ElasticSearchOperationException { + * ResolverUtils testUtils = Mockito.mock(ResolverUtils.class); + * Mockito.when(testUtils.getConfigSettings(anyString())).thenReturn(GOOD_TEST_CONFIG); + * SearchableGroups testGroups = SearchableGroups.getTestInstance(); + * + * testGroups.setUtils(testUtils); testGroups.initSearchableGroups(); + * + * assertFalse(testGroups.getGroups().isEmpty()); + * + * assertEquals(null, testGroups.getSearchableGroups("Test")); } + */ +} diff --git a/src/test/java/org/openecomp/sparky/viewandinspect/SelfLinkNodeCollectorTester.java b/src/test/java/org/openecomp/sparky/viewandinspect/SelfLinkNodeCollectorTester.java new file mode 100644 index 0000000..fe3a8f8 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/viewandinspect/SelfLinkNodeCollectorTester.java @@ -0,0 +1,69 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.viewandinspect; + +import org.openecomp.sparky.config.oxm.OxmModelLoader; +import org.openecomp.sparky.viewandinspect.entity.ActiveInventoryNode; +import org.openecomp.sparky.viewandinspect.services.VisualizationContext; + +/** + * The Class SelfLinkNodeCollectorTester. + */ +public class SelfLinkNodeCollectorTester { + + /** + * The main method. + * + * @param args the arguments + * @throws Exception the exception + */ + public static void main(String[] args) throws Exception { + // TODO Auto-generated method stub + + System.getProperties().setProperty("AJSC_HOME", "d:\\3\\"); + //VisualizationContext collector = new VisualizationContext(OxmModelLoader.getInstance()); + + /* + * This is a good test of the LinkResolverServer when we are ready + */ + + ActiveInventoryNode ain = new ActiveInventoryNode(); + ain.setSelfLink( + "https://localhost:9292/aai/v7/network/generic-vnfs/generic-vnf/d2f661e7-d6a0-43b5-979f-720803396a70/"); + ain.setEntityType("generic-vnf"); + + /* + * collector.collectSelfLinks(ain, 1, "generic-vnf", + * "generic-vnf.d2f661e7-d6a0-43b5-979f-720803396a70"); + */ + + // collector.shutdown(); + + ain.dumpNodeTree(true); + + } + +} diff --git a/src/test/java/org/openecomp/sparky/viewandinspect/ViewAndInspectSearchRequestTest.java b/src/test/java/org/openecomp/sparky/viewandinspect/ViewAndInspectSearchRequestTest.java new file mode 100644 index 0000000..41ad2c6 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/viewandinspect/ViewAndInspectSearchRequestTest.java @@ -0,0 +1,81 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.viewandinspect; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openecomp.sparky.viewandinspect.entity.QuerySearchEntity; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * The Class ViewAndInspectSearchRequestTest. + */ +@RunWith(PowerMockRunner.class) +public class ViewAndInspectSearchRequestTest { + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception {} + + /** + * Validate basic construction. + */ + @Test + public void validateBasicConstruction() { + + QuerySearchEntity request = new QuerySearchEntity(); + + // test constructor defaults + assertNull(request.getQueryStr()); + assertEquals("10", request.getMaxResults()); + + request.setMaxResults("500"); + assertEquals("500", request.getMaxResults()); + + assertNull(request.getSearchTerms()); + + request.setQueryStr(""); + assertEquals(1, request.getSearchTerms().length); + + request.setQueryStr("t1"); + assertEquals(1, request.getSearchTerms().length); + + request.setQueryStr("t1 t2 t3"); + assertEquals(3, request.getSearchTerms().length); + + } + +} + diff --git a/src/test/java/org/openecomp/sparky/viewandinspect/entity/EntityEntryTest.java b/src/test/java/org/openecomp/sparky/viewandinspect/entity/EntityEntryTest.java new file mode 100644 index 0000000..6dfa448 --- /dev/null +++ b/src/test/java/org/openecomp/sparky/viewandinspect/entity/EntityEntryTest.java @@ -0,0 +1,74 @@ +/* +* ============LICENSE_START======================================================= +* SPARKY (AAI UI service) +* ================================================================================ +* Copyright © 2017 AT&T Intellectual Property. +* Copyright © 2017 Amdocs +* All rights reserved. +* ================================================================================ +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/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. +* ============LICENSE_END========================================================= +* +* ECOMP and OpenECOMP are trademarks +* and service marks of AT&T Intellectual Property. +*/ + +package org.openecomp.sparky.viewandinspect.entity; + + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * The Class EntityEntryTest. + */ +@RunWith(PowerMockRunner.class) +public class EntityEntryTest { + + /** + * Inits the. + * + * @throws Exception the exception + */ + @Before + public void init() throws Exception {} + + /** + * Validate basic construction. + * + * @throws NoSuchAlgorithmException the no such algorithm exception + * @throws IOException Signals that an I/O exception has occurred. + */ + @Test + public void validateBasicConstruction() throws NoSuchAlgorithmException, IOException { + + EntityEntry entityEntry = new EntityEntry(); + + entityEntry.setEntityType("ShinyEntityType"); + entityEntry.setEntityPrimaryKeyValue("primary_key_value"); + entityEntry.setSearchTags("t1 t2 t3"); + + assertEquals("ShinyEntityType", entityEntry.getEntityType()); + assertEquals("primary_key_value", entityEntry.getEntityPrimaryKeyValue()); + assertEquals("t1 t2 t3", entityEntry.getSearchTags()); + + } + +} diff --git a/src/test/resources/bundleconfig/etc/appprops/source-of-truth.properties b/src/test/resources/bundleconfig/etc/appprops/source-of-truth.properties new file mode 100644 index 0000000..f08722f --- /dev/null +++ b/src/test/resources/bundleconfig/etc/appprops/source-of-truth.properties @@ -0,0 +1,47 @@ +# Source of Truth mappings. This file maps an enitity path to a source of truth identifier +# AAI v7 +/v7/network/ipsec-configurations/ipsec-configuration/requested-vig-address-type=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/requested-encryption-strength=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/requested-dmz-type=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/shared-dmz-network-address=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/requested-customer-name=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ike-version=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-authentication=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-encryption=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-dh-group=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-am-group-id=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-am-password=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ikev1-sa-lifetime=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ipsec-authentication=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ipsec-encryption=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ipsec-sa-lifetime=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/ipsec-pfs=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/xauth-userid=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/xauth-user-password=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/dpd-interval=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/dpd-frequency=service-manager +/v7/network/ipsec-configurations/ipsec-configuration/vig-servers=service-manager + +# AAI v8 +/v8/network/ipsec-configurations/ipsec-configuration/requested-vig-address-type=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/requested-encryption-strength=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/requested-dmz-type=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/shared-dmz-network-address=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/requested-customer-name=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ike-version=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-authentication=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-encryption=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-dh-group=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-am-group-id=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-am-password=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ikev1-sa-lifetime=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ipsec-authentication=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ipsec-encryption=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ipsec-sa-lifetime=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/ipsec-pfs=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/xauth-userid=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/xauth-user-password=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/dpd-interval=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/dpd-frequency=service-manager +/v8/network/ipsec-configurations/ipsec-configuration/vig-servers=service-manager + diff --git a/src/test/resources/es_test_scripts/commands.txt b/src/test/resources/es_test_scripts/commands.txt new file mode 100644 index 0000000..5d25157 --- /dev/null +++ b/src/test/resources/es_test_scripts/commands.txt @@ -0,0 +1,3 @@ +commands histoty +curl -XPUT localhost:9200/topographyhistorysearchindex?pretty --data-binary @topoHistoryConfigSettings.json +curl -XPUT localhost:9200/_bulk?pretty --data-binary @topoHistoryBulkLoad.json diff --git a/src/test/resources/es_test_scripts/geoEntities.json b/src/test/resources/es_test_scripts/geoEntities.json new file mode 100644 index 0000000..9af3978 --- /dev/null +++ b/src/test/resources/es_test_scripts/geoEntities.json @@ -0,0 +1,6 @@ +{"index":{"_index":"topographicalsearchindex-localhost","_type":"default"} +{"pkey": "complex.TEST1", "entityType": "complex", "longitude": "-82.089844", "latitude": "33.642063", "selfLink": "http://localhost:8443/complex/TEST1"} +{"index":{"_index":"topographicalsearchindex-localhost","_type":"default"} +{"pkey": "complex.TEST2", "entityType": "complex", "longitude": "-114.785156", "latitude": "37.640335", "selfLink": "http://localhost:8443/complex/TEST2"} +{"index":{"_index":"topographicalsearchindex-localhost","_type":"default"} +{"pkey": "complex.TEST3", "entityType": "complex", "longitude": "-97.910156", "latitude": "27.595935", "selfLink": "http://localhost:8443/complex/TEST3"} diff --git a/src/test/resources/es_test_scripts/prepareGeoEntityBulkImport.pl b/src/test/resources/es_test_scripts/prepareGeoEntityBulkImport.pl new file mode 100644 index 0000000..67ed571 --- /dev/null +++ b/src/test/resources/es_test_scripts/prepareGeoEntityBulkImport.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +my $filename = $ARGV[0]; +my $outputfile= $ARGV[1]; + +open my $fh_input, '<', $filename or die "Cannot open $filename: $!"; +open my $fh_output, '>', $outputfile or die "Cannot open $outputfile: $!"; + +while ( my $line = <$fh_input> ) { + chomp ($line); + + if ( $line =~ /(.*)(\".*\")(.*)/ ) { + + # we have seen examples of the status field containing quoted comma-delimited + # strings which is messing up parsing of the record data which is supposed to be + # comma-separated at the field level. This little block converts sections of + # this type of data into a single-quoted-string with a semi-colon delimiter instead. + + my $beforeBadStr = $1; + my $badStr = $2; + my $afterBadStr = $3; + + $badStr =~ s/,/;/g; + $badStr =~ s/"/'/g; + + $line = $beforeBadStr . $badStr . $afterBadStr ; + + } + + my @row = split(",", $line); + print $fh_output "{\"index\":{\"_index\":\"topographicalsearchindex-localhost\",\"_type\":\"default\"}\n"; + print $fh_output "{\"pkey\": \"$row[0]\", \"entityType\": \"$row[1]\", \"location\" : {\"lat\": \"$row[3]\", \"lon\": \"$row[2]\"}, \"selfLink\": \"$row[4]\"}\n"; + +} + +close($fh_input); +close($fh_output); + diff --git a/src/test/resources/es_test_scripts/sampleGeoEntities.csv b/src/test/resources/es_test_scripts/sampleGeoEntities.csv new file mode 100644 index 0000000..d149e39 --- /dev/null +++ b/src/test/resources/es_test_scripts/sampleGeoEntities.csv @@ -0,0 +1,4 @@ +complex.TEST1,complex,-82.089844,33.642063,http://localhost:8443/complex/TEST1, +complex.TEST2,complex,-114.785156,37.640335,http://localhost:8443/complex/TEST2, +complex.TEST3,complex,-97.910156,27.595935,http://localhost:8443/complex/TEST3, +pserver.TEST1,pserver,-97.910156,27.595935,http://localhost:8443/pserver/TEST1 \ No newline at end of file diff --git a/src/test/resources/es_test_scripts/topoHistoryBulkLoad.json b/src/test/resources/es_test_scripts/topoHistoryBulkLoad.json new file mode 100644 index 0000000..77d57f7 --- /dev/null +++ b/src/test/resources/es_test_scripts/topoHistoryBulkLoad.json @@ -0,0 +1,24 @@ +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"vServer","timestamp":"31-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":2,"entityType":"pServer","timestamp":"31-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":3,"entityType":"pServer","timestamp":"31-01-2017 02:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"pServer","timestamp":"31-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":2,"entityType":"vServer","timestamp":"31-01-2017 01:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"pServer","timestamp":"30-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"generic-vnf","timestamp":"30-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"vpe","timestamp":"31-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"newvce","timestamp":"31-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"vce","timestamp":"31-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"vce","timestamp":"30-01-2017 03:00:00"} +{"index":{"_index":"topographyhistorysearchindex-localhost","_type":"default"} +{"count":4,"entityType":"vce","timestamp":"01-02-2017 03:00:00"} diff --git a/src/test/resources/es_test_scripts/topoHistoryConfigSettings.json b/src/test/resources/es_test_scripts/topoHistoryConfigSettings.json new file mode 100644 index 0000000..875813e --- /dev/null +++ b/src/test/resources/es_test_scripts/topoHistoryConfigSettings.json @@ -0,0 +1,20 @@ +{ + "topographyhistorysearchindex-localhost" : { + "mappings" : { + "default" : { + "properties" : { + "count" : { + "type" : "keyword" + }, + "entityType" : { + "type" : "keyword" + }, + "timestamp" : { + "type" : "date", + "format" : "MMM d y HH:m:s||dd-MM-yyyy HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSSZZ||MM/dd/yyyy||yyyyMMdd'T'HHmmssZ" + } + } + } + } + } +} diff --git a/src/test/resources/es_test_scripts/topographicalConfigSettings.json b/src/test/resources/es_test_scripts/topographicalConfigSettings.json new file mode 100644 index 0000000..c9f5d5d --- /dev/null +++ b/src/test/resources/es_test_scripts/topographicalConfigSettings.json @@ -0,0 +1,24 @@ +{ + "mappings": { + "default": { + "properties": { + "pkey": { + "type": "string" + }, + "entityType": { + "type": "string" + }, + "longitude": { + "type": "string" + }, + "latitude": { + "type": "string" + }, + "selfLink": { + "type": "string" + } + } + } + + } +} diff --git a/src/test/resources/es_test_scripts/topographysearch_schema.json b/src/test/resources/es_test_scripts/topographysearch_schema.json new file mode 100644 index 0000000..5de6904 --- /dev/null +++ b/src/test/resources/es_test_scripts/topographysearch_schema.json @@ -0,0 +1,9 @@ +{ + "fields": [ + {"name": "pkey", "data-type": "string", "searchable": "false"}, + {"name": "entityType", "data-type": "string", "searchable": "false"}, + {"name": "latitude", "data-type": "string", "searchable": "false"}, + {"name": "longitude", "data-type": "string", "searchable": "false"}, + {"name": "selfLink", "data-type": "string", "searchable": "false"} + ] +} \ No newline at end of file diff --git a/src/test/resources/portal/portal-authentication.properties b/src/test/resources/portal/portal-authentication.properties new file mode 100644 index 0000000..d0732a1 --- /dev/null +++ b/src/test/resources/portal/portal-authentication.properties @@ -0,0 +1,2 @@ +username=testuser +password=18fa91d072b7b072a8d3326c448e5861 \ No newline at end of file diff --git a/src/test/resources/portal/roles.config b/src/test/resources/portal/roles.config new file mode 100644 index 0000000..b8313bd --- /dev/null +++ b/src/test/resources/portal/roles.config @@ -0,0 +1,6 @@ +[ + { + "id":1, + "name":"View" + } +] \ No newline at end of file -- 2.16.6