From 7b42006c51d4f014f72ae39170544a85d2f09fb5 Mon Sep 17 00:00:00 2001 From: Shawn Severin Date: Mon, 11 Dec 2017 15:42:52 -0500 Subject: [PATCH] Adding UI extensibility Adding the ability for Sparky developers to create their own custom front-end views Issue-ID: AAI-542 Change-Id: I83f9608639799e3bf85b654f44a0a7a5a85ad264 Signed-off-by: Shawn Severin --- pom.xml | 9 +- .../v1/conf/HelloWorldBeans.xml | 8 - .../inventory-ui-service/v1/conf/jaxrsBeans.groovy | 2 +- .../v1/routes/helloServlet.route | 4 - .../v1/routes/helloWorld.route | 4 - .../v1/routes/jaxrsExample.route | 4 - .../v1/routes/serverStaticContent.route | 4 - .../routes/sparky-core-unifiedFilterRequest.route | 4 - src/main/config/aaiEntityNodeDescriptors.json | 30 + src/main/config/ajsc-override-web.xml | 34 +- src/main/config/cadi.properties | 36 + src/main/config/csp-cookie-filter.properties | 18 + .../config/es_sv_mappings.json} | 6 +- .../config/es_sv_settings.json} | 0 src/main/config/runner-web.xml | 45 +- src/main/docker/Dockerfile | 6 +- .../java/org/onap/aai/sparky/JaxrsEchoService.java | 6 +- .../java/org/onap/aai/sparky/JaxrsUserService.java | 61 + .../ConfigurationException.java => Test.java} | 8 +- .../search/AggregateSummaryProcessor.java | 238 +++ .../search/AggregateVnfSearchProvider.java | 160 ++ .../search/VnfSearchQueryBuilder.java | 16 +- .../sync/AggregationSyncControllerFactory.java | 232 +++ .../sync}/AggregationSynchronizer.java | 115 +- .../sync/HistoricalEntitySummarizer.java | 391 +++++ .../sync/HistoricalEntitySyncController.java | 90 ++ .../aai/sparky/analytics/AbstractStatistics.java | 42 +- .../aai/sparky/analytics/HistoricalCounter.java | 82 +- .../sync/AutoSuggestionSyncController.java | 97 ++ .../sync}/AutosuggestionSynchronizer.java | 96 +- .../sync/VnfAliasSuggestionSynchronizer.java} | 47 +- .../sync/VnfAliasSyncController.java | 95 ++ .../common/search/CommonSearchSuggestion.java | 88 ++ .../sparky/config/oxm/CrossEntityReference.java | 1 - .../config/oxm/CrossEntityReferenceDescriptor.java | 65 + .../config/oxm/CrossEntityReferenceLookup.java | 154 ++ .../sparky/config/oxm/GeoEntityDescriptor.java} | 109 +- .../aai/sparky/config/oxm/GeoEntityLookup.java | 155 ++ .../sparky/config/oxm/GeoOxmEntityDescriptor.java | 69 + .../aai/sparky/config/oxm/OxmEntityDescriptor.java | 142 +- .../aai/sparky/config/oxm/OxmEntityLookup.java | 151 ++ .../onap/aai/sparky/config/oxm/OxmModelLoader.java | 500 ++---- .../sparky/config/oxm/OxmModelLoaderFilter.java | 90 -- .../aai/sparky/config/oxm/OxmModelProcessor.java | 31 + .../sparky/config/oxm/SearchableEntityLookup.java | 138 ++ .../config/oxm/SearchableOxmEntityDescriptor.java | 73 + .../config/oxm/SuggestionEntityDescriptor.java | 52 + .../sparky/config/oxm/SuggestionEntityLookup.java | 197 +++ .../sync}/CrossEntityReferenceSynchronizer.java | 436 +++--- .../dal/{aai => }/ActiveInventoryAdapter.java | 337 ++-- .../onap/aai/sparky/dal/ElasticSearchAdapter.java | 120 ++ .../onap/aai/sparky/dal/NetworkTransaction.java | 25 +- .../dal/aai/ActiveInventoryDataProvider.java | 7 +- .../dal/aai/ActiveInventoryEntityStatistics.java | 65 +- ...tiveInventoryProcessingExceptionStatistics.java | 7 +- .../dal/aai/config/ActiveInventoryConfig.java | 74 +- .../dal/aai/config/ActiveInventoryRestConfig.java | 134 +- .../dal/aai/config/ActiveInventorySslConfig.java | 16 +- .../aai/sparky/dal/cache/InMemoryEntityCache.java | 107 -- .../sparky/dal/cache/PersistentEntityCache.java | 256 --- .../dal/elasticsearch/ElasticSearchAdapter.java | 213 --- .../elasticsearch/ElasticSearchDataProvider.java | 6 +- .../ElasticSearchEntityStatistics.java | 46 +- .../dal/elasticsearch/HashQueryResponse.java | 3 +- .../sparky/dal/elasticsearch/SearchAdapter.java | 68 +- .../elasticsearch/config/ElasticSearchConfig.java | 366 +---- .../sparky/dal/proxy/config/DataRouterConfig.java | 132 ++ .../dal/proxy/processor/AaiUiProxyProcessor.java | 227 +++ .../aai/sparky/dal/rest/RestClientBuilder.java | 10 +- .../aai/sparky/dal/rest/RestfulDataAccessor.java | 107 +- .../sparky/dal/sas/config/SearchServiceConfig.java | 5 + .../ResettableStreamHttpServletRequest.java | 128 -- .../sparky/dataintegrity/config/DiUiConstants.java | 77 + .../editattributes/AttributeEditProcessor.java | 182 +++ .../sparky/editattributes/AttributeUpdater.java | 366 +++++ .../editattributes/UserAuthorizationReader.java | 77 + .../aai/sparky/editattributes/UserValidator.java | 65 + .../entity/EditRequest.java} | 44 +- .../exception/AttributeUpdateException.java} | 46 +- .../inventory/EntityHistoryQueryBuilder.java | 143 ++ .../inventory/GeoVisualizationProcessor.java | 202 +++ .../sparky/inventory/entity/GeoIndexDocument.java | 292 ++++ .../inventory/entity/TopographicalEntity.java | 220 +++ .../org/onap/aai/sparky/logging/AaiUiMsgs.java | 53 +- .../sparky/{ => logging}/util/ServletUtils.java | 6 +- .../sparky/search/EntityCountHistoryProcessor.java | 417 +++++ .../entity => search}/SearchResponse.java | 23 +- .../aai/sparky/search/UnifiedSearchProcessor.java | 212 +++ .../onap/aai/sparky/search/VnfSearchService.java | 348 ----- .../onap/aai/sparky/search/api/SearchProvider.java | 34 + .../aai/sparky/search/config/SuggestionConfig.java | 6 +- .../search/entity/ExternalSearchRequestEntity.java | 69 + .../entity/QuerySearchEntity.java | 3 +- .../aai/sparky/search/entity/SearchSuggestion.java | 37 + .../search/filters/FilterElasticSearchAdapter.java | 7 +- .../aai/sparky/search/filters/FilterProcessor.java | 1 - .../search/filters/config/FiltersConfig.java | 2 +- .../search/registry/SearchProviderRegistry.java | 74 + .../org/onap/aai/sparky/security/EcompSso.java | 21 +- .../sparky/security/filter/CspCookieFilter.java | 274 ++++ .../aai/sparky/security/filter/LoginFilter.java | 19 +- .../security/portal/PortalRestAPIServiceImpl.java | 33 +- .../portal/config/PortalAuthenticationConfig.java | 1 + .../AbstractEntitySynchronizer.java | 96 +- .../ElasticSearchIndexCleaner.java | 318 +--- .../sparky/sync/ElasticSearchSchemaFactory.java | 109 ++ .../{synchronizer => sync}/IndexCleaner.java | 4 +- .../aai/sparky/sync/IndexIntegrityValidator.java | 176 +++ .../{synchronizer => sync}/IndexSynchronizer.java | 6 +- .../{synchronizer => sync}/IndexValidator.java | 2 +- .../org/onap/aai/sparky/sync/SyncController.java | 96 ++ .../SyncControllerImpl.java} | 304 +++- .../aai/sparky/sync/SyncControllerRegistrar.java | 27 + .../SyncControllerRegistry.java} | 33 +- .../aai/sparky/sync/SyncControllerService.java | 220 +++ .../config => sync}/SynchronizerConstants.java | 15 +- .../TaskProcessingStats.java | 62 +- .../TransactionRateMonitor.java} | 49 +- .../config/ElasticSearchEndpointConfig.java} | 54 +- .../sync/config/ElasticSearchSchemaConfig.java | 75 + .../config/NetworkStatisticsConfig.java | 2 +- .../sparky/sync/config/SyncControllerConfig.java | 303 ++++ .../entity/AggregationEntity.java | 50 +- .../entity/AggregationSuggestionEntity.java | 77 +- .../entity/IndexDocument.java | 9 +- .../entity/IndexableCrossEntityReference.java | 98 +- .../entity/IndexableEntity.java | 57 +- .../entity/MergableEntity.java | 19 +- .../entity/ObjectIdCollection.java | 18 +- .../entity/SearchableEntity.java | 92 +- .../entity/SelfLinkDescriptor.java | 2 +- .../entity/SuggestionSearchEntity.java | 283 ++-- .../entity/TransactionStorageType.java | 17 +- .../enumeration/OperationState.java | 4 +- .../enumeration/SynchronizerState.java | 4 +- .../task/PerformActiveInventoryRetrieval.java | 87 +- .../task/PerformElasticSearchPut.java | 82 +- .../task/PerformElasticSearchRetrieval.java | 60 +- .../task/PerformElasticSearchUpdate.java | 90 +- .../task/StoreDocumentTask.java | 87 +- .../aai/sparky/sync/task/SyncControllerTask.java | 53 + .../synchronizer/IndexIntegrityValidator.java | 227 --- .../aai/sparky/synchronizer/MyErrorHandler.java | 111 -- .../onap/aai/sparky/synchronizer/SyncHelper.java | 568 ------- .../config/SynchronizerConfiguration.java | 544 ------- .../synchronizer/config/TaskProcessorConfig.java | 325 ---- .../filter/ElasticSearchSynchronizerFilter.java | 136 -- .../task/CollectEntitySelfLinkTask.java | 104 -- .../task/CollectEntityTypeSelfLinksTask.java | 105 -- .../task/GetCrossEntityReferenceEntityTask.java | 105 -- .../task/PersistOperationResultToDisk.java | 157 -- .../task/RetrieveOperationResultFromDisk.java | 133 -- .../sparky/topology/sync/GeoSyncController.java | 95 ++ .../aai/sparky/topology/sync/GeoSynchronizer.java | 497 ++++++ .../org/onap/aai/sparky/util/ConfigHelper.java | 2 +- .../java/org/onap/aai/sparky/util/Encryptor.java | 78 +- .../java/org/onap/aai/sparky/util/ErrorUtil.java | 1 - .../org/onap/aai/sparky/util/KeystoreBuilder.java | 24 +- .../java/org/onap/aai/sparky/util/NodeUtils.java | 144 +- .../org/onap/aai/sparky/util/RestletUtils.java | 118 ++ .../aai/sparky/util/SuggestionsPermutation.java | 85 +- .../java/org/onap/aai/sparky/util/TreeWalker.java | 10 +- .../org/onap/aai/sparky/util/test/Encryptor.java | 83 - .../onap/aai/sparky/util/test/KeystoreBuilder.java | 541 ------- .../viewandinspect/EntityTypeAggregation.java | 13 +- .../SchemaVisualizationProcessor.java | 174 +++ .../config/TierSupportUiConstants.java | 352 +---- ...zationConfig.java => VisualizationConfigs.java} | 78 +- .../viewandinspect/entity/ActiveInventoryNode.java | 64 +- .../entity/D3VisualizationOutput.java | 66 - .../entity/GraphRequest.java} | 117 +- .../aai/sparky/viewandinspect/entity/JsonNode.java | 70 +- .../aai/sparky/viewandinspect/entity/NodeMeta.java | 22 +- .../entity/NodeProcessingTransaction.java | 2 +- .../sparky/viewandinspect/entity/Relationship.java | 4 +- .../viewandinspect/entity/RelationshipList.java | 18 +- .../entity/SearchableEntityList.java | 115 ++ .../entity/SelfLinkDeterminationTransaction.java | 3 +- .../sparky/viewandinspect/entity/Violations.java | 125 -- .../enumeration/NodeProcessingAction.java | 1 + .../search/ViewInspectSearchProvider.java | 440 ++++++ .../services/SearchServiceWrapper.java | 980 ------------ .../services/VisualizationContext.java | 97 +- .../services/VisualizationService.java | 110 +- .../services/VisualizationTransformer.java | 122 +- .../viewandinspect/servlet/SearchServlet.java | 224 --- .../servlet/VisualizationServlet.java | 200 --- .../task/CollectNodeSelfLinkTask.java | 29 - .../task/PerformNodeSelfLinkProcessingTask.java | 31 +- .../task/PerformSelfLinkDeterminationTask.java | 16 +- .../sync/ViewInspectEntitySynchronizer.java} | 128 +- .../sync/ViewInspectSyncController.java | 129 ++ src/main/resources/extApps/aai.war | Bin 0 -> 1372092 bytes src/main/resources/extApps/aai.xml | 1 - src/main/resources/logging/AAIUIMsgs.properties | 125 +- src/main/scripts/encNameValue.sh | 20 + src/main/scripts/start.sh | 61 +- .../onap/aai/sparky/FilterByContainsClassName.java | 22 - .../java/org/onap/aai/sparky/SparkyPojoTest.java | 221 --- .../sparky/analytics/AbstractStatisticsTest.java | 46 - .../sparky/analytics/AveragingRingBufferTest.java | 64 +- .../aai/sparky/analytics/HistogramSamplerTest.java | 50 +- .../sparky/analytics/HistoricalCounterTest.java | 87 +- .../analytics/TransactionRateControllerTest.java | 48 +- .../config/oxm/CrossEntityReferenceTest.java | 27 - .../config/oxm/OxmModelLoaderFilterTest.java | 57 - .../aai/sparky/dal/NetworkTransactionTest.java | 36 - .../sparky/dal/aai/ActiveInventoryAdapterTest.java | 104 -- .../aai/ActiveInventoryEntityStatisticsTest.java | 75 - ...InventoryProcessingExceptionStatisticsTest.java | 62 - .../dal/aai/config/ActiveInventoryConfigTest.java | 381 ++--- .../dal/aai/config/ActiveInventoryConfigUtil.java | 87 ++ .../aai/config/ActiveInventoryRestConfigTest.java | 484 +++--- .../aai/config/ActiveInventorySslConfigTest.java | 58 +- .../sparky/dal/cache/InMemoryEntityCacheTest.java | 26 - .../dal/elasticsearch/ElasticSearchConfigTest.java | 182 +-- .../ElasticSearchEntityStatisticsTest.java | 101 -- .../entity/AutoSuggestDocumentEntity.java | 48 +- .../entity/AutoSuggestDocumentEntityFields.java | 48 +- .../entity/AutoSuggestElasticHitEntity.java | 48 +- .../entity/AutoSuggestElasticHitsEntity.java | 48 +- .../entity/AutoSuggestElasticSearchResponse.java | 48 +- .../dal/elasticsearch/entity/BucketEntity.java | 48 +- .../dal/elasticsearch/entity/ElasticHit.java | 48 +- .../elasticsearch/entity/ElasticHitsEntity.java | 48 +- .../entity/ElasticSearchAggegrationResponse.java | 48 +- .../entity/ElasticSearchAggregation.java | 48 +- .../entity/ElasticSearchCountResponse.java | 48 +- .../dal/elasticsearch/entity/PayloadEntity.java | 48 +- .../proxy/processor/AaiUiProxyProcessorTest.java | 112 ++ .../dal/proxy/processor/DataRouterConfigUtil.java | 50 + .../aai/sparky/dal/rest/RestClientBuilderTest.java | 63 +- .../dal/rest/RestOperationalStatisticsTest.java | 210 --- .../sparky/dal/rest/RestfulDataAccessorTest.java | 51 +- .../aai/sparky/dal/sas/entity/DocumentEntity.java | 48 +- .../sparky/dal/sas/entity/EntityCountResponse.java | 48 +- .../dal/sas/entity/GroupByAggregationEntity.java | 48 +- .../entity/GroupByAggregationResponseEntity.java | 48 +- .../onap/aai/sparky/dal/sas/entity/HitEntity.java | 48 +- .../sas/entity/SearchAbstractionEntityBuilder.java | 71 +- .../dal/sas/entity/SearchAbstractionResponse.java | 48 +- .../aai/sparky/dal/sas/entity/SearchResult.java | 48 +- .../dataintegrity/config/DiUiConstantsTest.java | 65 + .../editattributes/AttributeUpdaterTest.java | 143 ++ .../sparky/editattributes/EditAttributesTest.java | 219 +++ .../TestUserAuthorizationReader.java | 113 ++ .../sparky/editattributes/TestUserValidator.java | 137 ++ .../inventory/EntityHistoryQueryBuilderTest.java | 33 + .../aai/sparky/inventory/GeoIndexDocumentTest.java | 121 ++ .../aai/sparky/logging/util/LoggingUtilsTest.java | 26 + .../search/EntityCountHistoryProcessorTest.java | 119 ++ .../sparky/search/UnifiedSearchProcessorTest.java | 644 ++++++++ .../sparky/search/VnfSearchQueryBuilderTest.java | 77 - .../aai/sparky/search/VnfSearchServiceTest.java | 94 -- .../sparky/search/filters/FilterProcessorTest.java | 31 +- .../security/SecurityContextFactoryImplTest.java | 48 +- .../portal/TestPortalRestAPIServiceImpl.java | 66 +- .../sparky/security/portal/TestUserManager.java | 117 +- .../AggregationSuggestionSynchronizerTest.java | 91 -- .../synchronizer/AsyncRateControlTester.java | 55 +- .../aai/sparky/synchronizer/IndexDocumentTest.java | 53 +- .../sparky/synchronizer/SyncControllerBuilder.java | 892 +++++------ .../synchronizer/SyncControllerServiceTest.java | 34 + .../sparky/synchronizer/SyncControllerTest.java | 86 - .../aai/sparky/synchronizer/SyncHelperTest.java | 126 -- .../sparky/synchronizer/TestSyncController.java | 177 +++ .../config/SynchronizerConfigurationTest.java | 395 ----- .../entity/AggregationSuggestionEntityTest.java | 9 +- .../entity/SuggestionSearchEntityTest.java | 161 ++ .../task/PerformActiveInventoryRetrievalTest.java | 87 ++ .../aai/sparky/util/CaptureLoggerAppender.java | 48 +- .../aai/sparky/util/ElasticEntitySummarizer.java | 118 +- .../aai/sparky/util/ElasticGarbageInjector.java | 170 -- .../onap/aai/sparky/util/EncryptConvertorTest.java | 14 - .../org/onap/aai/sparky/util/ExceptionHelper.java | 48 +- .../onap/aai/sparky/util/HttpServletHelper.java | 48 +- .../onap/aai/sparky/util/KeystoreBuilderTest.java | 105 -- .../org/onap/aai/sparky/util/LogValidator.java | 55 +- .../org/onap/aai/sparky/util/NodeUtilsTest.java | 517 ------ .../onap/aai/sparky/util/OxmModelLoaderTest.java | 166 -- .../sparky/util/SuggestionsPermutationTest.java | 201 +++ .../sparky/util/SuggestionsPermutationsTest.java | 36 - .../org/onap/aai/sparky/util/TreeWalkerTest.java | 50 +- .../viewandinspect/ActiveInventoryNodeTester.java | 354 ----- .../sparky/viewandinspect/SearchAdapterTest.java | 53 +- .../sparky/viewandinspect/SearchResponseTest.java | 54 +- .../sparky/viewandinspect/SearchServletTest.java | 1639 ++++++++++---------- .../viewandinspect/SearchableGroupsTest.java | 48 +- .../ViewAndInspectSearchRequestTest.java | 50 +- .../config/VisualizationConfigTest.java | 81 + .../entity/ActiveInventoryNodeTest.java | 101 ++ .../viewandinspect/entity/EntityEntryTest.java | 68 +- .../viewandinspect/entity/GraphRequestTest.java | 56 + .../viewandinspect/entity/InlineMessageTest.java | 55 + .../viewandinspect/entity/JsonNodeLinkTest.java | 56 + .../viewandinspect/entity/NodeDebugTest.java | 57 + .../sparky/viewandinspect/entity/NodeMetaTest.java | 87 ++ .../viewandinspect/entity/QueryParamsTest.java | 56 + .../viewandinspect/entity/QueryRequestTest.java | 54 + .../entity/RelatedToPropertyTest.java | 54 + .../entity/RelationshipDataTest.java | 54 + .../viewandinspect/entity/SearchResponseTest.java | 52 + .../SelfLinkDeterminationTransactionTest.java | 67 + .../services/VisualizationContextTest.java | 141 -- .../services/VisualizationServiceTest.java | 96 -- .../PerformNodeSelfLinkProcessingTaskTest.java | 232 +++ src/test/resources/appconfig/aai.properties | 87 -- .../resources/appconfig/elasticsearch.properties | 70 - .../appconfig/etc/aaiEntityNodeDescriptors.json | 188 --- src/test/resources/appconfig/etc/ajsc-chef.jks | Bin 5256 -> 0 bytes src/test/resources/appconfig/etc/ajsc-jetty.xml | 128 -- .../resources/appconfig/etc/ajsc-override-web.xml | 70 - src/test/resources/appconfig/etc/ajscJetty.jks | Bin 3736 -> 0 bytes .../appconfig/etc/autoSuggestMappings.json | 10 - .../appconfig/etc/autoSuggestSettings.json | 21 - .../resources/appconfig/etc/dynamicMappings.json | 14 - .../appconfig/etc/entityCountHistoryMappings.json | 16 - .../appconfig/etc/jul-redirect.properties | 13 - src/test/resources/appconfig/etc/keyfile | 27 - src/test/resources/appconfig/etc/runner-web.xml | 112 -- src/test/resources/appconfig/roles.config | 6 - .../resources/appconfig/search-service.properties | 29 - .../appconfig/suggestive-search.properties | 27 - .../resources/appconfig/synchronizer.properties | 33 - .../portal/portal-authentication.properties | 2 +- ...estionEntity_getIndexDocumentJson_expected.json | 1 - .../user-auth-reader/authorized-users-empty.config | 0 .../user-auth-reader/authorized-users.config | 3 + .../user-validator/authorized-users.config | 3 + 329 files changed, 17601 insertions(+), 17830 deletions(-) delete mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/HelloWorldBeans.xml delete mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloServlet.route delete mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloWorld.route delete mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/jaxrsExample.route delete mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/serverStaticContent.route delete mode 100644 src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/sparky-core-unifiedFilterRequest.route create mode 100644 src/main/config/cadi.properties create mode 100644 src/main/config/csp-cookie-filter.properties rename src/{test/resources/appconfig/etc/es_mappings.json => main/config/es_sv_mappings.json} (88%) rename src/{test/resources/appconfig/etc/es_settings.json => main/config/es_sv_settings.json} (100%) create mode 100644 src/main/java/org/onap/aai/sparky/JaxrsUserService.java rename src/main/java/org/onap/aai/sparky/{config/exception/ConfigurationException.java => Test.java} (88%) create mode 100644 src/main/java/org/onap/aai/sparky/aggregatevnf/search/AggregateSummaryProcessor.java create mode 100644 src/main/java/org/onap/aai/sparky/aggregatevnf/search/AggregateVnfSearchProvider.java rename src/main/java/org/onap/aai/sparky/{ => aggregatevnf}/search/VnfSearchQueryBuilder.java (91%) create mode 100644 src/main/java/org/onap/aai/sparky/aggregation/sync/AggregationSyncControllerFactory.java rename src/main/java/org/onap/aai/sparky/{synchronizer => aggregation/sync}/AggregationSynchronizer.java (87%) create mode 100644 src/main/java/org/onap/aai/sparky/aggregation/sync/HistoricalEntitySummarizer.java create mode 100644 src/main/java/org/onap/aai/sparky/aggregation/sync/HistoricalEntitySyncController.java create mode 100644 src/main/java/org/onap/aai/sparky/autosuggestion/sync/AutoSuggestionSyncController.java rename src/main/java/org/onap/aai/sparky/{synchronizer => autosuggestion/sync}/AutosuggestionSynchronizer.java (87%) rename src/main/java/org/onap/aai/sparky/{synchronizer/AggregationSuggestionSynchronizer.java => autosuggestion/sync/VnfAliasSuggestionSynchronizer.java} (76%) create mode 100644 src/main/java/org/onap/aai/sparky/autosuggestion/sync/VnfAliasSyncController.java create mode 100644 src/main/java/org/onap/aai/sparky/common/search/CommonSearchSuggestion.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceDescriptor.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceLookup.java rename src/{test/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderTest.java => main/java/org/onap/aai/sparky/config/oxm/GeoEntityDescriptor.java} (57%) create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/GeoEntityLookup.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/GeoOxmEntityDescriptor.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/OxmEntityLookup.java delete mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderFilter.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/OxmModelProcessor.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/SearchableEntityLookup.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/SearchableOxmEntityDescriptor.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityDescriptor.java create mode 100644 src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityLookup.java rename src/main/java/org/onap/aai/sparky/{synchronizer => crossentityreference/sync}/CrossEntityReferenceSynchronizer.java (62%) rename src/main/java/org/onap/aai/sparky/dal/{aai => }/ActiveInventoryAdapter.java (50%) create mode 100644 src/main/java/org/onap/aai/sparky/dal/ElasticSearchAdapter.java delete mode 100644 src/main/java/org/onap/aai/sparky/dal/cache/InMemoryEntityCache.java delete mode 100644 src/main/java/org/onap/aai/sparky/dal/cache/PersistentEntityCache.java delete mode 100644 src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchAdapter.java create mode 100644 src/main/java/org/onap/aai/sparky/dal/proxy/config/DataRouterConfig.java create mode 100644 src/main/java/org/onap/aai/sparky/dal/proxy/processor/AaiUiProxyProcessor.java delete mode 100644 src/main/java/org/onap/aai/sparky/dal/servlet/ResettableStreamHttpServletRequest.java create mode 100644 src/main/java/org/onap/aai/sparky/dataintegrity/config/DiUiConstants.java create mode 100644 src/main/java/org/onap/aai/sparky/editattributes/AttributeEditProcessor.java create mode 100644 src/main/java/org/onap/aai/sparky/editattributes/AttributeUpdater.java create mode 100644 src/main/java/org/onap/aai/sparky/editattributes/UserAuthorizationReader.java create mode 100644 src/main/java/org/onap/aai/sparky/editattributes/UserValidator.java rename src/main/java/org/onap/aai/sparky/{search/Suggestion.java => editattributes/entity/EditRequest.java} (65%) rename src/main/java/org/onap/aai/sparky/{dal/cache/EntityCache.java => editattributes/exception/AttributeUpdateException.java} (61%) create mode 100644 src/main/java/org/onap/aai/sparky/inventory/EntityHistoryQueryBuilder.java create mode 100644 src/main/java/org/onap/aai/sparky/inventory/GeoVisualizationProcessor.java create mode 100644 src/main/java/org/onap/aai/sparky/inventory/entity/GeoIndexDocument.java create mode 100644 src/main/java/org/onap/aai/sparky/inventory/entity/TopographicalEntity.java rename src/main/java/org/onap/aai/sparky/{ => logging}/util/ServletUtils.java (98%) create mode 100644 src/main/java/org/onap/aai/sparky/search/EntityCountHistoryProcessor.java rename src/main/java/org/onap/aai/sparky/{viewandinspect/entity => search}/SearchResponse.java (78%) create mode 100644 src/main/java/org/onap/aai/sparky/search/UnifiedSearchProcessor.java delete mode 100644 src/main/java/org/onap/aai/sparky/search/VnfSearchService.java create mode 100644 src/main/java/org/onap/aai/sparky/search/api/SearchProvider.java create mode 100644 src/main/java/org/onap/aai/sparky/search/entity/ExternalSearchRequestEntity.java rename src/main/java/org/onap/aai/sparky/{viewandinspect => search}/entity/QuerySearchEntity.java (97%) create mode 100644 src/main/java/org/onap/aai/sparky/search/entity/SearchSuggestion.java create mode 100644 src/main/java/org/onap/aai/sparky/search/registry/SearchProviderRegistry.java create mode 100644 src/main/java/org/onap/aai/sparky/security/filter/CspCookieFilter.java rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/AbstractEntitySynchronizer.java (85%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/ElasticSearchIndexCleaner.java (65%) create mode 100644 src/main/java/org/onap/aai/sparky/sync/ElasticSearchSchemaFactory.java rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/IndexCleaner.java (93%) create mode 100644 src/main/java/org/onap/aai/sparky/sync/IndexIntegrityValidator.java rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/IndexSynchronizer.java (90%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/IndexValidator.java (97%) create mode 100644 src/main/java/org/onap/aai/sparky/sync/SyncController.java rename src/main/java/org/onap/aai/sparky/{synchronizer/SyncController.java => sync/SyncControllerImpl.java} (61%) create mode 100644 src/main/java/org/onap/aai/sparky/sync/SyncControllerRegistrar.java rename src/main/java/org/onap/aai/sparky/{config/Configurable.java => sync/SyncControllerRegistry.java} (66%) create mode 100644 src/main/java/org/onap/aai/sparky/sync/SyncControllerService.java rename src/main/java/org/onap/aai/sparky/{synchronizer/config => sync}/SynchronizerConstants.java (84%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/TaskProcessingStats.java (76%) rename src/main/java/org/onap/aai/sparky/{synchronizer/TransactionRateController.java => sync/TransactionRateMonitor.java} (61%) rename src/main/java/org/onap/aai/sparky/{search/SuggestionList.java => sync/config/ElasticSearchEndpointConfig.java} (50%) create mode 100644 src/main/java/org/onap/aai/sparky/sync/config/ElasticSearchSchemaConfig.java rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/config/NetworkStatisticsConfig.java (99%) create mode 100644 src/main/java/org/onap/aai/sparky/sync/config/SyncControllerConfig.java rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/AggregationEntity.java (78%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/AggregationSuggestionEntity.java (66%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/IndexDocument.java (86%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/IndexableCrossEntityReference.java (55%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/IndexableEntity.java (72%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/MergableEntity.java (87%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/ObjectIdCollection.java (80%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/SearchableEntity.java (68%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/SelfLinkDescriptor.java (98%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/SuggestionSearchEntity.java (51%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/entity/TransactionStorageType.java (84%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/enumeration/OperationState.java (91%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/enumeration/SynchronizerState.java (92%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/task/PerformActiveInventoryRetrieval.java (56%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/task/PerformElasticSearchPut.java (58%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/task/PerformElasticSearchRetrieval.java (63%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/task/PerformElasticSearchUpdate.java (57%) rename src/main/java/org/onap/aai/sparky/{synchronizer => sync}/task/StoreDocumentTask.java (56%) create mode 100644 src/main/java/org/onap/aai/sparky/sync/task/SyncControllerTask.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/IndexIntegrityValidator.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/MyErrorHandler.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/SyncHelper.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/config/SynchronizerConfiguration.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/config/TaskProcessorConfig.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/filter/ElasticSearchSynchronizerFilter.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/task/CollectEntitySelfLinkTask.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/task/CollectEntityTypeSelfLinksTask.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/task/GetCrossEntityReferenceEntityTask.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/task/PersistOperationResultToDisk.java delete mode 100644 src/main/java/org/onap/aai/sparky/synchronizer/task/RetrieveOperationResultFromDisk.java create mode 100644 src/main/java/org/onap/aai/sparky/topology/sync/GeoSyncController.java create mode 100644 src/main/java/org/onap/aai/sparky/topology/sync/GeoSynchronizer.java create mode 100644 src/main/java/org/onap/aai/sparky/util/RestletUtils.java delete mode 100644 src/main/java/org/onap/aai/sparky/util/test/Encryptor.java delete mode 100644 src/main/java/org/onap/aai/sparky/util/test/KeystoreBuilder.java create mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/SchemaVisualizationProcessor.java rename src/main/java/org/onap/aai/sparky/viewandinspect/config/{VisualizationConfig.java => VisualizationConfigs.java} (68%) rename src/main/java/org/onap/aai/sparky/{suggestivesearch/SuggestionEntity.java => viewandinspect/entity/GraphRequest.java} (68%) create mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/entity/SearchableEntityList.java delete mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/entity/Violations.java create mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/search/ViewInspectSearchProvider.java delete mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/services/SearchServiceWrapper.java delete mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/servlet/SearchServlet.java delete mode 100644 src/main/java/org/onap/aai/sparky/viewandinspect/servlet/VisualizationServlet.java rename src/main/java/org/onap/aai/sparky/{synchronizer/SearchableEntitySynchronizer.java => viewinspect/sync/ViewInspectEntitySynchronizer.java} (85%) create mode 100644 src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectSyncController.java create mode 100644 src/main/resources/extApps/aai.war create mode 100644 src/main/scripts/encNameValue.sh delete mode 100644 src/test/java/org/onap/aai/sparky/FilterByContainsClassName.java delete mode 100644 src/test/java/org/onap/aai/sparky/SparkyPojoTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/analytics/AbstractStatisticsTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderFilterTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/dal/NetworkTransactionTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/dal/aai/ActiveInventoryAdapterTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/dal/aai/ActiveInventoryEntityStatisticsTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/dal/aai/ActiveInventoryProcessingExceptionStatisticsTest.java create mode 100644 src/test/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryConfigUtil.java delete mode 100644 src/test/java/org/onap/aai/sparky/dal/cache/InMemoryEntityCacheTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchEntityStatisticsTest.java create mode 100644 src/test/java/org/onap/aai/sparky/dal/proxy/processor/AaiUiProxyProcessorTest.java create mode 100644 src/test/java/org/onap/aai/sparky/dal/proxy/processor/DataRouterConfigUtil.java delete mode 100644 src/test/java/org/onap/aai/sparky/dal/rest/RestOperationalStatisticsTest.java create mode 100644 src/test/java/org/onap/aai/sparky/dataintegrity/config/DiUiConstantsTest.java create mode 100644 src/test/java/org/onap/aai/sparky/editattributes/AttributeUpdaterTest.java create mode 100644 src/test/java/org/onap/aai/sparky/editattributes/EditAttributesTest.java create mode 100644 src/test/java/org/onap/aai/sparky/editattributes/TestUserAuthorizationReader.java create mode 100644 src/test/java/org/onap/aai/sparky/editattributes/TestUserValidator.java create mode 100644 src/test/java/org/onap/aai/sparky/inventory/EntityHistoryQueryBuilderTest.java create mode 100644 src/test/java/org/onap/aai/sparky/inventory/GeoIndexDocumentTest.java create mode 100644 src/test/java/org/onap/aai/sparky/logging/util/LoggingUtilsTest.java create mode 100644 src/test/java/org/onap/aai/sparky/search/EntityCountHistoryProcessorTest.java create mode 100644 src/test/java/org/onap/aai/sparky/search/UnifiedSearchProcessorTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/search/VnfSearchQueryBuilderTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/search/VnfSearchServiceTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/synchronizer/AggregationSuggestionSynchronizerTest.java create mode 100644 src/test/java/org/onap/aai/sparky/synchronizer/SyncControllerServiceTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/synchronizer/SyncControllerTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/synchronizer/SyncHelperTest.java create mode 100644 src/test/java/org/onap/aai/sparky/synchronizer/TestSyncController.java delete mode 100644 src/test/java/org/onap/aai/sparky/synchronizer/config/SynchronizerConfigurationTest.java create mode 100644 src/test/java/org/onap/aai/sparky/synchronizer/entity/SuggestionSearchEntityTest.java create mode 100644 src/test/java/org/onap/aai/sparky/synchronizer/task/PerformActiveInventoryRetrievalTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/util/ElasticGarbageInjector.java delete mode 100644 src/test/java/org/onap/aai/sparky/util/EncryptConvertorTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/util/KeystoreBuilderTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/util/NodeUtilsTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/util/OxmModelLoaderTest.java create mode 100644 src/test/java/org/onap/aai/sparky/util/SuggestionsPermutationTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/util/SuggestionsPermutationsTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/ActiveInventoryNodeTester.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNodeTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/GraphRequestTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/InlineMessageTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/JsonNodeLinkTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/NodeDebugTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/NodeMetaTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/QueryParamsTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/QueryRequestTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/RelatedToPropertyTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/RelationshipDataTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/SearchResponseTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/entity/SelfLinkDeterminationTransactionTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/services/VisualizationContextTest.java delete mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/services/VisualizationServiceTest.java create mode 100644 src/test/java/org/onap/aai/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTaskTest.java delete mode 100644 src/test/resources/appconfig/aai.properties delete mode 100644 src/test/resources/appconfig/elasticsearch.properties delete mode 100644 src/test/resources/appconfig/etc/aaiEntityNodeDescriptors.json delete mode 100644 src/test/resources/appconfig/etc/ajsc-chef.jks delete mode 100644 src/test/resources/appconfig/etc/ajsc-jetty.xml delete mode 100644 src/test/resources/appconfig/etc/ajsc-override-web.xml delete mode 100644 src/test/resources/appconfig/etc/ajscJetty.jks delete mode 100644 src/test/resources/appconfig/etc/autoSuggestMappings.json delete mode 100644 src/test/resources/appconfig/etc/autoSuggestSettings.json delete mode 100644 src/test/resources/appconfig/etc/dynamicMappings.json delete mode 100644 src/test/resources/appconfig/etc/entityCountHistoryMappings.json delete mode 100644 src/test/resources/appconfig/etc/jul-redirect.properties delete mode 100644 src/test/resources/appconfig/etc/keyfile delete mode 100644 src/test/resources/appconfig/etc/runner-web.xml delete mode 100644 src/test/resources/appconfig/roles.config delete mode 100644 src/test/resources/appconfig/search-service.properties delete mode 100644 src/test/resources/appconfig/suggestive-search.properties delete mode 100644 src/test/resources/appconfig/synchronizer.properties delete mode 100644 src/test/resources/sync/entity/AggregationSuggestionEntity_getIndexDocumentJson_expected.json create mode 100644 src/test/resources/user-auth-reader/authorized-users-empty.config create mode 100644 src/test/resources/user-auth-reader/authorized-users.config create mode 100644 src/test/resources/user-validator/authorized-users.config diff --git a/pom.xml b/pom.xml index 5f4921e..a5ddf4f 100644 --- a/pom.xml +++ b/pom.xml @@ -18,7 +18,7 @@ v1 2.0.0 /appl/${project.artifactId} - org.amdocs.aaiee + org.onap.aai sparky-fe 1.1.0-SNAPSHOT @@ -56,7 +56,6 @@ - org.mockito mockito-all @@ -405,9 +404,9 @@ - org.onap.aai.sparky-fe - sparky-fe - 1.1.0-SNAPSHOT + ${frontEndGroupdId} + ${frontEndArtifactId} + ${frontEndVersion} war ${basedir}/target/swm/package/nix/dist_files${distFilesRoot}/extApps/ aai.war 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 deleted file mode 100644 index c052560..0000000 --- a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/conf/HelloWorldBeans.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - 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 index b65cb80..da9b558 100644 --- 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 @@ -3,7 +3,7 @@ beans{ xmlns jaxrs: "http://cxf.apache.org/jaxrs" xmlns util: "http://www.springframework.org/schema/util" - echoService(org.onap.aai.sparky.JaxrsEchoService) + echoService(org.openecomp.sparky.JaxrsEchoService) util.list(id: 'jaxrsServices') { ref(bean:'echoService') 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 deleted file mode 100644 index 5ede9c1..0000000 --- a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloServlet.route +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ 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 deleted file mode 100644 index bc3e178..0000000 --- a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/helloWorld.route +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ 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 deleted file mode 100644 index 25c1977..0000000 --- a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/jaxrsExample.route +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ 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 deleted file mode 100644 index bf221c6..0000000 --- a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/serverStaticContent.route +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/sparky-core-unifiedFilterRequest.route b/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/sparky-core-unifiedFilterRequest.route deleted file mode 100644 index 36cf518..0000000 --- a/src/main/ajsc/inventory-ui-service_v1/inventory-ui-service/v1/routes/sparky-core-unifiedFilterRequest.route +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/main/config/aaiEntityNodeDescriptors.json b/src/main/config/aaiEntityNodeDescriptors.json index bf95f28..e72bab0 100644 --- a/src/main/config/aaiEntityNodeDescriptors.json +++ b/src/main/config/aaiEntityNodeDescriptors.json @@ -129,6 +129,21 @@ "className": "node-button", "r": "10" } + }, + { + "type": "button", + "name": "icon_triangle_warning", + "class": "node-button", + "shapeAttributes": { + "offset": { + "x": "46", + "y": "-12" + } + }, + "svgAttributes": { + "className": "node-button", + "r": "10" + } }] }, "selectedNodeClass": { @@ -183,6 +198,21 @@ "className": "node-button", "r": "10" } + }, + { + "type": "button", + "name": "icon_triangle_warning", + "class": "node-button", + "shapeAttributes": { + "offset": { + "x": "46", + "y": "-12" + } + }, + "svgAttributes": { + "className": "node-button", + "r": "10" + } }] } } \ No newline at end of file diff --git a/src/main/config/ajsc-override-web.xml b/src/main/config/ajsc-override-web.xml index c66ac89..e267829 100644 --- a/src/main/config/ajsc-override-web.xml +++ b/src/main/config/ajsc-override-web.xml @@ -3,34 +3,22 @@ - - - - - ElasticSearchSynchronizerFilter - /nothingShouldBeSentHere/* - - - - OxmModelLoaderFilter - /nothingShouldBeSentHereEither/* + + + LoginFilter + /* PortalRestAPIProxy /api/v2/* - - - - VisualizationServlet - /visualization/* - - + + springSecurityFilterChain /* - - + + ManagementServlet /mgmt @@ -46,12 +34,6 @@ /services/* - - SearchServlet - /elasticSearchQuery/* - /search/* - - jsp *.jsp 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/test/resources/appconfig/etc/es_mappings.json b/src/main/config/es_sv_mappings.json similarity index 88% rename from src/test/resources/appconfig/etc/es_mappings.json rename to src/main/config/es_sv_mappings.json index 216e3d9..c964ca3 100644 --- a/src/test/resources/appconfig/etc/es_mappings.json +++ b/src/main/config/es_sv_mappings.json @@ -16,6 +16,10 @@ "type": "string", "analyzer": "ngram_analyzer" }, + "perspectives" : { + "type": "string", + "index": "not_analyzed" + }, "crossEntityReferenceValues": { "type": "string", "analyzer": "ngram_analyzer" @@ -23,7 +27,7 @@ "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" diff --git a/src/test/resources/appconfig/etc/es_settings.json b/src/main/config/es_sv_settings.json similarity index 100% rename from src/test/resources/appconfig/etc/es_settings.json rename to src/main/config/es_sv_settings.json diff --git a/src/main/config/runner-web.xml b/src/main/config/runner-web.xml index 1c6ccdc..2e39e24 100644 --- a/src/main/config/runner-web.xml +++ b/src/main/config/runner-web.xml @@ -19,27 +19,22 @@ org.springframework.web.context.ContextLoaderListener - + + + PortalRestAPIProxy + org.openecomp.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy + + + + LoginFilter + org.onap.aai.sparky.security.filter.LoginFilter + + ManagementServlet ajsc.ManagementServlet - - - VisualizationServlet - org.onap.aai.sparky.viewandinspect.servlet.VisualizationServlet - - - - ElasticSearchSynchronizerFilter - org.onap.aai.sparky.synchronizer.filter.ElasticSearchSynchronizerFilter - - - - OxmModelLoaderFilter - org.onap.aai.sparky.config.oxm.OxmModelLoaderFilter - - + WriteableRequestFilter com.att.ajsc.csi.writeablerequestfilter.WriteableRequestFilter @@ -59,11 +54,6 @@ ajsc.servlet.AjscCamelServlet - - SearchServlet - org.onap.aai.sparky.viewandinspect.servlet.SearchServlet - - springSecurityFilterChain org.springframework.web.filter.DelegatingFilterProxy @@ -74,16 +64,9 @@ org.springframework.web.servlet.DispatcherServlet 1 + - - PortalRestAPIProxy - org.openecomp.portalsdk.core.onboarding.crossapi.PortalRestAPIProxy - - - - - + diff --git a/src/main/docker/Dockerfile b/src/main/docker/Dockerfile index 1ab49ff..b77cf1c 100644 --- a/src/main/docker/Dockerfile +++ b/src/main/docker/Dockerfile @@ -15,10 +15,8 @@ RUN export JAVA_HOME # Build up the deployment folder structure RUN mkdir -p $MICRO_HOME -copy swm/package/nix/dist_files/appl/sparky-be/1.1.0-SNAPSHOT/ $MICRO_HOME/ -RUN ls -la $MICRO_HOME/ -RUN mkdir -p $BIN_HOME -COPY *.sh $BIN_HOME/ +copy swm/package/nix/dist_files/appl/inventory-ui-service/1.1.0-SNAPSHOT/ $MICRO_HOME/ +RUN ls -la $BIN_HOME/ RUN chmod 755 $BIN_HOME/* RUN ln -s /logs $MICRO_HOME/logs diff --git a/src/main/java/org/onap/aai/sparky/JaxrsEchoService.java b/src/main/java/org/onap/aai/sparky/JaxrsEchoService.java index 8e7e0a2..f7ea619 100644 --- a/src/main/java/org/onap/aai/sparky/JaxrsEchoService.java +++ b/src/main/java/org/onap/aai/sparky/JaxrsEchoService.java @@ -22,14 +22,14 @@ */ package org.onap.aai.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; +import com.att.ajsc.beans.PropertiesMapBean; +import com.att.ajsc.filemonitor.AJSCPropertiesMap; + /** * The Class JaxrsEchoService. diff --git a/src/main/java/org/onap/aai/sparky/JaxrsUserService.java b/src/main/java/org/onap/aai/sparky/JaxrsUserService.java new file mode 100644 index 0000000..dc7f5a4 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/JaxrsUserService.java @@ -0,0 +1,61 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.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/onap/aai/sparky/config/exception/ConfigurationException.java b/src/main/java/org/onap/aai/sparky/Test.java similarity index 88% rename from src/main/java/org/onap/aai/sparky/config/exception/ConfigurationException.java rename to src/main/java/org/onap/aai/sparky/Test.java index f796c38..6efca77 100644 --- a/src/main/java/org/onap/aai/sparky/config/exception/ConfigurationException.java +++ b/src/main/java/org/onap/aai/sparky/Test.java @@ -20,12 +20,8 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.config.exception; +package org.onap.aai.sparky; - -/** - * The Class ConfigurationException. - */ -public class ConfigurationException extends Exception { +public class Test { } diff --git a/src/main/java/org/onap/aai/sparky/aggregatevnf/search/AggregateSummaryProcessor.java b/src/main/java/org/onap/aai/sparky/aggregatevnf/search/AggregateSummaryProcessor.java new file mode 100644 index 0000000..6d2ec6e --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/aggregatevnf/search/AggregateSummaryProcessor.java @@ -0,0 +1,238 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.aggregatevnf.search; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.json.JsonObject; + +import org.apache.camel.Exchange; +import org.apache.camel.component.restlet.RestletConstants; +import org.json.JSONArray; +import org.json.JSONObject; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; +import org.onap.aai.sparky.dataintegrity.config.DiUiConstants; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.search.filters.FilterQueryBuilder; +import org.onap.aai.sparky.search.filters.entity.SearchFilter; +import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.MediaType; +import org.restlet.data.Status; + +public class AggregateSummaryProcessor { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(AggregateSummaryProcessor.class); + + private static final String KEY_FILTERS = "filters"; + + private SearchAdapter search = null; + + private String vnfAggregationIndexName; + private String elasticSearchIp; + private String elatsticSearchPort; + + public AggregateSummaryProcessor() { + try { + if (search == null) { + search = new SearchAdapter(); + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Failed to get elastic search configuration with error = " + exc.getMessage()); + } + } + + public void setVnfAggregationIndexName(String vnfAggregationIndexName) { + this.vnfAggregationIndexName = vnfAggregationIndexName; + } + + public void setElasticSearchIp(String elasticSearchIp) { + this.elasticSearchIp = elasticSearchIp; + } + + public void setElatsticSearchPort(String elatsticSearchPort) { + this.elatsticSearchPort = elatsticSearchPort; + } + + public void getFilteredAggregation(Exchange exchange) { + + Response response = + exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class); + + Request request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class); + + /* + * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log + * entry which includes client (e.g. browser) information + */ + request.setLoggable(false); + + try { + String payload = exchange.getIn().getBody(String.class); + + if (payload == null || payload.isEmpty()) { + + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, "Request Payload is empty"); + + /* + * Don't throw back an error, just return an empty set + */ + + } else { + + JSONObject parameters = new JSONObject(payload); + + JSONArray requestFilters = null; + if (parameters.has(KEY_FILTERS)) { + requestFilters = parameters.getJSONArray(KEY_FILTERS); + } else { + + JSONObject zeroResponsePayload = new JSONObject(); + zeroResponsePayload.put("count", 0); + response.setStatus(Status.SUCCESS_OK); + response.setEntity(zeroResponsePayload.toString(), MediaType.APPLICATION_JSON); + exchange.getOut().setBody(response); + + LOG.error(AaiUiMsgs.ERROR_FILTERS_NOT_FOUND); + return; + } + + if (requestFilters != null && requestFilters.length() > 0) { + List filtersToQuery = new ArrayList(); + for (int i = 0; i < requestFilters.length(); i++) { + JSONObject filterEntry = requestFilters.getJSONObject(i); + filtersToQuery.add(filterEntry); + } + + String jsonResponsePayload = getVnfFilterAggregations(filtersToQuery); + response.setStatus(Status.SUCCESS_OK); + response.setEntity(jsonResponsePayload, MediaType.APPLICATION_JSON); + exchange.getOut().setBody(response); + + } else { + String emptyResponse = getEmptyAggResponse(); + response.setStatus(Status.SUCCESS_OK); + response.setEntity(emptyResponse, MediaType.APPLICATION_JSON); + exchange.getOut().setBody(response); + LOG.error(AaiUiMsgs.ERROR_FILTERS_NOT_FOUND); + } + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "FilterProcessor failed to get filter list due to error = " + exc.getMessage()); + } + } + + private String getEmptyAggResponse() { + JSONObject aggPayload = new JSONObject(); + aggPayload.put("totalChartHits", 0); + aggPayload.put("buckets", new JSONArray()); + JSONObject payload = new JSONObject(); + payload.append("groupby_aggregation", aggPayload); + + return payload.toString(); + } + + private static final String FILTER_ID_KEY = "filterId"; + private static final String FILTER_VALUE_KEY = "filterValue"; + private static final int DEFAULT_SHOULD_MATCH_SCORE = 1; + private static final String VNF_FILTER_AGGREGATION = "vnfFilterAggregation"; + + + private String getVnfFilterAggregations(List filtersToQuery) throws IOException { + + List searchFilters = new ArrayList(); + for (JSONObject filterEntry : filtersToQuery) { + + String filterId = filterEntry.getString(FILTER_ID_KEY); + if (filterId != null) { + SearchFilter filter = new SearchFilter(); + filter.setFilterId(filterId); + + if (filterEntry.has(FILTER_VALUE_KEY)) { + String filterValue = filterEntry.getString(FILTER_VALUE_KEY); + filter.addValue(filterValue); + } + + searchFilters.add(filter); + } + } + + // Create query for summary by entity type + JsonObject vnfSearch = + FilterQueryBuilder.createCombinedBoolAndAggQuery(searchFilters, DEFAULT_SHOULD_MATCH_SCORE); + + // Parse response for summary by entity type query + OperationResult opResult = + search.doPost(getFullUrl(vnfAggregationIndexName, TierSupportUiConstants.ES_SEARCH_API), + vnfSearch.toString(), DiUiConstants.APP_JSON); + + return buildAggregateVnfResponseJson(opResult.getResult()); + + } + + /** + * Get Full URL for search using elastic search configuration. + * + * @param api the api + * @return the full url + */ + private String getFullUrl(String indexName, String api) { + final String host = elasticSearchIp; + final String port = elatsticSearchPort; + return String.format("http://%s:%s/%s/%s", host, port, indexName, api); + } + + private String buildAggregateVnfResponseJson(String responseJsonStr) { + + JSONObject finalOutputToFe = new JSONObject(); + JSONObject responseJson = new JSONObject(responseJsonStr); + + + JSONObject hits = responseJson.getJSONObject("hits"); + int totalHits = hits.getInt("total"); + finalOutputToFe.put("total", totalHits); + + JSONObject aggregations = responseJson.getJSONObject("aggregations"); + String[] aggKeys = JSONObject.getNames(aggregations); + JSONObject aggregationsList = new JSONObject(); + + for (String aggName : aggKeys) { + JSONObject aggregation = aggregations.getJSONObject(aggName); + JSONArray buckets = aggregation.getJSONArray("buckets"); + aggregationsList.put(aggName, buckets); + } + + finalOutputToFe.put("aggregations", aggregationsList); + + return finalOutputToFe.toString(); + } +} diff --git a/src/main/java/org/onap/aai/sparky/aggregatevnf/search/AggregateVnfSearchProvider.java b/src/main/java/org/onap/aai/sparky/aggregatevnf/search/AggregateVnfSearchProvider.java new file mode 100644 index 0000000..ec3dfaa --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/aggregatevnf/search/AggregateVnfSearchProvider.java @@ -0,0 +1,160 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.aggregatevnf.search; + +import java.util.ArrayList; +import java.util.List; + +import javax.json.JsonObject; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.common.search.CommonSearchSuggestion; +import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; +import org.onap.aai.sparky.dataintegrity.config.DiUiConstants; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.search.api.SearchProvider; +import org.onap.aai.sparky.search.entity.QuerySearchEntity; +import org.onap.aai.sparky.search.entity.SearchSuggestion; +import org.onap.aai.sparky.search.filters.entity.UiFilterValueEntity; +import org.onap.aai.sparky.util.NodeUtils; +import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class AggregateVnfSearchProvider implements SearchProvider { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(AggregateVnfSearchProvider.class); + + private ObjectMapper mapper; + private static SearchAdapter search = null; + + private String autoSuggestIndexName; + private String elasticSearchIp; + private String elatsticSearchPort; + + public AggregateVnfSearchProvider() { + + mapper = new ObjectMapper(); + + try { + if (search == null) { + search = new SearchAdapter(); + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.CONFIGURATION_ERROR, + "Search Configuration Error. Error = " + exc.getMessage()); + } + } + + public void setAutoSuggestIndexName(String autoSuggestIndexName) { + this.autoSuggestIndexName = autoSuggestIndexName; + } + + public void setElasticSearchIp(String elasticSearchIp) { + this.elasticSearchIp = elasticSearchIp; + } + + public void setElatsticSearchPort(String elatsticSearchPort) { + this.elatsticSearchPort = elatsticSearchPort; + } + + /** + * Get Full URL for search using elastic search configuration. + * + * @param api the api + * @return the full url + */ + private String getFullUrl(String indexName, String api) { + final String host = elasticSearchIp; + final String port = elatsticSearchPort; + return String.format("http://%s:%s/%s/%s", host, port, indexName, api); + } + + @Override + public List search(QuerySearchEntity queryRequest) { + + List returnList = new ArrayList(); + + try { + + /* Create suggestions query */ + JsonObject vnfSearch = VnfSearchQueryBuilder.createSuggestionsQuery( + String.valueOf(queryRequest.getMaxResults()), queryRequest.getQueryStr()); + + /* Parse suggestions response */ + OperationResult opResult = + search.doPost(getFullUrl(autoSuggestIndexName, TierSupportUiConstants.ES_SUGGEST_API), + vnfSearch.toString(), DiUiConstants.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) { + CommonSearchSuggestion responseSuggestion = new CommonSearchSuggestion(); + 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"))); + + // Extract filter list from JSON and add to response suggestion + JSONObject payload = querySuggestion.getJSONObject("payload"); + if (payload.length() > 0) { + JSONArray filterList = payload.getJSONArray("filterList"); + for (int filter = 0; filter < filterList.length(); filter++) { + String filterValueString = filterList.getJSONObject(filter).toString(); + UiFilterValueEntity filterValue = + mapper.readValue(filterValueString, UiFilterValueEntity.class); + responseSuggestion.getFilterValues().add(filterValue); + } + } + returnList.add(responseSuggestion); + } + } + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, "Search failed due to error = " + exc.getMessage()); + } + + return returnList; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/search/VnfSearchQueryBuilder.java b/src/main/java/org/onap/aai/sparky/aggregatevnf/search/VnfSearchQueryBuilder.java similarity index 91% rename from src/main/java/org/onap/aai/sparky/search/VnfSearchQueryBuilder.java rename to src/main/java/org/onap/aai/sparky/aggregatevnf/search/VnfSearchQueryBuilder.java index 9e206b3..96fea3f 100644 --- a/src/main/java/org/onap/aai/sparky/search/VnfSearchQueryBuilder.java +++ b/src/main/java/org/onap/aai/sparky/aggregatevnf/search/VnfSearchQueryBuilder.java @@ -20,9 +20,8 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.search; +package org.onap.aai.sparky.aggregatevnf.search; -import java.util.Date; import java.util.Map; import javax.json.Json; @@ -31,11 +30,23 @@ import javax.json.JsonArrayBuilder; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; +import org.onap.aai.sparky.dataintegrity.config.DiUiConstants; + /** * Build a JSON payload to send to elastic search to get vnf search data. */ public class VnfSearchQueryBuilder { + static final String SEVERITY = DiUiConstants.SEVERITY; + static final String TIMESTAMP = DiUiConstants.KEY_TIMESTAMP; + static final String VIOLATIONS = DiUiConstants.VIOLATIONS; + static final String CATEGORY = DiUiConstants.CATEGORY; + static final String ENTITY_TYPE = DiUiConstants.ENTITY_TYPE; + + static final String ITEM = DiUiConstants.KEY_ITEM; + static final String ITEM_AGG = DiUiConstants.KEY_ITEM_AGG; + static final String BY_ITEM = DiUiConstants.KEY_BY_ITEM; + static final String BUCKETS = DiUiConstants.KEY_BUCKETS; /** * Creates the suggestions query. @@ -170,5 +181,4 @@ public class VnfSearchQueryBuilder { return jsonBuilder.build(); } - } diff --git a/src/main/java/org/onap/aai/sparky/aggregation/sync/AggregationSyncControllerFactory.java b/src/main/java/org/onap/aai/sparky/aggregation/sync/AggregationSyncControllerFactory.java new file mode 100644 index 0000000..6d8decf --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/aggregation/sync/AggregationSyncControllerFactory.java @@ -0,0 +1,232 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.aggregation.sync; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.config.oxm.SuggestionEntityDescriptor; +import org.onap.aai.sparky.config.oxm.SuggestionEntityLookup; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.ElasticSearchIndexCleaner; +import org.onap.aai.sparky.sync.ElasticSearchSchemaFactory; +import org.onap.aai.sparky.sync.IndexCleaner; +import org.onap.aai.sparky.sync.IndexIntegrityValidator; +import org.onap.aai.sparky.sync.SyncController; +import org.onap.aai.sparky.sync.SyncControllerImpl; +import org.onap.aai.sparky.sync.SyncControllerRegistrar; +import org.onap.aai.sparky.sync.SyncControllerRegistry; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.config.SyncControllerConfig; + +public class AggregationSyncControllerFactory implements SyncControllerRegistrar { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(AggregationSyncControllerFactory.class); + + private ActiveInventoryAdapter aaiAdapter; + private ElasticSearchAdapter esAdapter; + private SuggestionEntityLookup suggestionEntityLookup; + + private Map aggregationEntityToIndexMap; + private Map indexNameToSchemaConfigMap; + + private ElasticSearchEndpointConfig elasticSearchEndpointConfig; + private SyncControllerConfig syncControllerConfig; + private SyncControllerRegistry syncControllerRegistry; + private NetworkStatisticsConfig aaiStatConfig; + private NetworkStatisticsConfig esStatConfig; + + private List syncControllers; + + public AggregationSyncControllerFactory(ElasticSearchEndpointConfig esEndpointConfig, + SyncControllerConfig syncControllerConfig, SyncControllerRegistry syncControllerRegistry, + SuggestionEntityLookup suggestionEntityLookup) { + this.syncControllers = new ArrayList(); + this.elasticSearchEndpointConfig = esEndpointConfig; + this.syncControllerConfig = syncControllerConfig; + this.syncControllerRegistry = syncControllerRegistry; + this.suggestionEntityLookup = suggestionEntityLookup; + } + + public NetworkStatisticsConfig getAaiStatConfig() { + return aaiStatConfig; + } + + public void setAaiStatConfig(NetworkStatisticsConfig aaiStatConfig) { + this.aaiStatConfig = aaiStatConfig; + } + + public NetworkStatisticsConfig getEsStatConfig() { + return esStatConfig; + } + + public void setEsStatConfig(NetworkStatisticsConfig esStatConfig) { + this.esStatConfig = esStatConfig; + } + + public Map getIndexNameToSchemaConfigMap() { + return indexNameToSchemaConfigMap; + } + + public void setIndexNameToSchemaConfigMap( + Map indexNameToSchemaConfigMap) { + this.indexNameToSchemaConfigMap = indexNameToSchemaConfigMap; + } + + public ElasticSearchEndpointConfig getElasticSearchEndpointConfig() { + return elasticSearchEndpointConfig; + } + + public void setElasticSearchEndpointConfig( + ElasticSearchEndpointConfig elasticSearchEndpointConfig) { + this.elasticSearchEndpointConfig = elasticSearchEndpointConfig; + } + + public SyncControllerConfig getSyncControllerConfig() { + return syncControllerConfig; + } + + public void setSyncControllerConfig(SyncControllerConfig syncControllerConfig) { + this.syncControllerConfig = syncControllerConfig; + } + + public ActiveInventoryAdapter getAaiAdapter() { + return aaiAdapter; + } + + public void setAaiAdapter(ActiveInventoryAdapter aaiAdapter) { + this.aaiAdapter = aaiAdapter; + } + + public ElasticSearchAdapter getEsAdapter() { + return esAdapter; + } + + public void setEsAdapter(ElasticSearchAdapter esAdapter) { + this.esAdapter = esAdapter; + } + + public SuggestionEntityLookup getSuggestionEntityLookup() { + return suggestionEntityLookup; + } + + public void setSuggestionEntityLookup(SuggestionEntityLookup suggestionEntityLookup) { + this.suggestionEntityLookup = suggestionEntityLookup; + } + + public Map getAggregationEntityToIndexMap() { + return aggregationEntityToIndexMap; + } + + public void setAggregationEntityToIndexMap(Map aggregationEntityToIndexMap) { + this.aggregationEntityToIndexMap = aggregationEntityToIndexMap; + } + + public void buildControllers() { + + if (syncControllerConfig.isEnabled()) { + + Map suggestionEntitites = + suggestionEntityLookup.getSuggestionSearchEntityDescriptors(); + SyncControllerImpl aggregationSyncController = null; + + for (String entityType : suggestionEntitites.keySet()) { + + String indexName = aggregationEntityToIndexMap.get(entityType); + + if (indexName == null) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Could not determine aggregation index name" + " for entity type: " + entityType); + continue; + } + + try { + + aggregationSyncController = new SyncControllerImpl(syncControllerConfig, entityType); + + ElasticSearchSchemaConfig schemaConfig = indexNameToSchemaConfigMap.get(indexName); + + if (schemaConfig == null) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Could not determine elastic search schema config for index name: " + indexName); + continue; + } + + IndexIntegrityValidator aggregationIndexValidator = + new IndexIntegrityValidator(esAdapter, schemaConfig, elasticSearchEndpointConfig, + ElasticSearchSchemaFactory.getIndexSchema(schemaConfig)); + + aggregationSyncController.registerIndexValidator(aggregationIndexValidator); + + AggregationSynchronizer aggSynchronizer = new AggregationSynchronizer(entityType, + schemaConfig, syncControllerConfig.getNumInternalSyncWorkers(), + syncControllerConfig.getNumSyncActiveInventoryWorkers(), + syncControllerConfig.getNumSyncElasticWorkers(), aaiStatConfig, esStatConfig); + + aggSynchronizer.setAaiAdapter(aaiAdapter); + aggSynchronizer.setElasticSearchAdapter(esAdapter); + + aggregationSyncController.registerEntitySynchronizer(aggSynchronizer); + + IndexCleaner entityDataIndexCleaner = + new ElasticSearchIndexCleaner(esAdapter, elasticSearchEndpointConfig, schemaConfig); + + aggregationSyncController.registerIndexCleaner(entityDataIndexCleaner); + + syncControllers.add(aggregationSyncController); + } catch (Exception exc) { + + exc.printStackTrace(); + + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Failed to build aggregation sync controller. Error : " + exc.getMessage()); + } + + } + } else { + LOG.info(AaiUiMsgs.INFO_GENERIC, "Sync controller with name = " + + syncControllerConfig.getControllerName() + " is disabled"); + } + } + + @Override + public void registerController() { + + buildControllers(); + + if (syncControllerRegistry != null) { + for (SyncController controller : syncControllers) { + syncControllerRegistry.registerSyncController(controller); + } + } + + } +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/AggregationSynchronizer.java b/src/main/java/org/onap/aai/sparky/aggregation/sync/AggregationSynchronizer.java similarity index 87% rename from src/main/java/org/onap/aai/sparky/synchronizer/AggregationSynchronizer.java rename to src/main/java/org/onap/aai/sparky/aggregation/sync/AggregationSynchronizer.java index 817e633..2a115db 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/AggregationSynchronizer.java +++ b/src/main/java/org/onap/aai/sparky/aggregation/sync/AggregationSynchronizer.java @@ -20,56 +20,50 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.aggregation.sync; 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.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; import org.onap.aai.sparky.dal.NetworkTransaction; import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.config.SynchronizerConfiguration; -import org.onap.aai.sparky.synchronizer.entity.AggregationEntity; -import org.onap.aai.sparky.synchronizer.entity.MergableEntity; -import org.onap.aai.sparky.synchronizer.entity.SelfLinkDescriptor; -import org.onap.aai.sparky.synchronizer.enumeration.OperationState; -import org.onap.aai.sparky.synchronizer.enumeration.SynchronizerState; -import org.onap.aai.sparky.synchronizer.task.PerformActiveInventoryRetrieval; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchPut; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchRetrieval; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchUpdate; +import org.onap.aai.sparky.sync.AbstractEntitySynchronizer; +import org.onap.aai.sparky.sync.IndexSynchronizer; +import org.onap.aai.sparky.sync.SynchronizerConstants; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.entity.AggregationEntity; +import org.onap.aai.sparky.sync.entity.MergableEntity; +import org.onap.aai.sparky.sync.entity.SelfLinkDescriptor; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.onap.aai.sparky.sync.task.PerformActiveInventoryRetrieval; +import org.onap.aai.sparky.sync.task.PerformElasticSearchPut; +import org.onap.aai.sparky.sync.task.PerformElasticSearchRetrieval; +import org.onap.aai.sparky.sync.task.PerformElasticSearchUpdate; import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import org.slf4j.MDC; -import org.onap.aai.cl.mdc.MdcContext; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectReader; @@ -121,6 +115,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer private boolean syncInProgress; private Map contextMap; private String entityType; + private ElasticSearchSchemaConfig schemaConfig; /** * Instantiates a new entity aggregation synchronizer. @@ -128,11 +123,19 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer * @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 - + public AggregationSynchronizer(String entityType, ElasticSearchSchemaConfig schemaConfig, + int numSyncWorkers, int numActiveInventoryWorkers, int numElasticWorkers, + NetworkStatisticsConfig aaiStatConfig, NetworkStatisticsConfig esStatConfig) + throws Exception { + + super(LOG, "AGGES-" + schemaConfig.getIndexName().toUpperCase(), numSyncWorkers, + numActiveInventoryWorkers, numElasticWorkers, schemaConfig.getIndexName(), aaiStatConfig, + esStatConfig); // multiple + // Autosuggestion + // Entity Synchronizer will + // run for different indices + + this.schemaConfig = schemaConfig; this.entityType = entityType; this.allWorkEnumerated = false; this.entityCounters = new ConcurrentHashMap(); @@ -145,10 +148,10 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer 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.aaiEntityStats.intializeEntityCounters(entityType); + this.esEntityStats.intializeEntityCounters(entityType); + this.contextMap = MDC.getCopyOfContextMap(); } @@ -171,11 +174,13 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer MDC.setContextMap(contextMap); OperationResult typeLinksResult = null; try { - typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(entity); + typeLinksResult = aaiAdapter.getSelfLinksByEntityType(entity); aaiWorkOnHand.decrementAndGet(); processEntityTypeSelfLinks(typeLinksResult); } catch (Exception exc) { // TODO -> LOG, what should be logged here? + + exc.printStackTrace(); } return null; @@ -255,7 +260,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer * called incrementAndGet when queuing the failed PUT! */ - supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, esDataProvider), + supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, elasticSearchAdapter), esExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -341,7 +346,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer 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()); + MergableEntity merged = updater.readValue(ae.getAsJson()); jsonPayload = mapper.writeValueAsString(merged); } } catch (IOException exc) { @@ -352,14 +357,15 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer return; } } else { - jsonPayload = ae.getIndexDocumentJson(); + jsonPayload = ae.getAsJson(); } if (wasEntryDiscovered) { if (versionNumber != null && jsonPayload != null) { - String requestPayload = esDataProvider.buildBulkImportOperationRequest(getIndexName(), - ElasticSearchConfig.getConfig().getType(), ae.getId(), versionNumber, jsonPayload); + String requestPayload = + elasticSearchAdapter.buildBulkImportOperationRequest(schemaConfig.getIndexName(), + schemaConfig.getIndexDocType(), ae.getId(), versionNumber, jsonPayload); NetworkTransaction transactionTracker = new NetworkTransaction(); transactionTracker.setEntityType(esGetTxn.getEntityType()); @@ -368,7 +374,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer esWorkOnHand.incrementAndGet(); supplyAsync(new PerformElasticSearchUpdate(ElasticSearchConfig.getConfig().getBulkUrl(), - requestPayload, esDataProvider, transactionTracker), esPutExecutor) + requestPayload, elasticSearchAdapter, transactionTracker), esPutExecutor) .whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -394,7 +400,8 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer updateElasticTxn.setOperationType(HttpMethod.PUT); esWorkOnHand.incrementAndGet(); - supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, esDataProvider), + supplyAsync( + new PerformElasticSearchPut(jsonPayload, updateElasticTxn, elasticSearchAdapter), esPutExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -493,7 +500,8 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { - descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + descriptor = OxmEntityLookup.getInstance().getEntityDescriptors() + .get(linkDescriptor.getEntityType()); if (descriptor == null) { LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); @@ -509,7 +517,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer aaiWorkOnHand.incrementAndGet(); - supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiAdapter), aaiExecutor) .whenComplete((result, error) -> { aaiWorkOnHand.decrementAndGet(); @@ -549,7 +557,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer final String jsonResult = txn.getOperationResult().getResult(); if (jsonResult != null && jsonResult.length() > 0) { - AggregationEntity ae = new AggregationEntity(oxmModelLoader); + AggregationEntity ae = new AggregationEntity(); ae.setLink(ActiveInventoryConfig.extractResourcePath(txn.getLink())); populateAggregationEntityDocument(ae, jsonResult, txn.getDescriptor()); ae.deriveFields(); @@ -570,7 +578,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer esWorkOnHand.incrementAndGet(); - supplyAsync(new PerformElasticSearchRetrieval(n2, esDataProvider), esExecutor) + supplyAsync(new PerformElasticSearchRetrieval(n2, elasticSearchAdapter), esExecutor) .whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -629,7 +637,6 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer String message = "Could not deserialize JSON (representing operation result) as node tree. " + "Operation result = " + jsonResult + ". " + exc.getLocalizedMessage(); LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, message); - return; } JsonNode resultData = rootNode.get("result-data"); @@ -651,7 +658,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer if (resourceType != null && resourceLink != null) { - descriptor = oxmModelLoader.getEntityDescriptor(resourceType); + descriptor = OxmEntityLookup.getInstance().getEntityDescriptors().get(resourceType); if (descriptor == null) { LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); @@ -660,7 +667,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer } selflinks.add(new SelfLinkDescriptor(resourceLink, - SynchronizerConfiguration.NODES_ONLY_MODIFIER, resourceType)); + SynchronizerConstants.NODES_ONLY_MODIFIER, resourceType)); } @@ -673,7 +680,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#doSync() + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() */ @Override public OperationState doSync() { @@ -699,7 +706,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) */ @Override public String getStatReport(boolean showFinalReport) { @@ -718,7 +725,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#shutdown() + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() */ @Override public void shutdown() { @@ -747,7 +754,7 @@ public class AggregationSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.AbstractEntitySynchronizer#clearCache() + * @see org.openecomp.sparky.synchronizer.AbstractEntitySynchronizer#clearCache() */ @Override public void clearCache() { diff --git a/src/main/java/org/onap/aai/sparky/aggregation/sync/HistoricalEntitySummarizer.java b/src/main/java/org/onap/aai/sparky/aggregation/sync/HistoricalEntitySummarizer.java new file mode 100644 index 0000000..5ee11be --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/aggregation/sync/HistoricalEntitySummarizer.java @@ -0,0 +1,391 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.aggregation.sync; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +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 javax.ws.rs.core.MediaType; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.config.oxm.SearchableEntityLookup; +import org.onap.aai.sparky.config.oxm.SearchableOxmEntityDescriptor; +import org.onap.aai.sparky.dal.rest.HttpMethod; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.AbstractEntitySynchronizer; +import org.onap.aai.sparky.sync.IndexSynchronizer; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.onap.aai.sparky.util.NodeUtils; +import org.slf4j.MDC; + +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; + private ElasticSearchSchemaConfig schemaConfig; + + /** + * Instantiates a new historical entity summarizer. + * + * @param indexName the index name + * @throws Exception the exception + */ + public HistoricalEntitySummarizer(ElasticSearchSchemaConfig schemaConfig, int internalSyncWorkers, + int aaiWorkers, int esWorkers, NetworkStatisticsConfig aaiStatConfig, + NetworkStatisticsConfig esStatConfig) throws Exception { + super(LOG, "HES", internalSyncWorkers, aaiWorkers, esWorkers, schemaConfig.getIndexName(), + aaiStatConfig, esStatConfig); + + this.schemaConfig = schemaConfig; + 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(); + this.syncDurationInMs = -1; + } + + /** + * Collect all the work. + * + * @return the operation state + */ + private OperationState collectAllTheWork() { + + Map descriptorMap = + SearchableEntityLookup.getInstance().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 = aaiAdapter.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() { + this.syncDurationInMs = -1; + 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 = + elasticSearchAdapter.doPost(link, jsonString, MediaType.APPLICATION_JSON_TYPE); + 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) { + syncDurationInMs = System.currentTimeMillis() - syncStartedTimeStampInMs; + return this.getStatReport(syncDurationInMs, 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/onap/aai/sparky/aggregation/sync/HistoricalEntitySyncController.java b/src/main/java/org/onap/aai/sparky/aggregation/sync/HistoricalEntitySyncController.java new file mode 100644 index 0000000..1f7db2e --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/aggregation/sync/HistoricalEntitySyncController.java @@ -0,0 +1,90 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.aggregation.sync; + +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.sync.ElasticSearchSchemaFactory; +import org.onap.aai.sparky.sync.IndexIntegrityValidator; +import org.onap.aai.sparky.sync.SyncControllerImpl; +import org.onap.aai.sparky.sync.SyncControllerRegistrar; +import org.onap.aai.sparky.sync.SyncControllerRegistry; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.config.SyncControllerConfig; + +public class HistoricalEntitySyncController extends SyncControllerImpl + implements SyncControllerRegistrar { + + private SyncControllerRegistry syncControllerRegistry; + + public HistoricalEntitySyncController(SyncControllerConfig syncControllerConfig, + ActiveInventoryAdapter aaiAdapter, ElasticSearchAdapter esAdapter, + ElasticSearchSchemaConfig schemaConfig, ElasticSearchEndpointConfig endpointConfig, + int syncFrequencyInMinutes, NetworkStatisticsConfig aaiStatConfig, + NetworkStatisticsConfig esStatConfig) throws Exception { + super(syncControllerConfig); + + // final String controllerName = "Historical Entity Count Synchronizer"; + + long taskFrequencyInMs = syncFrequencyInMinutes * 60 * 1000; + + setDelayInMs(taskFrequencyInMs); + setSyncFrequencyInMs(taskFrequencyInMs); + + IndexIntegrityValidator entityCounterHistoryValidator = new IndexIntegrityValidator(esAdapter, + schemaConfig, endpointConfig, ElasticSearchSchemaFactory.getIndexSchema(schemaConfig)); + + registerIndexValidator(entityCounterHistoryValidator); + + HistoricalEntitySummarizer historicalSummarizer = new HistoricalEntitySummarizer(schemaConfig, + syncControllerConfig.getNumInternalSyncWorkers(), + syncControllerConfig.getNumSyncActiveInventoryWorkers(), + syncControllerConfig.getNumSyncElasticWorkers(), aaiStatConfig, esStatConfig); + + historicalSummarizer.setAaiAdapter(aaiAdapter); + historicalSummarizer.setElasticSearchAdapter(esAdapter); + + registerEntitySynchronizer(historicalSummarizer); + + } + + public SyncControllerRegistry getSyncControllerRegistry() { + return syncControllerRegistry; + } + + public void setSyncControllerRegistry(SyncControllerRegistry syncControllerRegistry) { + this.syncControllerRegistry = syncControllerRegistry; + } + + @Override + public void registerController() { + if (syncControllerRegistry != null) { + if (syncControllerConfig.isEnabled()) { + syncControllerRegistry.registerSyncController(this); + } + } + + } +} diff --git a/src/main/java/org/onap/aai/sparky/analytics/AbstractStatistics.java b/src/main/java/org/onap/aai/sparky/analytics/AbstractStatistics.java index 9d2fec6..6e7d854 100644 --- a/src/main/java/org/onap/aai/sparky/analytics/AbstractStatistics.java +++ b/src/main/java/org/onap/aai/sparky/analytics/AbstractStatistics.java @@ -20,6 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + package org.onap.aai.sparky.analytics; import java.util.HashMap; @@ -31,35 +32,6 @@ import java.util.concurrent.atomic.AtomicInteger; public class AbstractStatistics implements ComponentStatistics { private HashMap namedCounters; - - /** - * @return the namedCounters - */ - public HashMap getNamedCounters() { - return namedCounters; - } - - /** - * @param namedCounters the namedCounters to set - */ - public void setNamedCounters(HashMap namedCounters) { - this.namedCounters = namedCounters; - } - - /** - * @return the namedHistograms - */ - public HashMap getNamedHistograms() { - return namedHistograms; - } - - /** - * @param namedHistograms the namedHistograms to set - */ - public void setNamedHistograms(HashMap namedHistograms) { - this.namedHistograms = namedHistograms; - } - private HashMap namedHistograms; /** @@ -73,7 +45,7 @@ public class AbstractStatistics implements ComponentStatistics { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.analytics.ComponentStatistics#addCounter(java.lang.String) + * @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 @@ -96,7 +68,7 @@ public class AbstractStatistics implements ComponentStatistics { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.analytics.ComponentStatistics#pegCounter(java.lang.String) + * @see org.openecomp.sparky.analytics.ComponentStatistics#pegCounter(java.lang.String) */ @Override public void pegCounter(String key) { @@ -112,7 +84,7 @@ public class AbstractStatistics implements ComponentStatistics { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.analytics.ComponentStatistics#incrementCounter(java.lang.String, int) + * @see org.openecomp.sparky.analytics.ComponentStatistics#incrementCounter(java.lang.String, int) */ @Override public void incrementCounter(String key, int value) { @@ -129,7 +101,7 @@ public class AbstractStatistics implements ComponentStatistics { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.analytics.ComponentStatistics#addHistogram(java.lang.String, + * @see org.openecomp.sparky.analytics.ComponentStatistics#addHistogram(java.lang.String, * java.lang.String, long, int, int) */ @Override @@ -147,7 +119,7 @@ public class AbstractStatistics implements ComponentStatistics { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.analytics.ComponentStatistics#updateHistogram(java.lang.String, long) + * @see org.openecomp.sparky.analytics.ComponentStatistics#updateHistogram(java.lang.String, long) */ @Override public void updateHistogram(String key, long value) { @@ -161,7 +133,7 @@ public class AbstractStatistics implements ComponentStatistics { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.analytics.ComponentStatistics#reset() + * @see org.openecomp.sparky.analytics.ComponentStatistics#reset() */ @Override public void reset() { diff --git a/src/main/java/org/onap/aai/sparky/analytics/HistoricalCounter.java b/src/main/java/org/onap/aai/sparky/analytics/HistoricalCounter.java index 622693c..50941cc 100644 --- a/src/main/java/org/onap/aai/sparky/analytics/HistoricalCounter.java +++ b/src/main/java/org/onap/aai/sparky/analytics/HistoricalCounter.java @@ -20,7 +20,6 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ - package org.onap.aai.sparky.analytics; /** @@ -33,62 +32,6 @@ public class HistoricalCounter { private double min; - /** - * @return the totalOfSamples - */ - public double getTotalOfSamples() { - return totalOfSamples; - } - - /** - * @param totalOfSamples the totalOfSamples to set - */ - public void setTotalOfSamples(double totalOfSamples) { - this.totalOfSamples = totalOfSamples; - } - - /** - * @return the maintainSingleValue - */ - public boolean isMaintainSingleValue() { - return maintainSingleValue; - } - - /** - * @param maintainSingleValue the maintainSingleValue to set - */ - public void setMaintainSingleValue(boolean maintainSingleValue) { - this.maintainSingleValue = maintainSingleValue; - } - - /** - * @param min the min to set - */ - public void setMin(double min) { - this.min = min; - } - - /** - * @param max the max to set - */ - public void setMax(double max) { - this.max = max; - } - - /** - * @param numSamples the numSamples to set - */ - public void setNumSamples(long numSamples) { - this.numSamples = numSamples; - } - - /** - * @param value the value to set - */ - public void setValue(double value) { - this.value = value; - } - private double max; private double totalOfSamples; @@ -175,6 +118,31 @@ public class HistoricalCounter { return (totalOfSamples / numSamples); } + public void setMin(double min) { + this.min = min; + } + + public void setMax(double max) { + this.max = max; + } + + public double getTotalOfSamples() { + return totalOfSamples; + } + + public void setTotalOfSamples(double totalOfSamples) { + this.totalOfSamples = totalOfSamples; + } + + public void setNumSamples(long numSamples) { + this.numSamples = numSamples; + } + + public void setMaintainSingleValue(boolean maintainSingleValue) { + this.maintainSingleValue = maintainSingleValue; + } + + /** * Reset. */ diff --git a/src/main/java/org/onap/aai/sparky/autosuggestion/sync/AutoSuggestionSyncController.java b/src/main/java/org/onap/aai/sparky/autosuggestion/sync/AutoSuggestionSyncController.java new file mode 100644 index 0000000..950eb45 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/autosuggestion/sync/AutoSuggestionSyncController.java @@ -0,0 +1,97 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.autosuggestion.sync; + +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.sync.ElasticSearchIndexCleaner; +import org.onap.aai.sparky.sync.ElasticSearchSchemaFactory; +import org.onap.aai.sparky.sync.IndexCleaner; +import org.onap.aai.sparky.sync.IndexIntegrityValidator; +import org.onap.aai.sparky.sync.SyncControllerImpl; +import org.onap.aai.sparky.sync.SyncControllerRegistrar; +import org.onap.aai.sparky.sync.SyncControllerRegistry; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.config.SyncControllerConfig; + +public class AutoSuggestionSyncController extends SyncControllerImpl + implements SyncControllerRegistrar { + + private SyncControllerRegistry syncControllerRegistry; + + public AutoSuggestionSyncController(SyncControllerConfig syncControllerConfig, + ActiveInventoryAdapter aaiAdapter, ElasticSearchAdapter esAdapter, + ElasticSearchSchemaConfig schemaConfig, ElasticSearchEndpointConfig endpointConfig, + NetworkStatisticsConfig aaiStatConfig, NetworkStatisticsConfig esStatConfig) + throws Exception { + super(syncControllerConfig); + + // final String controllerName = "Auto Suggestion Synchronizer"; + + IndexIntegrityValidator autoSuggestionIndexValidator = new IndexIntegrityValidator(esAdapter, + schemaConfig, endpointConfig, ElasticSearchSchemaFactory.getIndexSchema(schemaConfig)); + + registerIndexValidator(autoSuggestionIndexValidator); + + AutosuggestionSynchronizer suggestionSynchronizer = new AutosuggestionSynchronizer(schemaConfig, + syncControllerConfig.getNumInternalSyncWorkers(), + syncControllerConfig.getNumSyncActiveInventoryWorkers(), + syncControllerConfig.getNumSyncElasticWorkers(), aaiStatConfig, esStatConfig); + + suggestionSynchronizer.setAaiAdapter(aaiAdapter); + suggestionSynchronizer.setElasticSearchAdapter(esAdapter); + + registerEntitySynchronizer(suggestionSynchronizer); + + IndexCleaner autosuggestIndexCleaner = + new ElasticSearchIndexCleaner(esAdapter, endpointConfig, schemaConfig); + + registerIndexCleaner(autosuggestIndexCleaner); + + } + + public SyncControllerRegistry getSyncControllerRegistry() { + return syncControllerRegistry; + } + + + + public void setSyncControllerRegistry(SyncControllerRegistry syncControllerRegistry) { + this.syncControllerRegistry = syncControllerRegistry; + } + + + + @Override + public void registerController() { + + if (syncControllerRegistry != null) { + if (syncControllerConfig.isEnabled()) { + syncControllerRegistry.registerSyncController(this); + } + } + + } +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/AutosuggestionSynchronizer.java b/src/main/java/org/onap/aai/sparky/autosuggestion/sync/AutosuggestionSynchronizer.java similarity index 87% rename from src/main/java/org/onap/aai/sparky/synchronizer/AutosuggestionSynchronizer.java rename to src/main/java/org/onap/aai/sparky/autosuggestion/sync/AutosuggestionSynchronizer.java index 328fb97..4ce7ce3 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/AutosuggestionSynchronizer.java +++ b/src/main/java/org/onap/aai/sparky/autosuggestion/sync/AutosuggestionSynchronizer.java @@ -20,7 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.autosuggestion.sync; import static java.util.concurrent.CompletableFuture.supplyAsync; @@ -40,25 +40,32 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; +import org.onap.aai.sparky.config.oxm.SuggestionEntityDescriptor; +import org.onap.aai.sparky.config.oxm.SuggestionEntityLookup; import org.onap.aai.sparky.dal.NetworkTransaction; import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.config.SynchronizerConfiguration; -import org.onap.aai.sparky.synchronizer.entity.SelfLinkDescriptor; -import org.onap.aai.sparky.synchronizer.entity.SuggestionSearchEntity; -import org.onap.aai.sparky.synchronizer.enumeration.OperationState; -import org.onap.aai.sparky.synchronizer.enumeration.SynchronizerState; -import org.onap.aai.sparky.synchronizer.task.PerformActiveInventoryRetrieval; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchPut; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchRetrieval; +import org.onap.aai.sparky.sync.AbstractEntitySynchronizer; +import org.onap.aai.sparky.sync.IndexSynchronizer; +import org.onap.aai.sparky.sync.SynchronizerConstants; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.entity.SelfLinkDescriptor; +import org.onap.aai.sparky.sync.entity.SuggestionSearchEntity; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.onap.aai.sparky.sync.task.PerformActiveInventoryRetrieval; +import org.onap.aai.sparky.sync.task.PerformElasticSearchPut; +import org.onap.aai.sparky.sync.task.PerformElasticSearchRetrieval; import org.onap.aai.sparky.util.NodeUtils; import org.onap.aai.sparky.util.SuggestionsPermutation; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; -import org.onap.aai.cl.mdc.MdcContext; import org.slf4j.MDC; import com.fasterxml.jackson.core.JsonProcessingException; @@ -114,10 +121,11 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer * @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 + public AutosuggestionSynchronizer(ElasticSearchSchemaConfig schemaConfig, int internalSyncWorkers, + int aaiWorkers, int esWorkers, NetworkStatisticsConfig aaiStatConfig, + NetworkStatisticsConfig esStatConfig) throws Exception { + super(LOG, "ASES-" + schemaConfig.getIndexName().toUpperCase(), internalSyncWorkers, aaiWorkers, + esWorkers, schemaConfig.getIndexName(), aaiStatConfig, esStatConfig); this.allWorkEnumerated = false; this.selflinks = new ConcurrentLinkedDeque(); @@ -137,8 +145,8 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer */ private OperationState collectAllTheWork() { final Map contextMap = MDC.getCopyOfContextMap(); - Map descriptorMap = - oxmModelLoader.getSuggestionSearchEntityDescriptors(); + Map descriptorMap = + SuggestionEntityLookup.getInstance().getSuggestionSearchEntityDescriptors(); if (descriptorMap.isEmpty()) { LOG.error(AaiUiMsgs.ERROR_LOADING_OXM_SUGGESTIBLE_ENTITIES); @@ -166,7 +174,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer MDC.setContextMap(contextMap); OperationResult typeLinksResult = null; try { - typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(key); + typeLinksResult = aaiAdapter.getSelfLinksByEntityType(key); aaiWorkOnHand.decrementAndGet(); processEntityTypeSelfLinks(typeLinksResult); } catch (Exception exc) { @@ -221,7 +229,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#doSync() + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() */ @Override public OperationState doSync() { @@ -252,7 +260,6 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer String message = "Could not deserialize JSON (representing operation result) as node tree. " + "Operation result = " + jsonResult + ". " + exc.getLocalizedMessage(); LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, message); - return; } JsonNode resultData = rootNode.get("result-data"); @@ -274,7 +281,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer if (resourceType != null && resourceLink != null) { - descriptor = oxmModelLoader.getEntityDescriptor(resourceType); + descriptor = OxmEntityLookup.getInstance().getEntityDescriptors().get(resourceType); if (descriptor == null) { LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); @@ -282,7 +289,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer continue; } selflinks.add(new SelfLinkDescriptor(resourceLink, - SynchronizerConfiguration.NODES_ONLY_MODIFIER, resourceType)); + SynchronizerConstants.NODES_ONLY_MODIFIER, resourceType)); } @@ -305,7 +312,8 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { - descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + descriptor = OxmEntityLookup.getInstance().getEntityDescriptors() + .get(linkDescriptor.getEntityType()); if (descriptor == null) { LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); @@ -321,7 +329,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer aaiWorkOnHand.incrementAndGet(); - supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiAdapter), aaiExecutor) .whenComplete((result, error) -> { aaiWorkOnHand.decrementAndGet(); @@ -354,9 +362,10 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer * * @return List of all valid suggestion attributes(key's) */ - public List getSuggestionFromReponse(JsonNode node, String entityName) { + public List getSuggestableAttrNamesFromReponse(JsonNode node, String entityName) { List suggestableAttr = new ArrayList(); - HashMap desc = oxmModelLoader.getOxmModel().get(entityName); + HashMap desc = + SuggestionEntityLookup.getInstance().getSuggestionSearchEntityOxmModel().get(entityName); String attr = desc.get("suggestibleAttributes"); suggestableAttr = Arrays.asList(attr.split(",")); List suggestableValue = new ArrayList<>(); @@ -389,17 +398,19 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer String entityName = txn.getDescriptor().getEntityName(); JsonNode entityNode = mapper.readTree(jsonResult); - SuggestionsPermutation suggPermutation = new SuggestionsPermutation(); - ArrayList> uniqueLists = suggPermutation - .getSuggestionsPermutation(getSuggestionFromReponse(entityNode, entityName)); + List availableSuggestableAttrName = + getSuggestableAttrNamesFromReponse(entityNode, entityName); + ArrayList> uniqueLists = + SuggestionsPermutation.getNonEmptyUniqueLists(availableSuggestableAttrName); // 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); + + SuggestionSearchEntity sse = + new SuggestionSearchEntity(SuggestionEntityLookup.getInstance()); sse.setSuggestableAttr(uniqueList); - sse.setPayloadFromResponse(entityNode); - sse.setLink(txn.getLink()); + sse.setFilterBasedPayloadFromResponse(entityNode, entityName, uniqueList); sse.setLink(ActiveInventoryConfig.extractResourcePath(txn.getLink())); populateSuggestionSearchEntityDocument(sse, jsonResult, txn); // The unique id for the document will be created at derive fields @@ -422,7 +433,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer esWorkOnHand.incrementAndGet(); - supplyAsync(new PerformElasticSearchRetrieval(n2, esDataProvider), esExecutor) + supplyAsync(new PerformElasticSearchRetrieval(n2, elasticSearchAdapter), esExecutor) .whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -457,7 +468,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer List primaryKeyValues = new ArrayList(); String pkeyValue = null; - for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) { + for (String keyName : resultDescriptor.getPrimaryKeyAttributeNames()) { pkeyValue = NodeUtils.getNodeFieldAsText(entityNode, keyName); if (pkeyValue != null) { primaryKeyValues.add(pkeyValue); @@ -516,7 +527,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer try { String jsonPayload = null; - jsonPayload = sse.getIndexDocumentJson(); + jsonPayload = sse.getAsJson(); if (link != null && jsonPayload != null) { NetworkTransaction updateElasticTxn = new NetworkTransaction(); @@ -526,7 +537,8 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer updateElasticTxn.setOperationType(HttpMethod.PUT); esWorkOnHand.incrementAndGet(); - supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, esDataProvider), + supplyAsync( + new PerformElasticSearchPut(jsonPayload, updateElasticTxn, elasticSearchAdapter), esPutExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -612,7 +624,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer * called incrementAndGet when queuing the failed PUT! */ - supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, esDataProvider), + supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, elasticSearchAdapter), esExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -673,7 +685,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) */ @Override public String getStatReport(boolean showFinalReport) { @@ -684,7 +696,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#shutdown() + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() */ @Override public void shutdown() { @@ -713,7 +725,7 @@ public class AutosuggestionSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.AbstractEntitySynchronizer#clearCache() + * @see org.openecomp.sparky.synchronizer.AbstractEntitySynchronizer#clearCache() */ @Override public void clearCache() { diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/AggregationSuggestionSynchronizer.java b/src/main/java/org/onap/aai/sparky/autosuggestion/sync/VnfAliasSuggestionSynchronizer.java similarity index 76% rename from src/main/java/org/onap/aai/sparky/synchronizer/AggregationSuggestionSynchronizer.java rename to src/main/java/org/onap/aai/sparky/autosuggestion/sync/VnfAliasSuggestionSynchronizer.java index cd5877a..c6fa69b 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/AggregationSuggestionSynchronizer.java +++ b/src/main/java/org/onap/aai/sparky/autosuggestion/sync/VnfAliasSuggestionSynchronizer.java @@ -20,44 +20,52 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.autosuggestion.sync; import static java.util.concurrent.CompletableFuture.supplyAsync; import java.util.Map; import java.util.concurrent.ExecutorService; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.dal.NetworkTransaction; import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.entity.AggregationSuggestionEntity; -import org.onap.aai.sparky.synchronizer.enumeration.OperationState; -import org.onap.aai.sparky.synchronizer.enumeration.SynchronizerState; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchPut; +import org.onap.aai.sparky.sync.AbstractEntitySynchronizer; +import org.onap.aai.sparky.sync.IndexSynchronizer; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.entity.AggregationSuggestionEntity; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.onap.aai.sparky.sync.task.PerformElasticSearchPut; import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; -import org.onap.aai.cl.mdc.MdcContext; import org.slf4j.MDC; -public class AggregationSuggestionSynchronizer extends AbstractEntitySynchronizer + +public class VnfAliasSuggestionSynchronizer extends AbstractEntitySynchronizer implements IndexSynchronizer { private static final Logger LOG = - LoggerFactory.getInstance().getLogger(AggregationSuggestionSynchronizer.class); + LoggerFactory.getInstance().getLogger(VnfAliasSuggestionSynchronizer.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); + public VnfAliasSuggestionSynchronizer(ElasticSearchSchemaConfig schemaConfig, + int internalSyncWorkers, int aaiWorkers, int esWorkers, NetworkStatisticsConfig aaiStatConfig, + NetworkStatisticsConfig esStatConfig) throws Exception { + super(LOG, "VASS-" + schemaConfig.getIndexName().toUpperCase(), internalSyncWorkers, aaiWorkers, + esWorkers, schemaConfig.getIndexName(), aaiStatConfig, esStatConfig); this.isSyncInProgress = false; this.shouldPerformRetry = false; - this.synchronizerName = "Aggregation Suggestion Synchronizer"; + this.synchronizerName = "VNFs Alias Suggestion Synchronizer"; this.contextMap = MDC.getCopyOfContextMap(); this.esPutExecutor = NodeUtils.createNamedExecutor("ASS-ES-PUT", 2, LOG); } @@ -83,6 +91,7 @@ public class AggregationSuggestionSynchronizer extends AbstractEntitySynchronize isSyncInProgress = true; this.syncDurationInMs = -1; syncStartedTimeStampInMs = System.currentTimeMillis(); + syncEntity(); while (!isSyncDone()) { @@ -101,10 +110,11 @@ public class AggregationSuggestionSynchronizer extends AbstractEntitySynchronize private void syncEntity() { String txnId = NodeUtils.getRandomTxnId(); - MdcContext.initialize(txnId, "AggregationSuggestionSynchronizer", "", "Sync", ""); + MdcContext.initialize(txnId, synchronizerName, "", "Sync", ""); AggregationSuggestionEntity syncEntity = new AggregationSuggestionEntity(); syncEntity.deriveFields(); + syncEntity.initializeFilters(); String link = null; try { @@ -115,7 +125,7 @@ public class AggregationSuggestionSynchronizer extends AbstractEntitySynchronize try { String jsonPayload = null; - jsonPayload = syncEntity.getIndexDocumentJson(); + jsonPayload = syncEntity.getAsJson(); if (link != null && jsonPayload != null) { NetworkTransaction elasticPutTxn = new NetworkTransaction(); @@ -124,9 +134,8 @@ public class AggregationSuggestionSynchronizer extends AbstractEntitySynchronize esWorkOnHand.incrementAndGet(); final Map contextMap = MDC.getCopyOfContextMap(); - supplyAsync( - new PerformElasticSearchPut(jsonPayload, elasticPutTxn, esDataProvider, contextMap), - esPutExecutor).whenComplete((result, error) -> { + supplyAsync(new PerformElasticSearchPut(jsonPayload, elasticPutTxn, elasticSearchAdapter, + contextMap), esPutExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); diff --git a/src/main/java/org/onap/aai/sparky/autosuggestion/sync/VnfAliasSyncController.java b/src/main/java/org/onap/aai/sparky/autosuggestion/sync/VnfAliasSyncController.java new file mode 100644 index 0000000..3376eed --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/autosuggestion/sync/VnfAliasSyncController.java @@ -0,0 +1,95 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.autosuggestion.sync; + +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.sync.ElasticSearchIndexCleaner; +import org.onap.aai.sparky.sync.ElasticSearchSchemaFactory; +import org.onap.aai.sparky.sync.IndexCleaner; +import org.onap.aai.sparky.sync.IndexIntegrityValidator; +import org.onap.aai.sparky.sync.SyncControllerImpl; +import org.onap.aai.sparky.sync.SyncControllerRegistrar; +import org.onap.aai.sparky.sync.SyncControllerRegistry; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.config.SyncControllerConfig; + +public class VnfAliasSyncController extends SyncControllerImpl implements SyncControllerRegistrar { + + private SyncControllerRegistry syncControllerRegistry; + + public VnfAliasSyncController(SyncControllerConfig syncControllerConfig, + ActiveInventoryAdapter aaiAdapter, ElasticSearchAdapter esAdapter, + ElasticSearchSchemaConfig schemaConfig, ElasticSearchEndpointConfig endpointConfig, + NetworkStatisticsConfig aaiStatConfig, NetworkStatisticsConfig esStatConfig) + throws Exception { + super(syncControllerConfig); + + // final String controllerName = "VNFs Alias Suggestion Synchronizer"; + + IndexIntegrityValidator indexValidator = new IndexIntegrityValidator(esAdapter, schemaConfig, + endpointConfig, ElasticSearchSchemaFactory.getIndexSchema(schemaConfig)); + + registerIndexValidator(indexValidator); + + VnfAliasSuggestionSynchronizer synchronizer = new VnfAliasSuggestionSynchronizer(schemaConfig, + syncControllerConfig.getNumInternalSyncWorkers(), + syncControllerConfig.getNumSyncActiveInventoryWorkers(), + syncControllerConfig.getNumSyncElasticWorkers(), aaiStatConfig, esStatConfig); + + synchronizer.setAaiAdapter(aaiAdapter); + synchronizer.setElasticSearchAdapter(esAdapter); + + registerEntitySynchronizer(synchronizer); + + + IndexCleaner indexCleaner = + new ElasticSearchIndexCleaner(esAdapter, endpointConfig, schemaConfig); + + registerIndexCleaner(indexCleaner); + + } + + public SyncControllerRegistry getSyncControllerRegistry() { + return syncControllerRegistry; + } + + public void setSyncControllerRegistry(SyncControllerRegistry syncControllerRegistry) { + this.syncControllerRegistry = syncControllerRegistry; + } + + @Override + public void registerController() { + + if (syncControllerRegistry != null) { + if (syncControllerConfig.isEnabled()) { + syncControllerRegistry.registerSyncController(this); + } + } + + } + + +} diff --git a/src/main/java/org/onap/aai/sparky/common/search/CommonSearchSuggestion.java b/src/main/java/org/onap/aai/sparky/common/search/CommonSearchSuggestion.java new file mode 100644 index 0000000..8a3f119 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/common/search/CommonSearchSuggestion.java @@ -0,0 +1,88 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.common.search; + +import java.util.ArrayList; +import java.util.List; + +import org.onap.aai.sparky.search.entity.SearchSuggestion; +import org.onap.aai.sparky.search.filters.entity.UiFilterValueEntity; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_NULL) +public class CommonSearchSuggestion implements SearchSuggestion { + protected String hashId; + protected String route; + protected String text; + protected List filterValues = new ArrayList<>(); + + public CommonSearchSuggestion() {} + + public CommonSearchSuggestion(String hashId, String route, String text, String perspective, + List filterValues) { + this.hashId = hashId; + this.route = route; + this.text = text; + this.filterValues = filterValues; + } + + public List getFilterValues() { + return filterValues; + } + + public String getHashId() { + return hashId; + } + + public String getRoute() { + return route; + } + + public String getText() { + return text; + } + + public void setHashId(String hashId) { + this.hashId = hashId; + } + + public void setRoute(String route) { + this.route = route; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public String toString() { + return "CommonSearchSuggestion [" + (hashId != null ? "hashId=" + hashId + ", " : "") + + (route != null ? "route=" + route + ", " : "") + + (text != null ? "text=" + text + ", " : "") + + (filterValues != null ? "filterValues=" + filterValues : "") + "]"; + } + + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReference.java b/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReference.java index e4a9f90..1df9296 100644 --- a/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReference.java +++ b/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReference.java @@ -20,7 +20,6 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ - package org.onap.aai.sparky.config.oxm; import java.util.ArrayList; diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceDescriptor.java b/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceDescriptor.java new file mode 100644 index 0000000..f0e6d4e --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceDescriptor.java @@ -0,0 +1,65 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +public class CrossEntityReferenceDescriptor extends OxmEntityDescriptor { + protected CrossEntityReference crossEntityReference; + + 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; + } + + + @Override + public String toString() { + return "CrossEntityReferenceDescriptor [" + + (crossEntityReference != null ? "crossEntityReference=" + crossEntityReference + ", " + : "") + + (entityName != null ? "entityName=" + entityName + ", " : "") + + (primaryKeyAttributeNames != null ? "primaryKeyAttributeNames=" + primaryKeyAttributeNames + : "") + + "]"; + } + + + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceLookup.java b/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceLookup.java new file mode 100644 index 0000000..81fe943 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/CrossEntityReferenceLookup.java @@ -0,0 +1,154 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +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 org.eclipse.persistence.dynamic.DynamicType; +import org.eclipse.persistence.internal.oxm.mappings.Descriptor; +import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; + +public class CrossEntityReferenceLookup implements OxmModelProcessor { + + // TODO: kill singleton collaborator pattern + private static CrossEntityReferenceLookup instance; + + private Map> crossReferenceEntityOxmModel; + private Map crossReferenceEntityDescriptors; + + + private CrossEntityReferenceLookup() { + crossReferenceEntityOxmModel = new LinkedHashMap>(); + crossReferenceEntityDescriptors = new HashMap(); + } + + public synchronized static CrossEntityReferenceLookup getInstance() { + + /* + * I hate this method and I want it to go away. The singleton pattern is transitory, I want this + * class to be wired via a bean reference instead. But from the starting point, it would require + * fixing all the classes across the code base up front and I don't want this task to expand + * beyond just refactoring the OxmModelLoader. For now I'll keep the singleton pattern, but I + * really want to get rid of it once we are properly spring wired. + */ + + if (instance == null) { + instance = new CrossEntityReferenceLookup(); + } + + return instance; + } + + + @Override + public void processOxmModel(DynamicJAXBContext jaxbContext) { + + @SuppressWarnings("rawtypes") + List descriptorsList = jaxbContext.getXMLContext().getDescriptors(); + + for (@SuppressWarnings("rawtypes") + Descriptor desc : descriptorsList) { + + DynamicType entity = jaxbContext.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(); + + // add entityName + oxmProperties.put("entityName", entityName); + + Map properties = entity.getDescriptor().getProperties(); + if (properties != null) { + for (Map.Entry entry : properties.entrySet()) { + + if (entry.getKey().equalsIgnoreCase("crossEntityReference")) { + oxmProperties.put("crossEntityReference", entry.getValue()); + } + } + } + + if (oxmProperties.containsKey("crossEntityReference")) { + crossReferenceEntityOxmModel.put(entityName, oxmProperties); + } + + } + + for (Entry> crossRefModel : crossReferenceEntityOxmModel + .entrySet()) { + HashMap attribute = crossRefModel.getValue(); + CrossEntityReferenceDescriptor entity = new CrossEntityReferenceDescriptor(); + entity.setEntityName(attribute.get("entityName")); + entity.setPrimaryKeyAttributeNames( + 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); + } + + } + + public Map> getCrossReferenceEntityOxmModel() { + return crossReferenceEntityOxmModel; + } + + public void setCrossReferenceEntityOxmModel( + Map> crossReferenceEntityOxmModel) { + this.crossReferenceEntityOxmModel = crossReferenceEntityOxmModel; + } + + public Map getCrossReferenceEntityDescriptors() { + return crossReferenceEntityDescriptors; + } + + public void setCrossReferenceEntityDescriptors( + Map crossReferenceEntityDescriptors) { + this.crossReferenceEntityDescriptors = crossReferenceEntityDescriptors; + } + + + +} diff --git a/src/test/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderTest.java b/src/main/java/org/onap/aai/sparky/config/oxm/GeoEntityDescriptor.java similarity index 57% rename from src/test/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderTest.java rename to src/main/java/org/onap/aai/sparky/config/oxm/GeoEntityDescriptor.java index 5ff9e85..5a45842 100644 --- a/src/test/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderTest.java +++ b/src/main/java/org/onap/aai/sparky/config/oxm/GeoEntityDescriptor.java @@ -1,50 +1,59 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.config.oxm; - -import static org.junit.Assert.assertNotEquals; - -import java.io.File; -import java.io.IOException; - -import org.junit.Test; -import org.mockito.Mockito; - -public class OxmModelLoaderTest { - - OxmModelLoader loader; - - @Test - public void testLoadModel_ShouldSucceed() throws IOException { - String version = "v11"; - 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()); - } -} +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +public class GeoEntityDescriptor extends OxmEntityDescriptor { + + protected String geoLatName; + + protected String geoLongName; + + 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; + } + + @Override + public String toString() { + return "GeoEntityDescriptor [" + (geoLatName != null ? "geoLatName=" + geoLatName + ", " : "") + + (geoLongName != null ? "geoLongName=" + geoLongName + ", " : "") + + (entityName != null ? "entityName=" + entityName + ", " : "") + + (primaryKeyAttributeNames != null ? "primaryKeyAttributeNames=" + primaryKeyAttributeNames + : "") + + "]"; + } + + + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/GeoEntityLookup.java b/src/main/java/org/onap/aai/sparky/config/oxm/GeoEntityLookup.java new file mode 100644 index 0000000..f8b1ceb --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/GeoEntityLookup.java @@ -0,0 +1,155 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +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 org.eclipse.persistence.dynamic.DynamicType; +import org.eclipse.persistence.internal.oxm.mappings.Descriptor; +import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; + +public class GeoEntityLookup implements OxmModelProcessor { + + // TODO: kill singleton collaborator pattern + private static GeoEntityLookup instance; + + private Map> geoEntityOxmModel; + + private Map geoEntityDescriptors; + + private GeoEntityLookup() { + geoEntityOxmModel = new LinkedHashMap>(); + geoEntityDescriptors = new HashMap(); + } + + public synchronized static GeoEntityLookup getInstance() { + + /* + * I hate this method and I want it to go away. The singleton pattern is transitory, I want this + * class to be wired via a bean reference instead. But from the starting point, it would require + * fixing all the classes across the code base up front and I don't want this task to expand + * beyond just refactoring the OxmModelLoader. For now I'll keep the singleton pattern, but I + * really want to get rid of it once we are properly spring wired. + */ + + if (instance == null) { + instance = new GeoEntityLookup(); + } + + return instance; + } + + public Map> getGeoEntityOxmModel() { + return geoEntityOxmModel; + } + + public void setGeoEntityOxmModel(Map> geoEntityOxmModel) { + this.geoEntityOxmModel = geoEntityOxmModel; + } + + public Map getGeoEntityDescriptors() { + return geoEntityDescriptors; + } + + public void setGeoEntityDescriptors(Map geoEntityDescriptors) { + this.geoEntityDescriptors = geoEntityDescriptors; + } + + @Override + public void processOxmModel(DynamicJAXBContext jaxbContext) { + + @SuppressWarnings("rawtypes") + List descriptorsList = jaxbContext.getXMLContext().getDescriptors(); + + for (@SuppressWarnings("rawtypes") + Descriptor desc : descriptorsList) { + + DynamicType entity = jaxbContext.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(); + + // add entityName + oxmProperties.put("entityName", entityName); + + Map properties = entity.getDescriptor().getProperties(); + + if (properties != null) { + for (Map.Entry entry : properties.entrySet()) { + + 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()); + } + } + } + } + + if (oxmProperties.containsKey("geoLat") && oxmProperties.containsKey("geoLong")) { + geoEntityOxmModel.put(entityName, oxmProperties); + } + + } + + for (Entry> entityModel : geoEntityOxmModel.entrySet()) { + + HashMap attribute = entityModel.getValue(); + + GeoOxmEntityDescriptor entity = new GeoOxmEntityDescriptor(); + + entity.setEntityName(attribute.get("entityName")); + + if (attribute.containsKey("primaryKeyAttributeNames")) { + + entity.setPrimaryKeyAttributeNames( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + + if (attribute.containsKey("geoLat") || attribute.containsKey("geoLong")) { + entity.setGeoLatName(attribute.get("geoLat")); + entity.setGeoLongName(attribute.get("geoLong")); + } + + geoEntityDescriptors.put(attribute.get("entityName"), entity); + } + } + + } + + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/GeoOxmEntityDescriptor.java b/src/main/java/org/onap/aai/sparky/config/oxm/GeoOxmEntityDescriptor.java new file mode 100644 index 0000000..595c81a --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/GeoOxmEntityDescriptor.java @@ -0,0 +1,69 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +public class GeoOxmEntityDescriptor extends OxmEntityDescriptor { + + private String geoLatName; + + private String geoLongName; + + 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() { + return (this.geoLongName != null && this.geoLatName != null); + } + + @Override + public String toString() { + return "GeoOxmEntityDescriptor [" + + (geoLatName != null ? "geoLatName=" + geoLatName + ", " : "") + + (geoLongName != null ? "geoLongName=" + geoLongName + ", " : "") + + (entityName != null ? "entityName=" + entityName + ", " : "") + + (primaryKeyAttributeNames != null ? "primaryKeyAttributeNames=" + primaryKeyAttributeNames + : "") + + "]"; + } + + + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/OxmEntityDescriptor.java b/src/main/java/org/onap/aai/sparky/config/oxm/OxmEntityDescriptor.java index 379cca2..3b3fabd 100644 --- a/src/main/java/org/onap/aai/sparky/config/oxm/OxmEntityDescriptor.java +++ b/src/main/java/org/onap/aai/sparky/config/oxm/OxmEntityDescriptor.java @@ -22,28 +22,18 @@ */ package org.onap.aai.sparky.config.oxm; +import java.util.ArrayList; import java.util.List; -import org.onap.aai.sparky.synchronizer.entity.SuggestionSearchEntity; - -/** - * The Class OxmEntityDescriptor. - */ public class OxmEntityDescriptor { - private String entityName; - - private List primaryKeyAttributeName; - - private List searchableAttributes; - - private CrossEntityReference crossEntityReference; + protected String entityName; - private String geoLatName; + protected List primaryKeyAttributeNames; - private String geoLongName; - - private SuggestionSearchEntity suggestionSearchEntity; + public OxmEntityDescriptor() { + primaryKeyAttributeNames = new ArrayList(); + } public String getEntityName() { return entityName; @@ -53,124 +43,24 @@ public class OxmEntityDescriptor { 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; + public List getPrimaryKeyAttributeNames() { + return primaryKeyAttributeNames; } - /** - * 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 void setPrimaryKeyAttributeNames(List primaryKeyAttributeNames) { + this.primaryKeyAttributeNames = primaryKeyAttributeNames; } - 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; + public void addPrimaryKeyName(String name) { + primaryKeyAttributeNames.add(name); } @Override public String toString() { - return "OxmEntityDescriptor [entityName=" + entityName + ", primaryKeyAttributeName=" - + primaryKeyAttributeName + ", searchableAttributes=" + searchableAttributes - + ", crossEntityReference=" + crossEntityReference + ", geoLatName=" + geoLatName - + ", geoLongName=" + geoLongName + ", suggestionSearchEntity=" + suggestionSearchEntity + return "OxmEntityDescriptor [" + (entityName != null ? "entityName=" + entityName + ", " : "") + + (primaryKeyAttributeNames != null ? "primaryKeyAttributeNames=" + primaryKeyAttributeNames + : "") + "]"; } + } diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/OxmEntityLookup.java b/src/main/java/org/onap/aai/sparky/config/oxm/OxmEntityLookup.java new file mode 100644 index 0000000..168a4b1 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/OxmEntityLookup.java @@ -0,0 +1,151 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +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 org.eclipse.persistence.dynamic.DynamicType; +import org.eclipse.persistence.internal.oxm.mappings.Descriptor; +import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; + +public class OxmEntityLookup implements OxmModelProcessor { + + // TODO: kill singleton collaborator pattern + private static OxmEntityLookup instance; + + private Map> oxmModel; + + private Map entityTypeLookup; + + private Map entityDescriptors; + + + private OxmEntityLookup() { + oxmModel = new LinkedHashMap>(); + entityTypeLookup = new LinkedHashMap(); + entityDescriptors = new HashMap(); + } + + public synchronized static OxmEntityLookup getInstance() { + + /* + * I hate this method and I want it to go away. The singleton pattern is transitory, I want this + * class to be wired via a bean reference instead. But from the starting point, it would require + * fixing all the classes across the code base up front and I don't want this task to expand + * beyond just refactoring the OxmModelLoader. For now I'll keep the singleton pattern, but I + * really want to get rid of it once we are properly spring wired. + */ + + if (instance == null) { + instance = new OxmEntityLookup(); + } + + return instance; + } + + + @Override + public void processOxmModel(DynamicJAXBContext jaxbContext) { + + @SuppressWarnings("rawtypes") + List descriptorsList = jaxbContext.getXMLContext().getDescriptors(); + + for (@SuppressWarnings("rawtypes") + Descriptor desc : descriptorsList) { + + DynamicType entity = jaxbContext.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(); + + oxmModel.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.setPrimaryKeyAttributeNames( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + + entityDescriptors.put(attribute.get("entityName"), entity); + } + } + + } + + public Map> getOxmModel() { + return oxmModel; + } + + public void setOxmModel(Map> oxmModel) { + this.oxmModel = oxmModel; + } + + public Map getEntityTypeLookup() { + return entityTypeLookup; + } + + public void setEntityTypeLookup(Map entityTypeLookup) { + this.entityTypeLookup = entityTypeLookup; + } + + public Map getEntityDescriptors() { + return entityDescriptors; + } + + public void setEntityDescriptors(Map entityDescriptors) { + this.entityDescriptors = entityDescriptors; + } + + public void addEntityDescriptor(String type, OxmEntityDescriptor descriptor) { + if (this.entityDescriptors != null) { + this.entityDescriptors.put(type, descriptor); + } + } + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelLoader.java b/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelLoader.java index 853a537..b953917 100644 --- a/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelLoader.java +++ b/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelLoader.java @@ -22,485 +22,163 @@ */ package org.onap.aai.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.HashSet; import java.util.Map; -import java.util.Map.Entry; -import java.util.Vector; +import java.util.Set; 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.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.entity.SuggestionSearchEntity; -import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; -/** - * 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(); - } + /* + * The intent of this parameter is to be able to programmatically over-ride the latest AAI schema + * version discovered from the aai-schema jar file. This property is optional, but if set on the + * bean or by another class in the system, then it will override the spec version that is loaded. + * + * If the latestVersionOverride is greater than 0 then it will set the latest version to the + * specified version, and that stream will be returned if available. + */ - return instance; + protected int oxmApiVersionOverride; + protected Set processors; + private int latestVersionNum = 0; - } + private final static Pattern p = Pattern.compile("aai_oxm_(v)(.*).xml"); - /** - * Instantiates a new oxm model loader. - */ public OxmModelLoader() { + this(-1, new HashSet()); + } + public OxmModelLoader(int apiVersionOverride, Set oxmModelProcessors) { + this.oxmApiVersionOverride = apiVersionOverride; + this.processors = oxmModelProcessors; } - /** - * 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; + protected synchronized Map getStreamHandlesForOxmFromResource() { + Map listOfOxmFiles = new HashMap(); + ClassLoader oxmClassLoader = OxmModelLoader.class.getClassLoader(); + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(oxmClassLoader); + Resource[] resources = null; + try { + resources = resolver.getResources("classpath*:/oxm/aai_oxm*.xml"); + } catch (IOException ex) { + LOG.error(AaiUiMsgs.OXM_LOADING_ERROR, ex.getMessage()); } - // load the latest version based on file name - loadModel(version); + if (resources == null) { + LOG.error(AaiUiMsgs.OXM_LOADING_ERROR, "No OXM schema files found on classpath"); + } + + for (Resource resource : resources) { + Matcher m = p.matcher(resource.getFilename()); + if (m.matches()) { + try { + listOfOxmFiles.put(new Integer(m.group(2)), resource.getInputStream()); + } catch (Exception e) { + LOG.error(AaiUiMsgs.OXM_LOADING_ERROR, resource.getFilename(), e.getMessage()); + } + } + } + return listOfOxmFiles; } /** - * Load model. - * - * @param version the version + * Load an oxm model. + * + * @param inputStream file handle for oxm */ - public void loadModel(String version) { - String fileName = loadOxmFileName(version); - - try (FileInputStream inputStream = new FileInputStream(new File(fileName))) { - Map properties = new HashMap(); - properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, inputStream); - + protected void loadModel(InputStream inputStream) { + 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 (FileNotFoundException fnf) { - LOG.info(AaiUiMsgs.OXM_READ_ERROR_NONVERBOSE); - LOG.error(AaiUiMsgs.OXM_READ_ERROR_VERBOSE, fileName); + LOG.info(AaiUiMsgs.OXM_LOAD_SUCCESS, String.valueOf(latestVersionNum)); } catch (Exception exc) { LOG.info(AaiUiMsgs.OXM_PARSE_ERROR_NONVERBOSE); - LOG.error(AaiUiMsgs.OXM_PARSE_ERROR_VERBOSE, fileName, exc.getMessage()); + LOG.error(AaiUiMsgs.OXM_PARSE_ERROR_VERBOSE, "OXM v" + latestVersionNum, exc.getMessage()); } } /** - * Parses the oxm context. - * - * @param oxmContext the oxm context + * Load the latest oxm model. */ - 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); - } + public synchronized void loadLatestOxmModel() { - 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); - } - } + LOG.info(AaiUiMsgs.INITIALIZE_OXM_MODEL_LOADER); - - 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); + // find handles for available oxm models + final Map listOfOxmStreams = getStreamHandlesForOxmFromResource(); + if (listOfOxmStreams.isEmpty()) { + LOG.error(AaiUiMsgs.OXM_FILE_NOT_FOUND); + return; } - 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(","))); - + InputStream stream = null; - List crossEntityRefTokens = - Arrays.asList(attribute.get("crossEntityReference").split(",")); - - if (crossEntityRefTokens.size() >= 2) { - CrossEntityReference entityRef = new CrossEntityReference(); - entityRef.setTargetEntityType(crossEntityRefTokens.get(0)); + if (oxmApiVersionOverride > 0) { + latestVersionNum = oxmApiVersionOverride; + LOG.warn(AaiUiMsgs.WARN_GENERIC, "Overriding AAI Schema with version = " + latestVersionNum); + stream = listOfOxmStreams.get(latestVersionNum); + } else { - for (int i = 1; i < crossEntityRefTokens.size(); i++) { - entityRef.addReferenceAttribute(crossEntityRefTokens.get(i)); + for (Integer key : listOfOxmStreams.keySet()) { + if (key.intValue() > latestVersionNum) { + latestVersionNum = key.intValue(); + stream = listOfOxmStreams.get(key); } - - 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); + // load the latest oxm file + loadModel(stream); - 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; - } - + public int getLatestVersionNum() { + return latestVersionNum; } - /** - * Load oxm folder. - * - * @return the file - */ - public File loadOxmFolder() { - return new File(TierSupportUiConstants.CONFIG_OXM_LOCATION); + public void setLatestVersionNum(int latestVersionNum) { + this.latestVersionNum = latestVersionNum; } /** - * Load oxm file name. + * Parses the oxm context. * - * @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 + * @param oxmContext the oxm context */ - public Map> getSearchableOxmModel() { - return searchableOxmModel; - } - - public Map> getCrossReferenceEntityOxmModel() { - return crossReferenceEntityOxmModel; - } - - public Map getEntityDescriptors() { - return entityDescriptors; - } + private void parseOxmContext(DynamicJAXBContext oxmContext) { - /** - * Gets the entity descriptor. - * - * @param type the type - * @return the entity descriptor - */ - public OxmEntityDescriptor getEntityDescriptor(String type) { - return entityDescriptors.get(type); - } + if (processors != null && processors.size() > 0) { - public Map getSearchableEntityDescriptors() { - return searchableEntityDescriptors; - } + for (OxmModelProcessor processor : processors) { - /** - * Gets the searchable entity descriptor. - * - * @param entityType the entity type - * @return the searchable entity descriptor - */ - public OxmEntityDescriptor getSearchableEntityDescriptor(String entityType) { - return searchableEntityDescriptors.get(entityType); - } + processor.processOxmModel(oxmContext); - public Map getCrossReferenceEntityDescriptors() { - return crossReferenceEntityDescriptors; - } + } - public Map getGeoEntityDescriptors() { - return geoEntityDescriptors; - } + } - public Map getSuggestionSearchEntityDescriptors() { - return suggestionSearchEntityDescriptors; } } diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderFilter.java b/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderFilter.java deleted file mode 100644 index 0ddf80a..0000000 --- a/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelLoaderFilter.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.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/onap/aai/sparky/config/oxm/OxmModelProcessor.java b/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelProcessor.java new file mode 100644 index 0000000..b8e7c6f --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/OxmModelProcessor.java @@ -0,0 +1,31 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; + +public interface OxmModelProcessor { + + public void processOxmModel(DynamicJAXBContext jaxbContext); + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/SearchableEntityLookup.java b/src/main/java/org/onap/aai/sparky/config/oxm/SearchableEntityLookup.java new file mode 100644 index 0000000..d8a27ac --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/SearchableEntityLookup.java @@ -0,0 +1,138 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +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 org.eclipse.persistence.dynamic.DynamicType; +import org.eclipse.persistence.internal.oxm.mappings.Descriptor; +import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; + +public class SearchableEntityLookup implements OxmModelProcessor { + + // TODO: kill singleton collaborator pattern + private static SearchableEntityLookup instance; + + private Map> searchableOxmModel; + private Map searchableEntityDescriptors; + + private SearchableEntityLookup() { + searchableOxmModel = new LinkedHashMap>(); + searchableEntityDescriptors = new HashMap(); + } + + public synchronized static SearchableEntityLookup getInstance() { + + /* + * I hate this method and I want it to go away. The singleton pattern is transitory, I want this + * class to be wired via a bean reference instead. But from the starting point, it would require + * fixing all the classes across the code base up front and I don't want this task to expand + * beyond just refactoring the OxmModelLoader. For now I'll keep the singleton pattern, but I + * really want to get rid of it once we are properly spring wired. + */ + + if (instance == null) { + instance = new SearchableEntityLookup(); + } + + return instance; + } + + + @Override + public void processOxmModel(DynamicJAXBContext jaxbContext) { + + @SuppressWarnings("rawtypes") + List descriptorsList = jaxbContext.getXMLContext().getDescriptors(); + + for (@SuppressWarnings("rawtypes") + Descriptor desc : descriptorsList) { + + DynamicType entity = jaxbContext.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(); + + // 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()); + } + } + } + + // Add all searchable entity types for reserve lookup + if (oxmProperties.containsKey("searchableAttributes")) { + searchableOxmModel.put(entityName, oxmProperties); + } + + } + + for (Entry> searchableModel : searchableOxmModel.entrySet()) { + HashMap attribute = searchableModel.getValue(); + SearchableOxmEntityDescriptor entity = new SearchableOxmEntityDescriptor(); + entity.setEntityName(attribute.get("entityName")); + entity.setPrimaryKeyAttributeNames( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + entity + .setSearchableAttributes(Arrays.asList(attribute.get("searchableAttributes").split(","))); + searchableEntityDescriptors.put(attribute.get("entityName"), entity); + } + + } + + public Map> getSearchableOxmModel() { + return searchableOxmModel; + } + + public void setSearchableOxmModel(Map> searchableOxmModel) { + this.searchableOxmModel = searchableOxmModel; + } + + public Map getSearchableEntityDescriptors() { + return searchableEntityDescriptors; + } + + public void setSearchableEntityDescriptors( + Map searchableEntityDescriptors) { + this.searchableEntityDescriptors = searchableEntityDescriptors; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/SearchableOxmEntityDescriptor.java b/src/main/java/org/onap/aai/sparky/config/oxm/SearchableOxmEntityDescriptor.java new file mode 100644 index 0000000..cdd5ad0 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/SearchableOxmEntityDescriptor.java @@ -0,0 +1,73 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +import java.util.List; + +public class SearchableOxmEntityDescriptor extends OxmEntityDescriptor { + + protected List searchableAttributes; + + public List getSearchableAttributes() { + return searchableAttributes; + } + + public void setSearchableAttributes(List searchableAttributes) { + this.searchableAttributes = searchableAttributes; + } + + public void addSearchableAttribute(String attributeName) { + searchableAttributes.add(attributeName); + } + + /** + * 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; + + } + + @Override + public String toString() { + return "SearchableOxmEntityDescriptor [" + + (searchableAttributes != null ? "searchableAttributes=" + searchableAttributes + ", " + : "") + + (entityName != null ? "entityName=" + entityName + ", " : "") + + (primaryKeyAttributeNames != null ? "primaryKeyAttributeNames=" + primaryKeyAttributeNames + : "") + + "]"; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityDescriptor.java b/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityDescriptor.java new file mode 100644 index 0000000..c72068a --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityDescriptor.java @@ -0,0 +1,52 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +import org.onap.aai.sparky.sync.entity.SuggestionSearchEntity; + +public class SuggestionEntityDescriptor extends OxmEntityDescriptor { + + protected SuggestionSearchEntity suggestionSearchEntity; + + public SuggestionSearchEntity getSuggestionSearchEntity() { + return suggestionSearchEntity; + } + + public void setSuggestionSearchEntity(SuggestionSearchEntity suggestionSearchEntity) { + this.suggestionSearchEntity = suggestionSearchEntity; + } + + @Override + public String toString() { + return "SuggestionEntityDescriptor [" + + (suggestionSearchEntity != null + ? "suggestionSearchEntity=" + suggestionSearchEntity + ", " : "") + + (entityName != null ? "entityName=" + entityName + ", " : "") + + (primaryKeyAttributeNames != null ? "primaryKeyAttributeNames=" + primaryKeyAttributeNames + : "") + + "]"; + } + + + +} diff --git a/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityLookup.java b/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityLookup.java new file mode 100644 index 0000000..758ae60 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/config/oxm/SuggestionEntityLookup.java @@ -0,0 +1,197 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.config.oxm; + +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 org.eclipse.persistence.dynamic.DynamicType; +import org.eclipse.persistence.internal.oxm.mappings.Descriptor; +import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; +import org.eclipse.persistence.mappings.DatabaseMapping; +import org.onap.aai.sparky.sync.entity.SuggestionSearchEntity; + +public class SuggestionEntityLookup implements OxmModelProcessor { + + // TODO: kill singleton collaborator pattern + private static SuggestionEntityLookup instance; + + private Map> suggestionSearchEntityOxmModel; + private Map suggestionSearchEntityDescriptors; + + private SuggestionEntityLookup() { + suggestionSearchEntityOxmModel = new LinkedHashMap>(); + suggestionSearchEntityDescriptors = new HashMap(); + } + + public synchronized static SuggestionEntityLookup getInstance() { + + /* + * I hate this method and I want it to go away. The singleton pattern is transitory, I want this + * class to be wired via a bean reference instead. But from the starting point, it would require + * fixing all the classes across the code base up front and I don't want this task to expand + * beyond just refactoring the OxmModelLoader. For now I'll keep the singleton pattern, but I + * really want to get rid of it once we are properly spring wired. + */ + + if (instance == null) { + instance = new SuggestionEntityLookup(); + } + + return instance; + } + + + @Override + public void processOxmModel(DynamicJAXBContext jaxbContext) { + + @SuppressWarnings("rawtypes") + List descriptorsList = jaxbContext.getXMLContext().getDescriptors(); + + for (@SuppressWarnings("rawtypes") + Descriptor desc : descriptorsList) { + + DynamicType entity = jaxbContext.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(); + + // add entityName + oxmProperties.put("entityName", entityName); + + Map properties = entity.getDescriptor().getProperties(); + if (properties != null) { + for (Map.Entry entry : properties.entrySet()) { + + + 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()); + } + } + } + + if (oxmProperties.containsKey("containsSuggestibleProps")) { + suggestionSearchEntityOxmModel.put(entityName, oxmProperties); + } + } + + 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("suggestionAliases") != null) { + suggestionSearchEntity + .setSuggestionAliases(Arrays.asList(attribute.get("suggestionAliases").split(","))); + } + + if (attribute.get("suggestibleAttributes") != null) { + suggestionSearchEntity.setSuggestionPropertyTypes( + Arrays.asList(attribute.get("suggestibleAttributes").split(","))); + } + + SuggestionEntityDescriptor entity = new SuggestionEntityDescriptor(); + entity.setSuggestionSearchEntity(suggestionSearchEntity); + entity.setEntityName(entityName); + + if (attribute.get("primaryKeyAttributeNames") != null) { + entity.setPrimaryKeyAttributeNames( + Arrays.asList(attribute.get("primaryKeyAttributeNames").replace(" ", "").split(","))); + } + + suggestionSearchEntityDescriptors.put(entityName, entity); + } + } + + public Map> getSuggestionSearchEntityOxmModel() { + return suggestionSearchEntityOxmModel; + } + + public void setSuggestionSearchEntityOxmModel( + Map> suggestionSearchEntityOxmModel) { + this.suggestionSearchEntityOxmModel = suggestionSearchEntityOxmModel; + } + + public Map getSuggestionSearchEntityDescriptors() { + return suggestionSearchEntityDescriptors; + } + + public void setSuggestionSearchEntityDescriptors( + Map suggestionSearchEntityDescriptors) { + this.suggestionSearchEntityDescriptors = suggestionSearchEntityDescriptors; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/CrossEntityReferenceSynchronizer.java b/src/main/java/org/onap/aai/sparky/crossentityreference/sync/CrossEntityReferenceSynchronizer.java similarity index 62% rename from src/main/java/org/onap/aai/sparky/synchronizer/CrossEntityReferenceSynchronizer.java rename to src/main/java/org/onap/aai/sparky/crossentityreference/sync/CrossEntityReferenceSynchronizer.java index 8328627..39ee8c5 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/CrossEntityReferenceSynchronizer.java +++ b/src/main/java/org/onap/aai/sparky/crossentityreference/sync/CrossEntityReferenceSynchronizer.java @@ -20,13 +20,11 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.crossentityreference.sync; 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; @@ -38,30 +36,39 @@ import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutorService; import java.util.function.Supplier; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.config.oxm.CrossEntityReference; +import org.onap.aai.sparky.config.oxm.CrossEntityReferenceDescriptor; +import org.onap.aai.sparky.config.oxm.CrossEntityReferenceLookup; import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; +import org.onap.aai.sparky.config.oxm.SearchableEntityLookup; +import org.onap.aai.sparky.config.oxm.SearchableOxmEntityDescriptor; import org.onap.aai.sparky.dal.NetworkTransaction; import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.config.SynchronizerConfiguration; -import org.onap.aai.sparky.synchronizer.entity.IndexableCrossEntityReference; -import org.onap.aai.sparky.synchronizer.entity.MergableEntity; -import org.onap.aai.sparky.synchronizer.entity.SelfLinkDescriptor; -import org.onap.aai.sparky.synchronizer.enumeration.OperationState; -import org.onap.aai.sparky.synchronizer.enumeration.SynchronizerState; -import org.onap.aai.sparky.synchronizer.task.PerformActiveInventoryRetrieval; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchPut; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchRetrieval; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchUpdate; +import org.onap.aai.sparky.sync.AbstractEntitySynchronizer; +import org.onap.aai.sparky.sync.IndexSynchronizer; +import org.onap.aai.sparky.sync.SynchronizerConstants; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.entity.IndexableCrossEntityReference; +import org.onap.aai.sparky.sync.entity.MergableEntity; +import org.onap.aai.sparky.sync.entity.SelfLinkDescriptor; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.onap.aai.sparky.sync.task.PerformActiveInventoryRetrieval; +import org.onap.aai.sparky.sync.task.PerformElasticSearchPut; +import org.onap.aai.sparky.sync.task.PerformElasticSearchRetrieval; +import org.onap.aai.sparky.sync.task.PerformElasticSearchUpdate; import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import org.slf4j.MDC; -import org.onap.aai.cl.mdc.MdcContext; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectReader; @@ -105,12 +112,13 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer LoggerFactory.getInstance().getLogger(CrossEntityReferenceSynchronizer.class); private static final String SERVICE_INSTANCE = "service-instance"; + 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. @@ -118,27 +126,29 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer * @param indexName the index name * @throws Exception the exception */ - public CrossEntityReferenceSynchronizer(String indexName, ActiveInventoryConfig aaiConfig) - throws Exception { - super(LOG, "CERS", 2, 5, 5, indexName); + public CrossEntityReferenceSynchronizer(ElasticSearchSchemaConfig schemaConfig, + int internalSyncWorkers, int aaiWorkers, int esWorkers, NetworkStatisticsConfig aaiStatConfig, + NetworkStatisticsConfig esStatConfig) throws Exception { + super(LOG, "CERS", internalSyncWorkers, aaiWorkers, esWorkers, schemaConfig.getIndexName(), + aaiStatConfig, esStatConfig); 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; + this.aaiEntityStats.intializeEntityCounters( + CrossEntityReferenceLookup.getInstance().getCrossReferenceEntityDescriptors().keySet()); + + this.esEntityStats.intializeEntityCounters( + CrossEntityReferenceLookup.getInstance().getCrossReferenceEntityDescriptors().keySet()); this.syncDurationInMs = -1; } /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#doSync() + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() */ @Override public OperationState doSync() { @@ -164,7 +174,7 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) */ @Override public String getStatReport(boolean showFinalReport) { @@ -175,7 +185,7 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#shutdown() + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() */ @Override public void shutdown() { @@ -200,8 +210,8 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer */ private OperationState launchSyncFlow() { final Map contextMap = MDC.getCopyOfContextMap(); - Map descriptorMap = - oxmModelLoader.getCrossReferenceEntityDescriptors(); + Map descriptorMap = + CrossEntityReferenceLookup.getInstance().getCrossReferenceEntityDescriptors(); if (descriptorMap.isEmpty()) { LOG.error(AaiUiMsgs.ERROR_LOADING_OXM); @@ -229,7 +239,7 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer MDC.setContextMap(contextMap); OperationResult typeLinksResult = null; try { - typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(key); + typeLinksResult = aaiAdapter.getSelfLinksByEntityType(key); aaiWorkOnHand.decrementAndGet(); processEntityTypeSelfLinks(typeLinksResult); } catch (Exception exc) { @@ -286,11 +296,12 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer SelfLinkDescriptor linkDescriptor = selflinks.poll(); aaiWorkOnHand.decrementAndGet(); - OxmEntityDescriptor descriptor = null; + CrossEntityReferenceDescriptor descriptor = null; if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { - descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + descriptor = CrossEntityReferenceLookup.getInstance().getCrossReferenceEntityDescriptors() + .get(linkDescriptor.getEntityType()); if (descriptor == null) { LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); @@ -302,13 +313,14 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer NetworkTransaction txn = new NetworkTransaction(); txn.setDescriptor(descriptor); - txn.setLink(linkDescriptor.getSelfLink() + linkDescriptor.getDepthModifier()); + txn.setLink(linkDescriptor.getSelfLink()); + txn.setQueryParameters(linkDescriptor.getDepthModifier()); txn.setOperationType(HttpMethod.GET); txn.setEntityType(linkDescriptor.getEntityType()); aaiWorkOnHand.incrementAndGet(); - supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiAdapter), aaiExecutor) .whenComplete((result, error) -> { aaiWorkOnHand.decrementAndGet(); @@ -345,15 +357,14 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer 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); - return; + // TODO // TODO -> LOG, waht should be logged here? } JsonNode resultData = rootNode.get("result-data"); ArrayNode resultDataArrayNode = null; + CrossEntityReferenceLookup cerLookup = CrossEntityReferenceLookup.getInstance(); + if (resultData.isArray()) { resultDataArrayNode = (ArrayNode) resultData; @@ -366,10 +377,10 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer final String resourceType = NodeUtils.getNodeFieldAsText(element, "resource-type"); final String resourceLink = NodeUtils.getNodeFieldAsText(element, "resource-link"); - OxmEntityDescriptor descriptor = null; + CrossEntityReferenceDescriptor descriptor = null; if (resourceType != null && resourceLink != null) { - descriptor = oxmModelLoader.getEntityDescriptor(resourceType); + descriptor = cerLookup.getCrossReferenceEntityDescriptors().get(resourceType); if (descriptor == null) { LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); @@ -378,7 +389,7 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer } if (descriptor.hasCrossEntityReferences()) { selflinks.add(new SelfLinkDescriptor(resourceLink, - SynchronizerConfiguration.DEPTH_ALL_MODIFIER, resourceType)); + SynchronizerConstants.DEPTH_ALL_MODIFIER, resourceType)); } } } @@ -398,13 +409,14 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer */ private String determineEntityQueryString(String entityType, JsonNode entityJsonNode) { - OxmEntityDescriptor entityDescriptor = oxmModelLoader.getEntityDescriptor(entityType); + OxmEntityDescriptor entityDescriptor = + OxmEntityLookup.getInstance().getEntityDescriptors().get(entityType); String queryString = null; if (entityDescriptor != null) { - final List primaryKeyNames = entityDescriptor.getPrimaryKeyAttributeName(); + final List primaryKeyNames = entityDescriptor.getPrimaryKeyAttributeNames(); final List keyValues = new ArrayList(); NodeUtils.extractFieldValuesFromObject(entityJsonNode, primaryKeyNames, keyValues); @@ -430,7 +442,10 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer return; } - if (txn.getDescriptor().hasCrossEntityReferences()) { + CrossEntityReferenceDescriptor cerDescriptor = CrossEntityReferenceLookup.getInstance() + .getCrossReferenceEntityDescriptors().get(txn.getDescriptor().getEntityName()); + + if (cerDescriptor != null && cerDescriptor.hasCrossEntityReferences()) { final String jsonResult = txn.getOperationResult().getResult(); @@ -448,182 +463,207 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer *
  • Rinse and repeat. */ - OxmEntityDescriptor parentEntityDescriptor = - oxmModelLoader.getEntityDescriptor(txn.getEntityType()); + CrossEntityReference cerDefinition = cerDescriptor.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) { + + if (cerDescriptor != null) { + + String childEntityType = cerDefinition.getTargetEntityType(); + + List childPrimaryKeyNames = cerDescriptor.getPrimaryKeyAttributeNames(); + + 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(); + + /** + * At present, there is an issue with resolving the self-link using the + * generic-query with nothing more than the service-instance identifier and the + * service-subscription. There is another level of detail we don't have access to + * unless we parse it out of the service-subscription self-link, which is a coupling + * I would like to avoid. Fortunately, there is a workaround, but only for + * service-instances, which is presently our only use-case for the + * cross-entity-reference in R1707. Going forwards hopefully there will be other + * ways to resolve a child self-link using parental embedded meta data that we don't + * currently have. + * + * The work-around with the service-instance entity-type is that it's possible to + * request the self-link using only the service-instance-id because of a historical + * AAI functional query requirement that it be possible to query a service-instance + * only by it's service-instance-id. This entity type is the only one in the system + * that can be queried this way which makes it a very limited workaround, but good + * enough for the current release. + */ + + if (SERVICE_INSTANCE.equals(childEntityType)) { + orderedQueryKeyParams.clear(); + orderedQueryKeyParams.add(childEntityQueryKeyString); + } else { + orderedQueryKeyParams.add(parentEntityQueryString); + orderedQueryKeyParams.add(childEntityQueryKeyString); + } - if (parentEntityDescriptor != null) { + String genericQueryStr = null; + try { + genericQueryStr = + aaiAdapter.getGenericQueryForSelfLink(childEntityType, orderedQueryKeyParams); - CrossEntityReference cerDefinition = parentEntityDescriptor.getCrossEntityReference(); + if (genericQueryStr != null) { + aaiWorkOnHand.incrementAndGet(); - if (cerDefinition != null) { - JsonNode convertedNode = null; - try { - convertedNode = - NodeUtils.convertJsonStrToJsonNode(txn.getOperationResult().getResult()); + OperationResult aaiQueryResult = aaiAdapter.queryActiveInventoryWithRetries( + genericQueryStr, "application/json", aaiAdapter.getNumRequestRetries()); - final String parentEntityQueryString = - determineEntityQueryString(txn.getEntityType(), convertedNode); + aaiWorkOnHand.decrementAndGet(); - List extractedParentEntityAttributeValues = new ArrayList(); + if (aaiQueryResult != null && aaiQueryResult.wasSuccessful()) { - NodeUtils.extractFieldValuesFromObject(convertedNode, - cerDefinition.getReferenceAttributes(), extractedParentEntityAttributeValues); + Collection entityLinks = new ArrayList(); + JsonNode genericQueryResult = null; + try { + genericQueryResult = + NodeUtils.convertJsonStrToJsonNode(aaiQueryResult.getResult()); - List nestedTargetEntityInstances = new ArrayList(); - NodeUtils.extractObjectsByKey(convertedNode, cerDefinition.getTargetEntityType(), - nestedTargetEntityInstances); + if (genericQueryResult != null) { - for (JsonNode targetEntityInstance : nestedTargetEntityInstances) { + NodeUtils.extractObjectsByKey(genericQueryResult, "resource-link", + entityLinks); - OxmEntityDescriptor cerDescriptor = oxmModelLoader - .getSearchableEntityDescriptor(cerDefinition.getTargetEntityType()); + String selfLink = null; - if (cerDescriptor != 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(); - String childEntityType = cerDefinition.getTargetEntityType(); + SearchableEntityLookup searchableEntityLookup = + SearchableEntityLookup.getInstance(); - List childPrimaryKeyNames = cerDescriptor.getPrimaryKeyAttributeName(); + SearchableOxmEntityDescriptor searchableDescriptor = + searchableEntityLookup.getSearchableEntityDescriptors() + .get(txn.getEntityType()); - List childKeyValues = new ArrayList(); - NodeUtils.extractFieldValuesFromObject(targetEntityInstance, childPrimaryKeyNames, - childKeyValues); + if (searchableDescriptor != null + && searchableDescriptor.getSearchableAttributes().size() > 0) { - String childEntityQueryKeyString = - childEntityType + "." + NodeUtils.concatArray(childPrimaryKeyNames, "/") + ":" - + NodeUtils.concatArray(childKeyValues); + IndexableCrossEntityReference icer = + getPopulatedDocument(targetEntityInstance, cerDescriptor); - /** - * Build generic-query to query child instance self-link from AAI - */ - List orderedQueryKeyParams = new ArrayList(); - if (SERVICE_INSTANCE.equals(childEntityType)) { - orderedQueryKeyParams.clear(); - orderedQueryKeyParams.add(childEntityQueryKeyString); - } else { - orderedQueryKeyParams.add(parentEntityQueryString); - orderedQueryKeyParams.add(childEntityQueryKeyString); - } - String genericQueryStr = null; - try { - genericQueryStr = aaiDataProvider.getGenericQueryForSelfLink(childEntityType, - orderedQueryKeyParams); - - if (genericQueryStr != null) { - aaiWorkOnHand.incrementAndGet(); - OperationResult aaiQueryResult = aaiDataProvider - .queryActiveInventoryWithRetries(genericQueryStr, "application/json", - aaiConfig.getAaiRestConfig().getNumRequestRetries()); - aaiWorkOnHand.decrementAndGet(); - 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(ActiveInventoryConfig.extractResourcePath(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); - } - }); - } + for (String parentCrossEntityReferenceAttributeValue : extractedParentEntityAttributeValues) { + icer.addCrossEntityReferenceValue( + parentCrossEntityReferenceAttributeValue); + } + + icer.setLink(ActiveInventoryConfig.extractResourcePath(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, elasticSearchAdapter), + 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 { + LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_DURING_AAI_RESPONSE_CONVERSION); } - } else { - String message = "Entity sync failed because AAI query failed with error "; - LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_QUERY_ERROR, message); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.JSON_CONVERSION_ERROR, JsonNode.class.toString(), + exc.getLocalizedMessage()); } } else { - String message = - "Entity Sync failed because generic query str could not be determined."; + String message = "Entity sync failed because AAI query failed with error " + + aaiQueryResult.getResult(); LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_QUERY_ERROR, message); } - } catch (Exception exc) { + + } else { String message = - "Failed to sync entity because generation of generic query failed with error = " - + exc.getMessage(); + "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()); + } catch (IOException ioe) { + LOG.error(AaiUiMsgs.JSON_PROCESSING_ERROR, ioe.getMessage()); + } } + } + + } else { + LOG.error(AaiUiMsgs.ENTITY_SYNC_FAILED_DESCRIPTOR_NOT_FOUND, txn.getEntityType()); } } @@ -694,7 +734,7 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer 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()); + MergableEntity merged = updater.readValue(icer.getAsJson()); jsonPayload = mapper.writeValueAsString(merged); } } catch (IOException exc) { @@ -703,14 +743,15 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer return; } } else { - jsonPayload = icer.getIndexDocumentJson(); + jsonPayload = icer.getAsJson(); } if (wasEntryDiscovered) { if (versionNumber != null && jsonPayload != null) { - String requestPayload = esDataProvider.buildBulkImportOperationRequest(getIndexName(), - ElasticSearchConfig.getConfig().getType(), icer.getId(), versionNumber, jsonPayload); + String requestPayload = elasticSearchAdapter.buildBulkImportOperationRequest( + getIndexName(), ElasticSearchConfig.getConfig().getType(), icer.getId(), + versionNumber, jsonPayload); NetworkTransaction transactionTracker = new NetworkTransaction(); transactionTracker.setEntityType(esGetResult.getEntityType()); @@ -719,7 +760,7 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer esWorkOnHand.incrementAndGet(); supplyAsync(new PerformElasticSearchUpdate(ElasticSearchConfig.getConfig().getBulkUrl(), - requestPayload, esDataProvider, transactionTracker), esPutExecutor) + requestPayload, elasticSearchAdapter, transactionTracker), esPutExecutor) .whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -743,7 +784,8 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer updateElasticTxn.setOperationType(HttpMethod.PUT); esWorkOnHand.incrementAndGet(); - supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, esDataProvider), + supplyAsync( + new PerformElasticSearchPut(jsonPayload, updateElasticTxn, elasticSearchAdapter), esPutExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -826,7 +868,7 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer * that for this request already when queuing the failed PUT! */ - supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, esDataProvider), + supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, elasticSearchAdapter), esExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -882,14 +924,14 @@ public class CrossEntityReferenceSynchronizer extends AbstractEntitySynchronizer protected IndexableCrossEntityReference getPopulatedDocument(JsonNode entityNode, OxmEntityDescriptor resultDescriptor) throws JsonProcessingException, IOException { - IndexableCrossEntityReference icer = new IndexableCrossEntityReference(oxmModelLoader); + IndexableCrossEntityReference icer = new IndexableCrossEntityReference(); icer.setEntityType(resultDescriptor.getEntityName()); List primaryKeyValues = new ArrayList(); String pkeyValue = null; - for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) { + for (String keyName : resultDescriptor.getPrimaryKeyAttributeNames()) { pkeyValue = NodeUtils.getNodeFieldAsText(entityNode, keyName); if (pkeyValue != null) { primaryKeyValues.add(pkeyValue); diff --git a/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryAdapter.java b/src/main/java/org/onap/aai/sparky/dal/ActiveInventoryAdapter.java similarity index 50% rename from src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryAdapter.java rename to src/main/java/org/onap/aai/sparky/dal/ActiveInventoryAdapter.java index 08a6584..40bb98c 100644 --- a/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryAdapter.java +++ b/src/main/java/org/onap/aai/sparky/dal/ActiveInventoryAdapter.java @@ -20,44 +20,43 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.dal.aai; +package org.onap.aai.sparky.dal; import java.io.IOException; +import java.net.URISyntaxException; import java.net.URLEncoder; -import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriBuilder; + +import org.apache.http.NameValuePair; import org.apache.http.client.utils.URIBuilder; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.restclient.client.RestClient; +import org.onap.aai.restclient.enums.RestAuthenticationMode; import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; import org.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; -import org.onap.aai.sparky.dal.aai.config.ActiveInventoryRestConfig; -import org.onap.aai.sparky.dal.aai.enums.RestAuthenticationMode; import org.onap.aai.sparky.dal.exception.ElasticSearchOperationException; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.rest.RestClientBuilder; -import org.onap.aai.sparky.dal.rest.RestfulDataAccessor; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.security.SecurityContextFactory; +import org.onap.aai.sparky.util.Encryptor; import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; -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 { +public class ActiveInventoryAdapter { private static final Logger LOG = LoggerFactory.getInstance().getLogger(ActiveInventoryAdapter.class); @@ -66,74 +65,153 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor private static final String HEADER_FROM_APP_ID = "X-FromAppId"; private static final String HEADER_AUTHORIZATION = "Authorization"; + private static final String HTTP_SCHEME = "http"; + private static final String HTTPS_SCHEME = "https"; + private static final String TRANSACTION_ID_PREFIX = "txnId-"; private static final String UI_APP_NAME = "AAI-UI"; + private OxmModelLoader oxmModelLoader; + private OxmEntityLookup oxmEntityLookup; - private ActiveInventoryConfig config; + private RestClient restClient; + + private String activeInventoryIpAddress; + private String activeInventoryServerPort; + private int numRequestRetries; + private String basicAuthUserName; + private String basicAuthPassword; + private RestAuthenticationMode restAuthenticationMode; + private int connectTimeoutInMs; + private int readTimeoutInMs; /** * 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) + + public ActiveInventoryAdapter(OxmModelLoader oxmModelLoader, + RestAuthenticationMode authenticationMode, boolean validateServerHostname, + boolean validateServerCertChain, String certFileName, String certPassword, + String truststoreFileName, int connectTimeoutInMs, int readTimeoutInMs) throws ElasticSearchOperationException, IOException { - super(restClientBuilder); - try { - this.config = ActiveInventoryConfig.getConfig(); - } catch (Exception exc) { - throw new ElasticSearchOperationException("Error getting active inventory configuration", - exc); - } + this.oxmModelLoader = oxmModelLoader; + this.restAuthenticationMode = authenticationMode; + this.connectTimeoutInMs = connectTimeoutInMs; + this.readTimeoutInMs = readTimeoutInMs; + + + Encryptor enc = new Encryptor(); + String certFileNameFullPath = TierSupportUiConstants.CONFIG_AUTH_LOCATION + certFileName; + String decryptedCertPassword = enc.decryptValue(certPassword); + String truststoreFileNameFullPath = + TierSupportUiConstants.CONFIG_AUTH_LOCATION + truststoreFileName; - clientBuilder.setUseHttps(true); + this.restClient = new RestClient().authenticationMode(authenticationMode) + .validateServerCertChain(validateServerCertChain) + .validateServerHostname(validateServerHostname).clientCertFile(certFileNameFullPath) + .clientCertPassword(decryptedCertPassword).trustStore(truststoreFileNameFullPath) + .connectTimeoutMs(connectTimeoutInMs).readTimeoutMs(readTimeoutInMs); - clientBuilder.setValidateServerHostname(config.getAaiSslConfig().isValidateServerHostName()); + } + + public ActiveInventoryAdapter(OxmModelLoader oxmModelLoader, + RestAuthenticationMode authenticationMode, boolean validateServerHostname, + boolean validateServerCertChain, String basicAuthUserName, String basicAuthPassword, + int connectTimeoutInMs, int readTimeoutInMs) + throws ElasticSearchOperationException, IOException { + + this.oxmModelLoader = oxmModelLoader; + this.restAuthenticationMode = authenticationMode; + + this.restClient = new RestClient().authenticationMode(authenticationMode) + .validateServerCertChain(validateServerCertChain) + .validateServerHostname(validateServerHostname).connectTimeoutMs(connectTimeoutInMs) + .readTimeoutMs(readTimeoutInMs); + + this.basicAuthUserName = basicAuthUserName; + this.basicAuthPassword = basicAuthPassword; + + } - SecurityContextFactory sslContextFactory = clientBuilder.getSslContextFactory(); - sslContextFactory.setServerCertificationChainValidationEnabled( - config.getAaiSslConfig().isValidateServerCertificateChain()); + protected Map> getMessageHeaders() { + + Map> headers = new HashMap>(); + + headers.putIfAbsent(HEADER_FROM_APP_ID, new ArrayList()); + headers.get(HEADER_FROM_APP_ID).add(UI_APP_NAME); + + headers.putIfAbsent(HEADER_TRANS_ID, new ArrayList()); + headers.get(HEADER_TRANS_ID).add(TRANSACTION_ID_PREFIX + NodeUtils.getRandomTxnId()); + + if (restAuthenticationMode == RestAuthenticationMode.SSL_BASIC) { + + headers.putIfAbsent(HEADER_AUTHORIZATION, new ArrayList()); + headers.get(HEADER_AUTHORIZATION).add(getBasicAuthenticationCredentials()); - 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()); + return headers; + } + protected String getBasicAuthenticationCredentials() { + String usernameAndPassword = String.join(":", basicAuthUserName, basicAuthPassword); + return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes()); } - /* - * (non-Javadoc) - * - * @see - * org.onap.aai.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()); + public int getNumRequestRetries() { + return numRequestRetries; + } + + + + public void setNumRequestRetries(int numRequestRetries) { + this.numRequestRetries = numRequestRetries; + } + + public OxmEntityLookup getOxmEntityLookup() { + return oxmEntityLookup; + } + + public void setOxmEntityLookup(OxmEntityLookup oxmEntityLookup) { + this.oxmEntityLookup = oxmEntityLookup; + } + + public String getActiveInventoryIpAddress() { + return activeInventoryIpAddress; + } + + public void setActiveInventoryIpAddress(String activeInventoryIpAddress) { + this.activeInventoryIpAddress = activeInventoryIpAddress; + } + + public String getActiveInventoryServerPort() { + return activeInventoryServerPort; + } + + public void setActiveInventoryServerPort(String activeInventoryServerPort) { + this.activeInventoryServerPort = activeInventoryServerPort; + } + + protected String getResourceBasePath() { + + String versionStr = null; + if (oxmModelLoader != null) { + versionStr = String.valueOf(oxmModelLoader.getLatestVersionNum()); } - return builder; + return "/aai/v" + versionStr; + + } + + public int getConnectTimeoutInMs() { + return this.connectTimeoutInMs; + } + + public int getReadTimeoutInMs() { + return this.readTimeoutInMs; } /** @@ -144,11 +222,9 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor * @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); + final String basePath = getResourceBasePath(); + return String.format("https://%s:%s%s%s", activeInventoryIpAddress, activeInventoryServerPort, + basePath, resourceUrl); } public String getGenericQueryForSelfLink(String startNodeType, List queryParams) @@ -165,21 +241,11 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor final String constructedLink = urlBuilder.toString(); - // TODO: debug log for constructed link - return constructedLink; } - /* - * (non-Javadoc) - * - * @see - * org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider#getSelfLinksByEntityType(java.lang. - * String) - */ - @Override public OperationResult getSelfLinksByEntityType(String entityType) throws Exception { /* @@ -192,8 +258,7 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor "Failed to getSelfLinksByEntityType() because entityType is null"); } - OxmEntityDescriptor entityDescriptor = - OxmModelLoader.getInstance().getEntityDescriptor(entityType); + OxmEntityDescriptor entityDescriptor = oxmEntityLookup.getEntityDescriptors().get(entityType); if (entityDescriptor == null) { throw new NoSuchElementException("Failed to getSelfLinksByEntityType() because could" @@ -202,25 +267,16 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor String link = null; final String primaryKeyStr = - NodeUtils.concatArray(entityDescriptor.getPrimaryKeyAttributeName(), "/"); + NodeUtils.concatArray(entityDescriptor.getPrimaryKeyAttributeNames(), "/"); link = getFullUrl("/search/nodes-query?search-node-type=" + entityType + "&filter=" + primaryKeyStr + ":EXISTS"); - - return doGet(link, "application/json"); + return restClient.get(link, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE); } - /* - * (non-Javadoc) - * - * @see - * org.onap.aai.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 { @@ -238,7 +294,6 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor "Failed to getSelfLinkForEntity() because primaryKeyValue is null"); } - /* * 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. @@ -264,8 +319,7 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor } - return queryActiveInventoryWithRetries(link, "application/json", - this.config.getAaiRestConfig().getNumRequestRetries()); + return queryActiveInventoryWithRetries(link, "application/json", numRequestRetries); } @@ -305,25 +359,19 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor */ // package protected for test classes instead of private OperationResult queryActiveInventory(String url, String acceptContentType) { - return doGet(url, acceptContentType); + + return restClient.get(url, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE); + } - /* - * (non-Javadoc) - * - * @see - * org.onap.aai.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++) { + for (int retryCount = 0; retryCount < numRetries; retryCount++) { - LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_SEQ, url, String.valueOf(x + 1)); + LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_SEQ, url, String.valueOf(retryCount + 1)); result = queryActiveInventory(url, responseType); @@ -337,33 +385,12 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor * parallelized threads per task processor. */ - result.setNumRequestRetries(x); + result.setNumRetries(retryCount); 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)); + result.setFromCache(false); + LOG.debug(AaiUiMsgs.QUERY_AAI_RETRY_DONE_SEQ, url, String.valueOf(retryCount + 1)); return result; } @@ -377,31 +404,57 @@ public class ActiveInventoryAdapter extends RestfulDataAccessor LOG.error(AaiUiMsgs.QUERY_AAI_WAIT_INTERRUPTION, exc.getLocalizedMessage()); break; } - LOG.error(AaiUiMsgs.QUERY_AAI_RETRY_FAILURE_WITH_SEQ, url, String.valueOf(x + 1)); - } + LOG.error(AaiUiMsgs.QUERY_AAI_RETRY_FAILURE_WITH_SEQ, url, String.valueOf(retryCount + 1)); + } - result.setResolvedLinkFailure(true); LOG.info(AaiUiMsgs.QUERY_AAI_RETRY_MAXED_OUT, url); return result; } - /* - * (non-Javadoc) + public String repairSelfLink(String selfLink) { + return repairSelfLink(selfLink, null); + } + + /** + * This method adds a scheme, host and port (if missing) to the passed-in URI. If these parts of + * the URI are already present, they will not be duplicated. * - * @see org.onap.aai.sparky.dal.rest.RestfulDataAccessor#shutdown() + * @param selflink The URI to repair + * @param queryParams The query parameters as a single string + * @return The corrected URI (i.e. includes a scheme/host/port) */ - @Override - public void shutdown() { - // TODO Auto-generated method stub + public String repairSelfLink(String selflink, String queryParams) { + if (selflink == null) { + return selflink; + } - if (entityCache != null) { - entityCache.shutdown(); + UriBuilder builder = UriBuilder.fromPath(selflink).host(activeInventoryIpAddress) + .port(Integer.parseInt(activeInventoryServerPort)); + + switch (restAuthenticationMode) { + + case SSL_BASIC: + case SSL_CERT: { + builder.scheme(HTTPS_SCHEME); + break; + } + + default: { + builder.scheme(HTTP_SCHEME); + } } - } + boolean includeQueryParams = ((null != queryParams) && (!"".equals(queryParams))); + /* + * builder.build().toString() will encode special characters to hexadecimal pairs prefixed with + * a '%' so we're adding the query parameters separately, in their UTF-8 representations, so + * that characters such as '?', '&', etc. remain intact as needed by the synchronizer + */ + return (builder.build().toString() + (includeQueryParams ? queryParams : "")); + } } diff --git a/src/main/java/org/onap/aai/sparky/dal/ElasticSearchAdapter.java b/src/main/java/org/onap/aai/sparky/dal/ElasticSearchAdapter.java new file mode 100644 index 0000000..1e2bb8d --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/dal/ElasticSearchAdapter.java @@ -0,0 +1,120 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.dal; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MediaType; + +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.restclient.client.RestClient; +import org.onap.aai.restclient.enums.RestAuthenticationMode; + +/** + * The Class ElasticSearchAdapter. + * + */ +public class ElasticSearchAdapter { + + private static final String BULK_IMPORT_INDEX_TEMPLATE = + "{\"index\":{\"_index\":\"%s\",\"_type\":\"%s\",\"_id\":\"%s\", \"_version\":\"%s\"}}\n"; + + private RestClient restClient; + + /** + * Instantiates a new elastic search adapter. + */ + public ElasticSearchAdapter(RestAuthenticationMode restAuthenticationMode, int connectTimeoutInMs, + int readTimeoutInMs) { + + this.restClient = new RestClient().authenticationMode(restAuthenticationMode) + .connectTimeoutMs(connectTimeoutInMs).readTimeoutMs(readTimeoutInMs); + + } + + protected Map> getMessageHeaders() { + Map> headers = new HashMap>(); + // insert mandatory headers if there are any + return headers; + } + + public OperationResult doGet(String url, MediaType acceptContentType) { + return restClient.get(url, getMessageHeaders(), acceptContentType); + } + + public OperationResult doDelete(String url, MediaType acceptContentType) { + return restClient.delete(url, getMessageHeaders(), acceptContentType); + } + + public OperationResult doPost(String url, String jsonPayload, MediaType acceptContentType) { + return restClient.post(url, jsonPayload, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE, + acceptContentType); + } + + public OperationResult doPut(String url, String jsonPayload, MediaType acceptContentType) { + return restClient.put(url, jsonPayload, getMessageHeaders(), MediaType.APPLICATION_JSON_TYPE, + acceptContentType); + } + + public OperationResult doPatch(String url, String jsonPayload, MediaType acceptContentType) { + + Map> headers = getMessageHeaders(); + headers.putIfAbsent("X-HTTP-Method-Override", new ArrayList()); + headers.get("X-HTTP-Method-Override").add("PATCH"); + + return restClient.post(url, jsonPayload, headers, MediaType.APPLICATION_JSON_TYPE, + acceptContentType); + } + + public OperationResult doHead(String url, MediaType acceptContentType) { + return restClient.head(url, getMessageHeaders(), acceptContentType); + } + + public OperationResult doBulkOperation(String url, String payload) { + return restClient.put(url, payload, getMessageHeaders(), + MediaType.APPLICATION_FORM_URLENCODED_TYPE, MediaType.APPLICATION_JSON_TYPE); + } + + 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(); + + } + + public OperationResult retrieveEntityById(String host, String port, String indexName, + String docType, String resourceUrl) { + String esUrl = + String.format("http://%s:%s/%s/%s/%s", host, port, indexName, docType, resourceUrl); + return doGet(esUrl, MediaType.APPLICATION_JSON_TYPE); + } + +} diff --git a/src/main/java/org/onap/aai/sparky/dal/NetworkTransaction.java b/src/main/java/org/onap/aai/sparky/dal/NetworkTransaction.java index da24c80..fbc89c3 100644 --- a/src/main/java/org/onap/aai/sparky/dal/NetworkTransaction.java +++ b/src/main/java/org/onap/aai/sparky/dal/NetworkTransaction.java @@ -22,9 +22,10 @@ */ package org.onap.aai.sparky.dal; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; + /** * The Class NetworkTransaction. @@ -37,12 +38,16 @@ public class NetworkTransaction { private String link; + private String queryParameters; + private HttpMethod operationType; private OxmEntityDescriptor descriptor; private long createdTimeStampInMs; + private long opTimeInMs; + private long taskAgeInMs; /** @@ -50,6 +55,7 @@ public class NetworkTransaction { */ public NetworkTransaction() { this.createdTimeStampInMs = System.currentTimeMillis(); + this.opTimeInMs = 0L; } /** @@ -64,6 +70,7 @@ public class NetworkTransaction { this.operationType = method; this.entityType = entityType; this.operationResult = or; + this.opTimeInMs = 0L; } public HttpMethod getOperationType() { @@ -109,6 +116,22 @@ public class NetworkTransaction { this.link = link; } + public String getQueryParameters() { + return queryParameters; + } + + public void setQueryParameters(String queryParameters) { + this.queryParameters = queryParameters; + } + + public long getOpTimeInMs() { + return opTimeInMs; + } + + public void setOpTimeInMs(long opTimeInMs) { + this.opTimeInMs = opTimeInMs; + } + public OxmEntityDescriptor getDescriptor() { return descriptor; } diff --git a/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryDataProvider.java b/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryDataProvider.java index 21fb4e6..75e7a54 100644 --- a/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryDataProvider.java +++ b/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryDataProvider.java @@ -30,6 +30,11 @@ import org.onap.aai.sparky.dal.rest.RestDataProvider; /** * The Interface ActiveInventoryDataProvider. */ + +/* + * TODO: DELETE ME + */ + public interface ActiveInventoryDataProvider extends RestDataProvider { /** @@ -81,7 +86,7 @@ public interface ActiveInventoryDataProvider extends RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#shutdown() + * @see org.openecomp.sparky.dal.rest.RestDataProvider#shutdown() */ @Override void shutdown(); diff --git a/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryEntityStatistics.java b/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryEntityStatistics.java index c1ed906..6ffebef 100644 --- a/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryEntityStatistics.java +++ b/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryEntityStatistics.java @@ -29,10 +29,9 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; -import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.rest.OperationResult; + /** * The Class ActiveInventoryEntityStatistics. @@ -51,9 +50,6 @@ public class ActiveInventoryEntityStatistics { private static final String ERROR = "Error"; - private OxmModelLoader loader; - - private Map> activeInventoryEntityStatistics; /** @@ -76,30 +72,6 @@ public class ActiveInventoryEntityStatistics { } - /* - * 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. */ @@ -126,11 +98,8 @@ public class ActiveInventoryEntityStatistics { * * @param loader the loader */ - public ActiveInventoryEntityStatistics(OxmModelLoader loader) { - this.loader = loader; + public ActiveInventoryEntityStatistics() { activeInventoryEntityStatistics = new HashMap>(); - // createSearchableActiveInventoryEntityStatistics(); - // createCrossEntityReferenceActiveInventoryEntityStatistics(); reset(); } @@ -139,21 +108,29 @@ public class ActiveInventoryEntityStatistics { * * @param descriptors the descriptors */ - public void initializeCountersFromOxmEntityDescriptors( - Map descriptors) { + public void intializeEntityCounters(String... entityTypes) { + + if (entityTypes != null && entityTypes.length > 0) { + for (String entityType : entityTypes) { + activeInventoryEntityStatistics.put(entityType, createEntityOpStats()); + } - if (descriptors == null) { - return; } - OxmEntityDescriptor descriptor = null; - for (String key : descriptors.keySet()) { - descriptor = descriptors.get(key); - activeInventoryEntityStatistics.put(descriptor.getEntityName(), createEntityOpStats()); + } + + public void intializeEntityCounters(Set entityTypes) { + + if (entityTypes != null && entityTypes.size() > 0) { + for (String entityType : entityTypes) { + activeInventoryEntityStatistics.put(entityType, createEntityOpStats()); + } } + } + /** * Reset. */ @@ -230,8 +207,8 @@ public class ActiveInventoryEntityStatistics { opStats.get(NO_PAYLOAD).incrementAndGet(); } - if (or.getNumRequestRetries() > 0) { - opStats.get(NUM_RETRIES).addAndGet(or.getNumRequestRetries()); + if (or.getNumRetries() > 0) { + opStats.get(NUM_RETRIES).addAndGet(or.getNumRetries()); } } diff --git a/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryProcessingExceptionStatistics.java b/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryProcessingExceptionStatistics.java index eb4eb6c..329d0f0 100644 --- a/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryProcessingExceptionStatistics.java +++ b/src/main/java/org/onap/aai/sparky/dal/aai/ActiveInventoryProcessingExceptionStatistics.java @@ -22,12 +22,13 @@ */ package org.onap.aai.sparky.dal.aai; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.analytics.AbstractStatistics; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; /** * The Class ActiveInventoryProcessingExceptionStatistics. diff --git a/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryConfig.java b/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryConfig.java index e88ca51..d311993 100644 --- a/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryConfig.java +++ b/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryConfig.java @@ -26,30 +26,25 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Properties; -import javax.ws.rs.core.UriBuilder; - +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.config.TaskProcessorConfig; import org.onap.aai.sparky.util.ConfigHelper; import org.onap.aai.sparky.util.Encryptor; import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; /** * 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 Logger LOG = LoggerFactory.getInstance().getLogger(ActiveInventoryConfig.class); - private static final String HTTP_SCHEME = "http"; - private static final String HTTPS_SCHEME = "https"; + public static ActiveInventoryConfig getConfig() throws Exception { if (instance == null) { @@ -61,7 +56,6 @@ public class ActiveInventoryConfig { private ActiveInventoryRestConfig aaiRestConfig; private ActiveInventorySslConfig aaiSslConfig; - private TaskProcessorConfig taskProcessorConfig; /** * Instantiates a new active inventory config. @@ -71,37 +65,18 @@ public class ActiveInventoryConfig { 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)); - - + initialize(props); } - protected ActiveInventoryConfig(Properties props) throws Exception { + public ActiveInventoryConfig(Properties props) throws Exception { + initialize(props); + } + private void initialize(Properties props) { 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; } @@ -118,35 +93,11 @@ public class ActiveInventoryConfig { 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(); - - } public static String extractResourcePath(String selflink) { try { - return new URI(selflink).getPath(); + return new URI(selflink).getRawPath(); } catch (URISyntaxException uriSyntaxException) { LOG.error(AaiUiMsgs.ERROR_EXTRACTING_RESOURCE_PATH_FROM_LINK, uriSyntaxException.getMessage()); @@ -165,9 +116,6 @@ public class ActiveInventoryConfig { + aaiSslConfig + "]"; } - public URI getBaseUri() { - return UriBuilder.fromUri("https://" + aaiRestConfig.getHost() + ":" + aaiRestConfig.getPort() - + aaiRestConfig.getResourceBasePath()).build(); - } + } diff --git a/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryRestConfig.java b/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryRestConfig.java index 5ed537b..617a74c 100644 --- a/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryRestConfig.java +++ b/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventoryRestConfig.java @@ -36,27 +36,6 @@ public class ActiveInventoryRestConfig { private String host; - /** - * @return the cacheFailures - */ - public boolean isCacheFailures() { - return cacheFailures; - } - - /** - * @param cacheFailures the cacheFailures to set - */ - public void setCacheFailures(boolean cacheFailures) { - this.cacheFailures = cacheFailures; - } - - /** - * @param shallowEntities the shallowEntities to set - */ - public void setShallowEntities(List shallowEntities) { - this.shallowEntities = shallowEntities; - } - private String port; private int connectTimeoutInMs; @@ -67,20 +46,6 @@ public class ActiveInventoryRestConfig { 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; @@ -96,13 +61,12 @@ public class ActiveInventoryRestConfig { */ public ActiveInventoryRestConfig(Properties props) { - if (props == null) { + if (props == null || props.isEmpty()) { 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")); @@ -114,23 +78,6 @@ public class ActiveInventoryRestConfig { 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())); @@ -154,26 +101,6 @@ public class ActiveInventoryRestConfig { 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. @@ -195,14 +122,6 @@ public class ActiveInventoryRestConfig { return false; } - public boolean isUseCacheOnly() { - return useCacheOnly; - } - - public void setUseCacheOnly(boolean useCacheOnly) { - this.useCacheOnly = useCacheOnly; - } - public int getNumResolverWorkers() { return numResolverWorkers; } @@ -211,30 +130,6 @@ public class ActiveInventoryRestConfig { 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; } @@ -243,10 +138,6 @@ public class ActiveInventoryRestConfig { return port; } - public String getResourceBasePath() { - return resourceBasePath; - } - public void setHost(String host) { this.host = host; } @@ -255,29 +146,6 @@ public class ActiveInventoryRestConfig { 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; } diff --git a/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventorySslConfig.java b/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventorySslConfig.java index 080a787..75ce36a 100644 --- a/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventorySslConfig.java +++ b/src/main/java/org/onap/aai/sparky/dal/aai/config/ActiveInventorySslConfig.java @@ -56,7 +56,7 @@ public class ActiveInventorySslConfig { */ public ActiveInventorySslConfig(Properties props, Encryptor encryptor) { - if (props == null) { + if (props == null || props.isEmpty()) { return; } @@ -197,20 +197,6 @@ public class ActiveInventorySslConfig { return "Basic " + java.util.Base64.getEncoder().encodeToString(usernameAndPassword.getBytes()); } - /** - * @return the enableSslDebug - */ - public boolean isEnableSslDebug() { - return enableSslDebug; - } - - /** - * @param enableSslDebug the enableSslDebug to set - */ - public void setEnableSslDebug(boolean enableSslDebug) { - this.enableSslDebug = enableSslDebug; - } - /* * (non-Javadoc) * diff --git a/src/main/java/org/onap/aai/sparky/dal/cache/InMemoryEntityCache.java b/src/main/java/org/onap/aai/sparky/dal/cache/InMemoryEntityCache.java deleted file mode 100644 index 5245e29..0000000 --- a/src/main/java/org/onap/aai/sparky/dal/cache/InMemoryEntityCache.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.dal.cache; - -import java.util.concurrent.ConcurrentHashMap; - -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; - -/** - * 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.onap.aai.sparky.dal.cache.EntityCache#put(java.lang.String, - * org.onap.aai.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.onap.aai.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.onap.aai.sparky.dal.cache.EntityCache#shutdown() - */ - @Override - public void shutdown() { - // TODO Auto-generated method stub - // nothing to do - - } - - /* - * (non-Javadoc) - * - * @see org.onap.aai.sparky.dal.cache.EntityCache#clear() - */ - @Override - public void clear() { - cachedEntityData.clear(); - } - -} diff --git a/src/main/java/org/onap/aai/sparky/dal/cache/PersistentEntityCache.java b/src/main/java/org/onap/aai/sparky/dal/cache/PersistentEntityCache.java deleted file mode 100644 index f64b3c7..0000000 --- a/src/main/java/org/onap/aai/sparky/dal/cache/PersistentEntityCache.java +++ /dev/null @@ -1,256 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.dal.aai.ActiveInventoryAdapter; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.task.PersistOperationResultToDisk; -import org.onap.aai.sparky.synchronizer.task.RetrieveOperationResultFromDisk; -import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; - -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.onap.aai.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.onap.aai.sparky.dal.cache.EntityCache#put(java.lang.String, - * org.onap.aai.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()); - } - - }); - } - - } - - - /* - * (non-Javadoc) - * - * @see org.onap.aai.sparky.dal.cache.EntityCache#shutdown() - */ - @Override - public void shutdown() { - if (persistentExecutor != null) { - persistentExecutor.shutdown(); - } - - } - - /* - * (non-Javadoc) - * - * @see org.onap.aai.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/onap/aai/sparky/dal/elasticsearch/ElasticSearchAdapter.java b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchAdapter.java deleted file mode 100644 index 9962bcb..0000000 --- a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchAdapter.java +++ /dev/null @@ -1,213 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.dal.elasticsearch; - -import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; -import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.rest.RestDataProvider; -import org.onap.aai.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.onap.aai.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.onap.aai.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.onap.aai.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.onap.aai.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.onap.aai.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.onap.aai.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.onap.aai.sparky.dal.rest.RestDataProvider#clearCache() - */ - @Override - public void clearCache() { - restDataProvider.clearCache(); - } - - /* - * (non-Javadoc) - * - * @see org.onap.aai.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.onap.aai.sparky.dal.elasticsearch.ElasticSearchDataProvider#shutdown() - */ - @Override - public void shutdown() { - restDataProvider.shutdown(); - } - - /* - * (non-Javadoc) - * - * @see - * org.onap.aai.sparky.dal.rest.RestDataProvider#doRestfulOperation(org.onap.aai.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.onap.aai.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"); - } - - /** - * @return the bulkImportIndexTemplate - */ - public static String getBulkImportIndexTemplate() { - return BULK_IMPORT_INDEX_TEMPLATE; - } - - /** - * @return the restDataProvider - */ - public RestDataProvider getRestDataProvider() { - return restDataProvider; - } - - /** - * @return the esConfig - */ - public ElasticSearchConfig getEsConfig() { - return esConfig; - } - -} diff --git a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchDataProvider.java b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchDataProvider.java index 416e251..90075fe 100644 --- a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchDataProvider.java +++ b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchDataProvider.java @@ -25,6 +25,10 @@ package org.onap.aai.sparky.dal.elasticsearch; import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.dal.rest.RestDataProvider; +/* + * TODO: DELETE ME + */ + /** * The Interface ElasticSearchDataProvider. */ @@ -57,7 +61,7 @@ public interface ElasticSearchDataProvider extends RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#shutdown() + * @see org.openecomp.sparky.dal.rest.RestDataProvider#shutdown() */ @Override void shutdown(); diff --git a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchEntityStatistics.java b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchEntityStatistics.java index 50d318b..ba012bd 100644 --- a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchEntityStatistics.java +++ b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/ElasticSearchEntityStatistics.java @@ -29,11 +29,10 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; -import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.dal.NetworkTransaction; import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; + /** * The Class ElasticSearchEntityStatistics. @@ -48,7 +47,6 @@ public class ElasticSearchEntityStatistics { private static final String ERROR = "ERROR"; private Map> entityStatistics; - private OxmModelLoader loader; /** * Creates the entity op stats. @@ -70,19 +68,6 @@ public class ElasticSearchEntityStatistics { } - /* - * 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. */ @@ -109,10 +94,8 @@ public class ElasticSearchEntityStatistics { * * @param loader the loader */ - public ElasticSearchEntityStatistics(OxmModelLoader loader) { - this.loader = loader; + public ElasticSearchEntityStatistics() { entityStatistics = new HashMap>(); - // createActiveInventoryEntityStatistics(); reset(); } @@ -121,18 +104,25 @@ public class ElasticSearchEntityStatistics { * * @param descriptors the descriptors */ - public void initializeCountersFromOxmEntityDescriptors( - Map descriptors) { + public void intializeEntityCounters(String... entityTypes) { + + if (entityTypes != null && entityTypes.length > 0) { + for (String entityType : entityTypes) { + entityStatistics.put(entityType, createEntityOpStats()); + } - if (descriptors == null) { - return; } - OxmEntityDescriptor descriptor = null; - for (String key : descriptors.keySet()) { - descriptor = descriptors.get(key); - entityStatistics.put(descriptor.getEntityName(), createEntityOpStats()); + } + + public void intializeEntityCounters(Set entityTypes) { + + if (entityTypes != null && entityTypes.size() > 0) { + for (String entityType : entityTypes) { + entityStatistics.put(entityType, createEntityOpStats()); + } } + } /** diff --git a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/HashQueryResponse.java b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/HashQueryResponse.java index 646916b..8abf20f 100644 --- a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/HashQueryResponse.java +++ b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/HashQueryResponse.java @@ -22,8 +22,7 @@ */ package org.onap.aai.sparky.dal.elasticsearch; -import org.json.JSONObject; -import org.onap.aai.sparky.dal.rest.OperationResult; +import org.onap.aai.restclient.client.OperationResult; public class HashQueryResponse { private String jsonPayload = null; diff --git a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/SearchAdapter.java b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/SearchAdapter.java index 200f405..c4e81b7 100644 --- a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/SearchAdapter.java +++ b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/SearchAdapter.java @@ -29,20 +29,17 @@ import java.util.Map; import javax.ws.rs.core.MediaType; -import org.onap.aai.sparky.dal.rest.OperationResult; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.Headers; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.restclient.client.RestClient; import org.onap.aai.sparky.dal.sas.config.SearchServiceConfig; import org.onap.aai.sparky.util.Encryptor; import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import org.slf4j.MDC; -import org.onap.aai.restclient.client.RestClient; -import org.onap.aai.restclient.enums.RestAuthenticationMode; -import org.onap.aai.restclient.client.Headers; -import org.onap.aai.cl.mdc.MdcContext; - -import org.onap.aai.cl.mdc.MdcContext; /** * The Class SearchAdapter. @@ -53,41 +50,6 @@ public class SearchAdapter { private RestClient client; - /** - * @return the client - */ - public RestClient getClient() { - return client; - } - - /** - * @param client the client to set - */ - public void setClient(RestClient client) { - this.client = client; - } - - /** - * @return the commonHeaders - */ - public Map> getCommonHeaders() { - return commonHeaders; - } - - /** - * @param commonHeaders the commonHeaders to set - */ - public void setCommonHeaders(Map> commonHeaders) { - this.commonHeaders = commonHeaders; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - private Map> commonHeaders; private SearchServiceConfig sasConfig; @@ -99,8 +61,8 @@ public class SearchAdapter { public SearchAdapter() throws Exception { sasConfig = SearchServiceConfig.getConfig(); Encryptor encryptor = new Encryptor(); - client = new RestClient().authenticationMode(RestAuthenticationMode.SSL_CERT) - .validateServerHostname(false).validateServerCertChain(false) + + client = new RestClient().validateServerHostname(false).validateServerCertChain(false) .clientCertFile(TierSupportUiConstants.CONFIG_AUTH_LOCATION + sasConfig.getCertName()) .clientCertPassword(encryptor.decryptValue(sasConfig.getKeystorePassword())) .trustStore(TierSupportUiConstants.CONFIG_AUTH_LOCATION + sasConfig.getKeystore()); @@ -119,27 +81,25 @@ public class SearchAdapter { } public OperationResult doPost(String url, String jsonPayload, String acceptContentType) { - org.onap.aai.restclient.client.OperationResult or = client.post(url, jsonPayload, - getTxnHeader(), MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + 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.onap.aai.restclient.client.OperationResult or = - client.get(url, getTxnHeader(), MediaType.APPLICATION_JSON_TYPE); + 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.onap.aai.restclient.client.OperationResult or = client.put(url, payload, getTxnHeader(), - MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_JSON_TYPE); + 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.onap.aai.restclient.client.OperationResult or = - client.delete(url, getTxnHeader(), MediaType.APPLICATION_JSON_TYPE); + OperationResult or = client.delete(url, getTxnHeader(), MediaType.APPLICATION_JSON_TYPE); return new OperationResult(or.getResultCode(), or.getResult()); } diff --git a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/config/ElasticSearchConfig.java b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/config/ElasticSearchConfig.java index 68e4151..c3c27f8 100644 --- a/src/main/java/org/onap/aai/sparky/dal/elasticsearch/config/ElasticSearchConfig.java +++ b/src/main/java/org/onap/aai/sparky/dal/elasticsearch/config/ElasticSearchConfig.java @@ -22,21 +22,11 @@ */ package org.onap.aai.sparky.dal.elasticsearch.config; -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; import java.util.Properties; -import org.onap.aai.sparky.dal.exception.ElasticSearchOperationException; -import org.onap.aai.sparky.synchronizer.config.TaskProcessorConfig; import org.onap.aai.sparky.util.ConfigHelper; import org.onap.aai.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. @@ -64,9 +54,7 @@ public class ElasticSearchConfig { private String settingsFileName; - private int syncAdapterMaxConcurrentWorkers; - - private String auditIndexName; + private String topographicalSearchIndex; private String entityCountHistoryIndex; @@ -112,16 +100,6 @@ public class ElasticSearchConfig { 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) { @@ -174,6 +152,10 @@ public class ElasticSearchConfig { private void initializeProperties() { Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); + if (props == null || props.isEmpty()) { + return; + } + 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); @@ -182,9 +164,11 @@ public class ElasticSearchConfig { 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"); @@ -197,13 +181,6 @@ public class ElasticSearchConfig { 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() { @@ -266,24 +243,16 @@ public class ElasticSearchConfig { 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 String getTopographicalSearchIndex() { + return topographicalSearchIndex; } - public void setAuditIndexName(String auditIndexName) { - this.auditIndexName = auditIndexName; + public void setTopographicalSearchIndex(String topographicalSearchIndex) { + this.topographicalSearchIndex = topographicalSearchIndex; } public String getEntityCountHistoryIndex() { @@ -309,55 +278,6 @@ public class ElasticSearchConfig { 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; } @@ -382,266 +302,4 @@ public class ElasticSearchConfig { 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); - } - - } - - /** - * @return the instance - */ - public static ElasticSearchConfig getInstance() { - return instance; - } - - /** - * @param instance the instance to set - */ - public static void setInstance(ElasticSearchConfig instance) { - ElasticSearchConfig.instance = instance; - } - - /** - * @return the configFile - */ - public static String getConfigFile() { - return CONFIG_FILE; - } - - /** - * @return the ipAddressDefault - */ - public static String getIpAddressDefault() { - return IP_ADDRESS_DEFAULT; - } - - /** - * @return the httpPortDefault - */ - public static String getHttpPortDefault() { - return HTTP_PORT_DEFAULT; - } - - /** - * @return the javaApiPortDefault - */ - public static String getJavaApiPortDefault() { - return JAVA_API_PORT_DEFAULT; - } - - /** - * @return the typeDefault - */ - public static String getTypeDefault() { - return TYPE_DEFAULT; - } - - /** - * @return the clusterNameDefault - */ - public static String getClusterNameDefault() { - return CLUSTER_NAME_DEFAULT; - } - - /** - * @return the indexNameDefault - */ - public static String getIndexNameDefault() { - return INDEX_NAME_DEFAULT; - } - - /** - * @return the auditIndexNameDefault - */ - public static String getAuditIndexNameDefault() { - return AUDIT_INDEX_NAME_DEFAULT; - } - - /** - * @return the topographicalIndexNameDefault - */ - public static String getTopographicalIndexNameDefault() { - return TOPOGRAPHICAL_INDEX_NAME_DEFAULT; - } - - /** - * @return the entityCountHistoryIndexNameDefault - */ - public static String getEntityCountHistoryIndexNameDefault() { - return ENTITY_COUNT_HISTORY_INDEX_NAME_DEFAULT; - } - - /** - * @return the entityAutoSuggestIndexNameDefault - */ - public static String getEntityAutoSuggestIndexNameDefault() { - return ENTITY_AUTO_SUGGEST_INDEX_NAME_DEFAULT; - } - - /** - * @return the entityAutoSuggestSettingsFileDefault - */ - public static String getEntityAutoSuggestSettingsFileDefault() { - return ENTITY_AUTO_SUGGEST_SETTINGS_FILE_DEFAULT; - } - - /** - * @return the entityAutoSuggestMappingsFileDefault - */ - public static String getEntityAutoSuggestMappingsFileDefault() { - return ENTITY_AUTO_SUGGEST_MAPPINGS_FILE_DEFAULT; - } - - /** - * @return the entityDynamicMappingsFileDefault - */ - public static String getEntityDynamicMappingsFileDefault() { - return ENTITY_DYNAMIC_MAPPINGS_FILE_DEFAULT; - } - - /** - * @return the bulkApi - */ - public static String getBulkApi() { - return BULK_API; - } - - /* - * (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 - + ", entityCountHistoryIndex=" + entityCountHistoryIndex + ", autosuggestIndexname=" - + autosuggestIndexname + ", entityCountHistoryMappingsFileName=" - + entityCountHistoryMappingsFileName + ", autoSuggestSettingsFileName=" - + autoSuggestSettingsFileName + ", autoSuggestMappingsFileName=" - + autoSuggestMappingsFileName + ", dynamicMappingsFileName=" + dynamicMappingsFileName - + ", processorConfig=" + processorConfig + "]"; - } } diff --git a/src/main/java/org/onap/aai/sparky/dal/proxy/config/DataRouterConfig.java b/src/main/java/org/onap/aai/sparky/dal/proxy/config/DataRouterConfig.java new file mode 100644 index 0000000..df2ae13 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/dal/proxy/config/DataRouterConfig.java @@ -0,0 +1,132 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.dal.proxy.config; + +import java.util.Properties; + +import org.onap.aai.sparky.util.ConfigHelper; +import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; + +public class DataRouterConfig { + private String host; + private String port; + private String drUriSuffix; + private String certName; + private String keystorePassword; + private String keystore; + private int connectTimeout; + private int readTimeout; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getPort() { + return port; + } + + public void setPort(String port) { + this.port = port; + } + + 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; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public int getReadTimeout() { + return readTimeout; + } + + public void setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + } + + public String getDrUriSuffix() { + return drUriSuffix; + } + + public void setDrUriSuffix(String drUriSuffix) { + this.drUriSuffix = drUriSuffix; + } + + public DataRouterConfig(Properties props) { + + if (props == null) { + return; + } + + Properties restProps = ConfigHelper.getConfigWithPrefix("data-router.rest", props); + host = restProps.getProperty(TierSupportUiConstants.IP_ADDRESS, "localhost"); + port = restProps.getProperty(TierSupportUiConstants.PORT, "9502"); + drUriSuffix = restProps.getProperty(TierSupportUiConstants.DR_URI_SUFFIX, "ui-request"); + connectTimeout = + Integer.parseInt(restProps.getProperty(TierSupportUiConstants.DR_CONNECT_TIMEOUT, "5000")); + readTimeout = + Integer.parseInt(restProps.getProperty(TierSupportUiConstants.DR_READ_TIMEOUT, "1000")); + + Properties sslProps = ConfigHelper.getConfigWithPrefix("data-router.ssl", props); + certName = sslProps.getProperty(TierSupportUiConstants.DR_CERT_NAME, "aai-client-cert.p12"); + keystorePassword = sslProps.getProperty(TierSupportUiConstants.DR_KEYSTORE_PASSWORD, ""); + keystore = sslProps.getProperty(TierSupportUiConstants.DR_KEYSTORE, "tomcat_keystore"); + } + + @Override + public String toString() { + return "DataRouterConfig [host=" + host + ", port=" + port + ", drUriSuffix=" + drUriSuffix + + ", certName=" + certName + ", keystorePassword=" + keystorePassword + ", keystore=" + + keystore + ", connectTimeout=" + connectTimeout + ", readTimeout=" + readTimeout + "]"; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/dal/proxy/processor/AaiUiProxyProcessor.java b/src/main/java/org/onap/aai/sparky/dal/proxy/processor/AaiUiProxyProcessor.java new file mode 100644 index 0000000..444a34b --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/dal/proxy/processor/AaiUiProxyProcessor.java @@ -0,0 +1,227 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.dal.proxy.processor; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.json.Json; +import javax.json.JsonObjectBuilder; +import javax.servlet.http.HttpServletRequest; + +import org.apache.camel.Exchange; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.restclient.client.RestClient; +import org.onap.aai.restclient.rest.HttpUtil; +import org.onap.aai.sparky.dal.proxy.config.DataRouterConfig; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.util.ConfigHelper; +import org.onap.aai.sparky.util.Encryptor; +import org.onap.aai.sparky.util.NodeUtils; +import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; +import org.slf4j.MDC; + +/** + * The Class AaiUiProxyProcessor. + */ +public class AaiUiProxyProcessor { + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(AaiUiProxyProcessor.class); + private static Logger auditLogger = + LoggerFactory.getInstance().getAuditLogger(AaiUiProxyProcessor.class.getName()); + public String configFile = + TierSupportUiConstants.DYNAMIC_CONFIG_APP_LOCATION + "data-router.properties"; + + private RestClient client; + private DataRouterConfig config; + private String drBaseUrl; + private OperationResult operationResult = null; + + private String xTransactionId; + private String xFromAppId; + + private static final String ROUTER_SERVICE = "routerService"; + + public String getDrBaseUrl() { + return drBaseUrl; + } + + public void setDrBaseUrl(String drBaseUrl) { + this.drBaseUrl = drBaseUrl; + } + + /** + * Instantiates a new AaiUiProxyProcessor. + */ + + public AaiUiProxyProcessor() { + Properties props = ConfigHelper.loadConfigFromExplicitPath(configFile); + config = new DataRouterConfig(props); + initializeProxyProcessor(config); + } + + public AaiUiProxyProcessor(DataRouterConfig config) { + initializeProxyProcessor(config); + } + + private void initializeProxyProcessor(DataRouterConfig config) { + Encryptor encryptor = new Encryptor(); + client = new RestClient().validateServerHostname(false).validateServerCertChain(false) + .clientCertFile(TierSupportUiConstants.CONFIG_AUTH_LOCATION + config.getCertName()) + .clientCertPassword(encryptor.decryptValue(config.getKeystorePassword())) + .trustStore(TierSupportUiConstants.CONFIG_AUTH_LOCATION + config.getKeystore()) + .connectTimeoutMs(config.getConnectTimeout()).readTimeoutMs(config.getReadTimeout()); + + drBaseUrl = + "https://" + config.getHost() + ":" + config.getPort() + "/" + config.getDrUriSuffix(); + } + + void setUpMdcContext(final Exchange exchange, final HttpServletRequest request) { + + Object xTransactionId = exchange.getIn().getHeader("X-TransactionId"); + if (xTransactionId == null) { + this.xTransactionId = NodeUtils.getRandomTxnId(); + } else { + this.xTransactionId = (String) xTransactionId; + } + + Object partnerName = exchange.getIn().getHeader("X-FromAppId"); + if (partnerName == null) { + xFromAppId = "Browser"; + } else { + xFromAppId = (String) partnerName; + } + + MdcContext.initialize((String) xTransactionId, "AAI-UI", "", xFromAppId, + request.getRequestURI() + ":" + request.getLocalPort()); + } + + private Map> getHeaders() { + Map> headers = new HashMap<>(); + headers.put("X-FromAppId", Arrays.asList(TierSupportUiConstants.APP_NAME)); + 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; + } + + private String getProxyPayloadAsString(final Exchange exchange) { + JsonObjectBuilder jsonBuilder = Json.createObjectBuilder(); + String srcUri = ""; + try { + srcUri = (String) exchange.getIn().getHeader(Exchange.HTTP_URI); + jsonBuilder.add("origin-uri", srcUri); + + String body = exchange.getIn().getBody(String.class); + + if (body != null && body.length() != 0) { + jsonBuilder.add("origin-payload", body); + } + + } catch (Exception e) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Failed to extract payload for proxying.\n" + "Requestor URL: " + srcUri); + } + + return jsonBuilder.build().toString(); + } + + private String getDrUrl(String requestUri) { + String url = ""; + int pos = requestUri.indexOf(ROUTER_SERVICE); + if (pos != -1) { + url = drBaseUrl + requestUri.substring(pos + ROUTER_SERVICE.length()); + } else { + LOG.error(AaiUiMsgs.DR_REQUEST_URI_FOR_PROXY_UNKNOWN, requestUri); + } + return url; + } + + public void proxyMessage(Exchange exchange) { + HttpServletRequest request = exchange.getIn().getBody(HttpServletRequest.class); + + setUpMdcContext(exchange, request); + + try { + Map> headers = getHeaders(); + String proxyPayload = getProxyPayloadAsString(exchange); + String fromUrl = (String) exchange.getIn().getHeader(Exchange.HTTP_URI); + String toUrl = getDrUrl(fromUrl); + auditLogger.info(AaiUiMsgs.DR_PROXY_FROM_TO, fromUrl, toUrl); + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, + "Proxying request:\n" + proxyPayload + "\n" + "Target URL:\n" + toUrl); + + long startTimeInMs = System.currentTimeMillis(); + + operationResult = client.post(toUrl, proxyPayload, headers, + javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE, + javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE); + + long drOpTime = (System.currentTimeMillis() - startTimeInMs); + int rc = operationResult.getResultCode(); + String result = ""; + + if (HttpUtil.isHttpResponseClassSuccess(rc)) { + result = operationResult.getResult(); + } else { + result = operationResult.getFailureCause(); + LOG.info(AaiUiMsgs.DR_PROCESSING_FAILURE, String.valueOf(rc), proxyPayload); + } + + auditLogger.info(AaiUiMsgs.DR_PROCESSING_TIME, String.valueOf(drOpTime)); + + exchange.getOut().setHeader("X-TransactionId", xTransactionId); + exchange.getOut().setHeader("X-FromAppId", xFromAppId); + exchange.getOut().setHeader("RequestUrl", request.getRequestURI()); + exchange.getOut().setHeader("RequestPort", request.getLocalPort()); + exchange.getOut().setBody(result); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_PROCESSING_REQUEST, exc); + } + } + + public RestClient getClient() { + return client; + } + + public void setClient(RestClient client) { + this.client = client; + } + + public DataRouterConfig getConfig() { + return config; + } + + public void setConfig(DataRouterConfig config) { + this.config = config; + } + + protected OperationResult getOperationResult() { + return operationResult; + } +} diff --git a/src/main/java/org/onap/aai/sparky/dal/rest/RestClientBuilder.java b/src/main/java/org/onap/aai/sparky/dal/rest/RestClientBuilder.java index 77f04e0..5977a03 100644 --- a/src/main/java/org/onap/aai/sparky/dal/rest/RestClientBuilder.java +++ b/src/main/java/org/onap/aai/sparky/dal/rest/RestClientBuilder.java @@ -22,11 +22,6 @@ */ package org.onap.aai.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; @@ -34,6 +29,11 @@ import javax.net.ssl.SSLSession; import org.onap.aai.sparky.security.SecurityContextFactory; import org.onap.aai.sparky.security.SecurityContextFactoryImpl; +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; + /** * 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 diff --git a/src/main/java/org/onap/aai/sparky/dal/rest/RestfulDataAccessor.java b/src/main/java/org/onap/aai/sparky/dal/rest/RestfulDataAccessor.java index c229de1..9f07aff 100644 --- a/src/main/java/org/onap/aai/sparky/dal/rest/RestfulDataAccessor.java +++ b/src/main/java/org/onap/aai/sparky/dal/rest/RestfulDataAccessor.java @@ -24,11 +24,9 @@ package org.onap.aai.sparky.dal.rest; import java.security.SecureRandom; -import org.onap.aai.sparky.dal.cache.EntityCache; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.util.NodeUtils; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.logging.AaiUiMsgs; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; @@ -37,6 +35,9 @@ import com.sun.jersey.api.client.WebResource.Builder; /** * The Class RestfulDataAccessor. + * + * TODO: DELETE ME + * */ public class RestfulDataAccessor implements RestDataProvider { @@ -44,8 +45,6 @@ public class RestfulDataAccessor implements RestDataProvider { protected RestClientBuilder clientBuilder; - protected EntityCache entityCache; - private boolean cacheEnabled; private static final Logger LOG = LoggerFactory.getInstance().getLogger(RestfulDataAccessor.class); @@ -66,37 +65,6 @@ public class RestfulDataAccessor implements RestDataProvider { 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); - } } /** @@ -119,27 +87,12 @@ public class RestfulDataAccessor implements RestDataProvider { } - /** - * 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.onap.aai.sparky.dal.rest.RestDataProvider#doRestfulOperation(org.onap.aai.sparky.dal.rest. - * HttpMethod, java.lang.String, java.lang.String, java.lang.String, java.lang.String) + * 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, @@ -151,31 +104,11 @@ public class RestfulDataAccessor implements RestDataProvider { 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 operationResult = new OperationResult(); operationResult.setRequestLink(url); try { @@ -245,8 +178,6 @@ public class RestfulDataAccessor implements RestDataProvider { String.valueOf(operationResult.getResultCode())); } - cacheResult(operationResult); - return operationResult; } @@ -262,7 +193,7 @@ public class RestfulDataAccessor implements RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#doGet(java.lang.String, java.lang.String) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doGet(java.lang.String, java.lang.String) */ @Override public OperationResult doGet(String url, String acceptContentType) { @@ -272,7 +203,8 @@ public class RestfulDataAccessor implements RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#doDelete(java.lang.String, java.lang.String) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doDelete(java.lang.String, + * java.lang.String) */ @Override public OperationResult doDelete(String url, String acceptContentType) { @@ -282,7 +214,7 @@ public class RestfulDataAccessor implements RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#doPost(java.lang.String, java.lang.String, + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPost(java.lang.String, java.lang.String, * java.lang.String) */ @Override @@ -294,7 +226,7 @@ public class RestfulDataAccessor implements RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#doPut(java.lang.String, java.lang.String, + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPut(java.lang.String, java.lang.String, * java.lang.String) */ @Override @@ -306,7 +238,7 @@ public class RestfulDataAccessor implements RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#doPatch(java.lang.String, java.lang.String, + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doPatch(java.lang.String, java.lang.String, * java.lang.String) */ @Override @@ -318,7 +250,7 @@ public class RestfulDataAccessor implements RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#doHead(java.lang.String, java.lang.String) + * @see org.openecomp.sparky.dal.rest.RestDataProvider#doHead(java.lang.String, java.lang.String) */ @Override public OperationResult doHead(String url, String acceptContentType) { @@ -350,27 +282,20 @@ public class RestfulDataAccessor implements RestDataProvider { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#shutdown() + * @see org.openecomp.sparky.dal.rest.RestDataProvider#shutdown() */ @Override public void shutdown() { - if (entityCache != null) { - entityCache.shutdown(); - } - } /* * (non-Javadoc) * - * @see org.onap.aai.sparky.dal.rest.RestDataProvider#clearCache() + * @see org.openecomp.sparky.dal.rest.RestDataProvider#clearCache() */ @Override public void clearCache() { - if (cacheEnabled) { - entityCache.clear(); - } } diff --git a/src/main/java/org/onap/aai/sparky/dal/sas/config/SearchServiceConfig.java b/src/main/java/org/onap/aai/sparky/dal/sas/config/SearchServiceConfig.java index 0925d71..cb6f933 100644 --- a/src/main/java/org/onap/aai/sparky/dal/sas/config/SearchServiceConfig.java +++ b/src/main/java/org/onap/aai/sparky/dal/sas/config/SearchServiceConfig.java @@ -70,6 +70,9 @@ public class SearchServiceConfig { 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 { @@ -108,6 +111,8 @@ public class SearchServiceConfig { 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"); diff --git a/src/main/java/org/onap/aai/sparky/dal/servlet/ResettableStreamHttpServletRequest.java b/src/main/java/org/onap/aai/sparky/dal/servlet/ResettableStreamHttpServletRequest.java deleted file mode 100644 index 4713222..0000000 --- a/src/main/java/org/onap/aai/sparky/dal/servlet/ResettableStreamHttpServletRequest.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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/onap/aai/sparky/dataintegrity/config/DiUiConstants.java b/src/main/java/org/onap/aai/sparky/dataintegrity/config/DiUiConstants.java new file mode 100644 index 0000000..c449931 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/dataintegrity/config/DiUiConstants.java @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.dataintegrity.config; + +/** + * The Class DiUiConstants. + */ +public class DiUiConstants { + + public static final String APP_JSON = "application/json"; + public static final String CATEGORY = "category"; + + public static final String ENTITY_TYPE = "entityType"; + public static final String KEY_AGG = "aggregations"; + public static final String KEY_AGG_RESULT = "aggregationResult"; + public static final String KEY_AGG_RESULT_COUNT = "count"; + public static final String KEY_AGG_RESULT_ID = "key_as_string"; + public static final String KEY_BUCKETS = "buckets"; + public static final String KEY_ROUTE = "route"; + public static final String KEY_FILTERS = "filters"; + public static final String KEY_FILTER_VALUE = "filterValue"; + public static final String KEY_FILTER_ID = "filterId"; + public static final String KEY_START_DATE = "startDate"; + public static final String KEY_END_DATE = "endDate"; + public static final String KEY_TIME_ZONE = "time_zone"; + public static final String DEFAULT_TIME_ZONE = "+00:00"; + + public static final String WIDGET_TYPE_SEVERITY = "severity"; + public static final String WIDGET_TYPE_CATEGORY = "category"; + public static final String WIDGET_TYPE_ENTITY_TYPE = "entityType"; + public static final String WIDGET_TYPE_PAGINATED_TABLE = "pagination"; + public static final String WIDGET_TYPE_DATE_HISTOGRAM = "dateHistogram"; + + + public static final String KEY_BY_ITEM = "by_item"; + public static final String KEY_ENTITY_ID = "entityId"; + public static final String KEY_HITS = "hits"; + public static final String KEY_SEARCH_RESULT = "searchResult"; + public static final String KEY_INNER_HITS = "inner_hits"; + public static final String KEY_ITEM = "item"; + public static final String KEY_ITEM_AGG = "item_aggregation"; + public static final String KEY_TIMESTAMP = "violationTimestamp"; + public static final String KEY_TOTAL_HITS = "totalHits"; + public static final String KEY_VIOLATION_DETAILS = "violationDetails"; + public static final String SEARCH_API = "query"; + + public static final String SEVERITY = "severity"; + public static final String UI_KEY_BY_CATEGORY = "group_by_status"; + public static final String UI_KEY_BY_DATE = "group_by_date"; + public static final String UI_KEY_BY_ENTITY_TYPE = "group_by_entityType"; + public static final String UI_KEY_BY_SEVERITY = "group_by_severity"; + + public static final String UI_KEY_ORDER_BY_DATE = "order_by_date"; + public static final String VIOLATIONS = "violations"; + public static final String KEY_VIEW_NAME = "Data Integrity"; + +} diff --git a/src/main/java/org/onap/aai/sparky/editattributes/AttributeEditProcessor.java b/src/main/java/org/onap/aai/sparky/editattributes/AttributeEditProcessor.java new file mode 100644 index 0000000..42b439e --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/editattributes/AttributeEditProcessor.java @@ -0,0 +1,182 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.editattributes; + +import java.io.UnsupportedEncodingException; +import java.util.Map; + +import org.apache.camel.Exchange; +import org.apache.camel.component.restlet.RestletConstants; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.editattributes.entity.EditRequest; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.util.NodeUtils; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ClientInfo; +import org.restlet.data.Cookie; +import org.restlet.data.MediaType; +import org.restlet.data.Status; +import org.restlet.util.Series; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The Class AttributeEditProcessor. + */ +public class AttributeEditProcessor { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(AttributeEditProcessor.class); + + private ObjectMapper mapper; + private AttributeUpdater attrUpdater; + + public AttributeEditProcessor(AttributeUpdater attributeUpdater) { + this.attrUpdater = attributeUpdater; + + this.mapper = new ObjectMapper(); + mapper.setSerializationInclusion(Include.NON_EMPTY); + } + + public void editAttribute(Exchange exchange) { + + Object xTransactionId = exchange.getIn().getHeader("X-TransactionId"); + + if (xTransactionId == null) { + xTransactionId = NodeUtils.getRandomTxnId(); + } + + Object partnerName = exchange.getIn().getHeader("X-FromAppId"); + if (partnerName == null) { + partnerName = "Browser"; + } + + Request request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class); + + /* + * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log + * entry which includes client (e.g. browser) information + */ + request.setLoggable(false); + + ClientInfo clientInfo = request.getClientInfo(); + MdcContext.initialize((String) xTransactionId, "AAI-UI", "", (String) partnerName, + clientInfo.getAddress() + ":" + clientInfo.getPort()); + + String payload = exchange.getIn().getBody(String.class); + EditRequest editRequest = null; + OperationResult operationResult = new OperationResult(); + + Response response = + exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class); + response.setStatus(Status.SUCCESS_OK); // 200 is assumed unless an actual exception occurs (a + // failure is still a valid response) + + boolean wasErrorDuringProcessing = false; + String errorMessage = null; + + + try { + + if (payload != null && !payload.isEmpty()) { + editRequest = mapper.readValue(payload, EditRequest.class); + + if (editRequest != null) { + + String attUid = getAttUid(request.getCookies()); + String objectUri = editRequest.getEntityUri(); + Map attributeValues = editRequest.getAttributes(); + + if (attUid != null && !attUid.isEmpty() && objectUri != null && !objectUri.isEmpty() + && attributeValues != null && !attributeValues.isEmpty()) { + + LOG.info(AaiUiMsgs.ATTRIBUTES_HANDLING_EDIT, objectUri, editRequest.toString()); + + operationResult = attrUpdater.updateObjectAttribute(objectUri, attributeValues, attUid); + + boolean wasSuccess = (operationResult.getResultCode() == 200); + String message = String.format("Edit Attributes completed with Result Code : %s (%s).", + operationResult.getResultCode(), wasSuccess ? "success" : "failed"); + + LOG.info(AaiUiMsgs.INFO_GENERIC, message); + } + } + } else { + wasErrorDuringProcessing = true; + errorMessage = "Empty payload provided, need details to complete request"; + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ATTRIBUTES_NOT_UPDATED_EXCEPTION, exc.getLocalizedMessage()); + operationResult.setResult(500, "Error encountered while trying to update attributes."); + response.setStatus(Status.SERVER_ERROR_INTERNAL); + } + + if (wasErrorDuringProcessing) { + LOG.error(AaiUiMsgs.ATTRIBUTES_NOT_UPDATED_MESSAGE, errorMessage); + } + + response.setEntity(operationResult.getResult(), MediaType.APPLICATION_JSON); + exchange.getOut().setBody(response); + } + + /** + * Gets the att uid. + * + * @param request the request + * @return the att uid + * @throws UnsupportedEncodingException the unsupported encoding exception + */ + public String getAttUid(Series cookies) throws UnsupportedEncodingException { + String attId = ""; + if (cookies == null) { + LOG.error(AaiUiMsgs.COOKIE_NOT_FOUND); + return attId; + } + for (Cookie cookie : cookies) { + if (cookie.getName().equals("attESHr")) { + // This cookie is of the form : + // "FIRSTNAME|LASTNAME|emailname@domain.com|||ab1234||fl6789,RBFMSKQ," + // + "Z9V2298,9762186|YNNNNNNNNNNNNNYNNYYNNNNN|FIRSTNAME|EY6SC9000|" + // we are to extract fl6789 from this which would be the attuid for the user. + String value = cookie.getValue(); + value = java.net.URLDecoder.decode(value, "UTF-8"); + LOG.info(AaiUiMsgs.COOKIE_FOUND, value); + String[] values = value.split("\\|"); + if (values.length > 7) { + attId = (values[7].split(","))[0]; + + String initials = (values[0].substring(0, 1) + values[1].substring(0, 1)).toLowerCase(); + if (attId.startsWith(initials)) { + return attId; + } + } + } + } + return attId; + } +} diff --git a/src/main/java/org/onap/aai/sparky/editattributes/AttributeUpdater.java b/src/main/java/org/onap/aai/sparky/editattributes/AttributeUpdater.java new file mode 100644 index 0000000..5e6d652 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/editattributes/AttributeUpdater.java @@ -0,0 +1,366 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.editattributes; + +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.persistence.dynamic.DynamicType; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; +import org.onap.aai.sparky.config.oxm.OxmModelLoader; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; +import org.onap.aai.sparky.editattributes.exception.AttributeUpdateException; +import org.onap.aai.sparky.logging.AaiUiMsgs; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; + +/** + * Class to process attribute updates on AAI objects. + * + * + */ +public class AttributeUpdater { + + /** + * The Class AaiEditObject. + */ + public class AaiEditObject { + String objectType; + String rootElement; + String keyName; + String keyValue; + String schemaVersion; + + /** + * Instantiates a new aai edit object. + */ + public AaiEditObject() { + + } + + /** + * Instantiates a new aai edit object. + * + * @param objectType the object type + * @param idName the id name + * @param schemaVersion the schema version + */ + public AaiEditObject(String objectType, String idName, String schemaVersion) { + super(); + this.objectType = objectType; + this.keyName = idName; + this.schemaVersion = schemaVersion; + } + + public String getObjectType() { + return objectType; + } + + public void setObjectType(String objectType) { + this.objectType = objectType; + } + + public String getKeyName() { + return keyName; + } + + public void setKeyName(String idName) { + this.keyName = idName; + } + + public String getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(String schemaVersion) { + this.schemaVersion = schemaVersion; + } + + public void setKeyValue(String keyValue) { + this.keyValue = keyValue; + } + + public String getKeyValue() { + return keyValue; + } + + public String getRootElement() { + return rootElement; + } + + public void setRootElement(String rootElement) { + this.rootElement = rootElement; + } + + } + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(AttributeUpdater.class); + private static final String MESSAGE_VERSION_EXTRACTION_REGEX = "\\/(v[0-9]+)"; + private static final String ATTRIBUTES_UPDATED_SUCCESSFULLY = "Attributes updated successfully"; + private static final String ATTRIBUTES_NOT_UPDATED = "Attributes not updated. "; + private ActiveInventoryConfig aaiConfig; + private ActiveInventoryAdapter aaiAdapter; + private UserValidator validator; + private OxmModelLoader oxmModelLoader; + private OxmEntityLookup oxmEntityLookup; + + /** + * Instantiates a new attribute updater. + * + * @throws AttributeUpdateException + */ + public AttributeUpdater(OxmModelLoader oxmModelLoader, OxmEntityLookup oxmEntityLookup, + ActiveInventoryAdapter activeInventoryAdapter) throws AttributeUpdateException { + super(); + this.oxmModelLoader = oxmModelLoader; + this.oxmEntityLookup = oxmEntityLookup; + this.aaiAdapter = activeInventoryAdapter; + + try { + this.aaiConfig = ActiveInventoryConfig.getConfig(); // TODO -> Config to become a bean + this.validator = new UserValidator(); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ATTRIBUTES_ERROR_GETTING_AAI_CONFIG_OR_ADAPTER, + exc.getLocalizedMessage()); + throw new AttributeUpdateException(exc); + } + } + + protected String getResourceBasePath() { + + String versionStr = null; + if (oxmModelLoader != null) { + versionStr = String.valueOf(oxmModelLoader.getLatestVersionNum()); + } + + return "/aai/v" + versionStr; + + } + + protected URI getBaseUri() { + return UriBuilder.fromUri("https://" + aaiConfig.getAaiRestConfig().getHost() + ":" + + aaiConfig.getAaiRestConfig().getPort() + getResourceBasePath()).build(); + } + + /** + * Update object attribute. + * + * @param objectUri - Valid URI of the object as per OXM model. + * @param attributeValues - Map of (attribute-name & attribute-value) for any attributes to be + * updated to the value. + * @param attUid - ATTUID of the user requesting the update. + * @return - OperationResult with success or failure reason. + */ + public OperationResult updateObjectAttribute(String objectUri, + Map attributeValues, String attUid) { + OperationResult result = new OperationResult(); + LOG.info(AaiUiMsgs.ATTRIBUTES_UPDATE_METHOD_CALLED, objectUri, attUid, + String.valueOf(attributeValues)); + if (!validator.isAuthorizedUser(attUid)) { + result.setResultCode(403); + result.setResult(String.format("User %s is not authorized for Attributes update ", attUid)); + LOG.error(AaiUiMsgs.ATTRIBUTES_USER_NOT_AUTHORIZED_TO_UPDATE, attUid); + return result; + } + + AaiEditObject object = null; + + try { + object = getEditObjectFromUri(objectUri); + } catch (AttributeUpdateException exc) { + result.setResultCode(400); + result.setResult(ATTRIBUTES_NOT_UPDATED); + LOG.error(AaiUiMsgs.ATTRIBUTES_NOT_UPDATED_EXCEPTION, exc.getLocalizedMessage()); + return result; + } + try { + String jsonPayload = convertEditRequestToJson(object, attributeValues); + String patchUri = getBaseUri().toString() + getRelativeUri(objectUri); + + + /* + * FIX ME: Dave Adams, 8-Nov-2017 + */ + + // result = aaiAdapter.doPatch(patchUri, jsonPayload, MediaType.APPLICATION_JSON); + + result = new OperationResult(); + result.setResultCode(404); + + if (result.getResultCode() == 200) { + result.setResult(ATTRIBUTES_UPDATED_SUCCESSFULLY); + String message = result.getResult() + " for " + objectUri; + LOG.info(AaiUiMsgs.INFO_GENERIC, message); + } else { + String message = + ATTRIBUTES_NOT_UPDATED + " For: " + objectUri + ". AAI PATCH Status Code : " + + result.getResultCode() + ". Error : " + result.getResult(); + LOG.error(AaiUiMsgs.ATTRIBUTES_NOT_UPDATED_MESSAGE, message); + } + } catch (AttributeUpdateException exc) { + result.setResultCode(500); + result.setResult(ATTRIBUTES_NOT_UPDATED + exc.getLocalizedMessage()); + LOG.error(AaiUiMsgs.ATTRIBUTES_NOT_UPDATED_EXCEPTION, exc.getLocalizedMessage()); + } + return result; + + } + + /** + * Gets the relative uri. + * + * @param objectUri the object uri + * @return the relative uri + */ + public String getRelativeUri(String objectUri) { + String tempUri = objectUri; + final Pattern pattern = Pattern.compile(MESSAGE_VERSION_EXTRACTION_REGEX, Pattern.DOTALL); + Matcher matcher = pattern.matcher(objectUri); + while (matcher.find()) { + tempUri = objectUri.substring(matcher.end()); + } + if (!tempUri.startsWith("/")) { + tempUri = "/" + tempUri; + } + return tempUri; + } + + /** + * Gets the edits the object from uri. + * + * @param objectUri the object uri + * @return the edits the object from uri + * @throws AttributeUpdateException the attribute update exception + */ + public AaiEditObject getEditObjectFromUri(String objectUri) throws AttributeUpdateException { + + AaiEditObject object = new AaiEditObject(); + String version = getVersionFromUri(objectUri); + + if (null == version) { + version = "v" + String.valueOf(oxmModelLoader.getLatestVersionNum()); + } + object.setSchemaVersion(version); + + String[] values = objectUri.split("/"); + if (values.length < 2) { + throw new AttributeUpdateException("Invalid or malformed object URI : " + objectUri); + } + String keyValue = values[values.length - 1]; + String rootElement = values[values.length - 2]; + + object.setKeyValue(keyValue); + object.setRootElement(rootElement); + + String objectJavaType = null; + Map entityTypeLookup = oxmEntityLookup.getEntityTypeLookup(); + DynamicType entity = entityTypeLookup.get(rootElement); + if (null != entity) { + objectJavaType = entity.getName(); + String message = + "Descriptor: Alias: " + objectJavaType + " : DefaultRootElement: " + rootElement; + LOG.debug(AaiUiMsgs.DEBUG_GENERIC, message); + } + + + if (objectJavaType == null) { + throw new AttributeUpdateException( + "Object type could not be determined from the URI : " + objectUri); + } + object.setObjectType(objectJavaType); + + // Set key attribute name + final List primaryKeys = entity.getDescriptor().getPrimaryKeyFieldNames(); + + if (primaryKeys.isEmpty()) { + throw new AttributeUpdateException("Object primary key not found in OXM version " + version); + } + + for (int i = 0; i < primaryKeys.size(); i++) { + final String primaryKey = primaryKeys.get(i); + if (primaryKey.indexOf("/text()") != -1) { + primaryKeys.set(i, primaryKey.replace("/text()", "")); + } + } + object.setKeyName(primaryKeys.iterator().next()); + + return object; + } + + /** + * Gets the version from uri. + * + * @param objectUri the object uri + * @return the version from uri + * @throws AttributeUpdateException the attribute update exception + */ + private String getVersionFromUri(String objectUri) throws AttributeUpdateException { + final Pattern pattern = Pattern.compile(MESSAGE_VERSION_EXTRACTION_REGEX, Pattern.DOTALL); + Matcher matcher = pattern.matcher(objectUri); + String messageSchemaVersion = null; + while (matcher.find()) { + messageSchemaVersion = matcher.group(1); + break; + } + return messageSchemaVersion; + } + + /** + * Convert edit request to json. + * + * @param object the object + * @param attributeValues the attribute values + * @return the string + * @throws AttributeUpdateException the attribute update exception + */ + private static String convertEditRequestToJson(AaiEditObject object, + Map attributeValues) throws AttributeUpdateException { + + ObjectMapper mapper = new ObjectMapper(); + mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.KebabCaseStrategy()); + ObjectWriter ow = mapper.writer(); + + Map patchAttributes = new HashMap<>(); + patchAttributes.put(object.getKeyName(), object.getKeyValue()); + patchAttributes.putAll(attributeValues); + + try { + return ow.writeValueAsString(patchAttributes); + } catch (JsonProcessingException exc) { + throw new AttributeUpdateException("Caught a JPE while creating PATCH request body = ", exc); + } + } +} diff --git a/src/main/java/org/onap/aai/sparky/editattributes/UserAuthorizationReader.java b/src/main/java/org/onap/aai/sparky/editattributes/UserAuthorizationReader.java new file mode 100644 index 0000000..65467a2 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/editattributes/UserAuthorizationReader.java @@ -0,0 +1,77 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.editattributes; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Reads user IDs from a file. Each line in the user authorization file should contain a single user + * ID. For example, + * + *
    + * user1
    + * user2
    + * 
    + */ +public class UserAuthorizationReader { + + private File userAuthorizationFile; + + /** + * Set the user authorization file. + * + * @param file a user authorization file + */ + public UserAuthorizationReader(File file) { + this.userAuthorizationFile = file; + } + + /** + * Gets user IDs from a file. + * + * @return a list of user IDs + * @throws IOException if there is a problem reading the user configuration file + */ + public List getUsers() throws IOException { + List userList = new ArrayList<>(); + try (Stream stream = Files.lines(getUserAuthorizationFile().toPath())) { + userList.addAll(stream.map(String::trim).collect(Collectors.toList())); + } + return userList; + } + + // Getters and setters + public File getUserAuthorizationFile() { + return userAuthorizationFile; + } + + public void setUserAuthorizationFile(File file) { + this.userAuthorizationFile = file; + } +} diff --git a/src/main/java/org/onap/aai/sparky/editattributes/UserValidator.java b/src/main/java/org/onap/aai/sparky/editattributes/UserValidator.java new file mode 100644 index 0000000..cccd815 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/editattributes/UserValidator.java @@ -0,0 +1,65 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.editattributes; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; + +/** + * Validates users against a user authorization file. + */ +public class UserValidator { + + private static final Logger LOG = LoggerFactory.getInstance().getLogger(UserValidator.class); + private static final String USER_AUTH_FILE = + TierSupportUiConstants.AUTHORIZED_USERS_FILE_LOCATION; + + private UserAuthorizationReader userAuthorizationReader = + new UserAuthorizationReader(new File(USER_AUTH_FILE)); + + /** + * Returns true if the user is authorized. + * + * @param userId a user identifier + * @return true if the user ID is present in the user authorization file + */ + public boolean isAuthorizedUser(String userId) { + if (userId != null && !userId.isEmpty()) { + try { + List users = userAuthorizationReader.getUsers(); + return users.contains(userId); + } catch (IOException exc) { + LOG.error(AaiUiMsgs.USER_AUTHORIZATION_FILE_UNAVAILABLE, userId); + return false; + } + } else { + return false; + } + } +} diff --git a/src/main/java/org/onap/aai/sparky/search/Suggestion.java b/src/main/java/org/onap/aai/sparky/editattributes/entity/EditRequest.java similarity index 65% rename from src/main/java/org/onap/aai/sparky/search/Suggestion.java rename to src/main/java/org/onap/aai/sparky/editattributes/entity/EditRequest.java index 72530ef..df4c685 100644 --- a/src/main/java/org/onap/aai/sparky/search/Suggestion.java +++ b/src/main/java/org/onap/aai/sparky/editattributes/entity/EditRequest.java @@ -20,38 +20,48 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.search; +package org.onap.aai.sparky.editattributes.entity; -public class Suggestion { +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The Class EditRequest. + */ +public class EditRequest { + + @JsonProperty("entity-uri") + private String entityUri; + + @JsonProperty("entity-type") private String entityType; - private String searchTags; - private SearchEntityProperties properties; - public Suggestion(SearchEntityProperties properties) { - this.properties = properties; - } + @JsonProperty("attributes") + private Map attributes = new HashMap<>(); - public String getEntityType() { - return entityType; + public String getEntityUri() { + return entityUri; } - public String getSearchTags() { - return searchTags; + public void setEntityUri(String entityUri) { + this.entityUri = entityUri; } - public SearchEntityProperties getProperties() { - return properties; + public String getEntityType() { + return entityType; } public void setEntityType(String entityType) { this.entityType = entityType; } - public void setSearchTags(String searchTags) { - this.searchTags = searchTags; + public Map getAttributes() { + return attributes; } - public void setProperties(SearchEntityProperties properties) { - this.properties = properties; + public void setAttributes(Map attributes) { + this.attributes = attributes; } } diff --git a/src/main/java/org/onap/aai/sparky/dal/cache/EntityCache.java b/src/main/java/org/onap/aai/sparky/editattributes/exception/AttributeUpdateException.java similarity index 61% rename from src/main/java/org/onap/aai/sparky/dal/cache/EntityCache.java rename to src/main/java/org/onap/aai/sparky/editattributes/exception/AttributeUpdateException.java index 04baf7c..4612785 100644 --- a/src/main/java/org/onap/aai/sparky/dal/cache/EntityCache.java +++ b/src/main/java/org/onap/aai/sparky/editattributes/exception/AttributeUpdateException.java @@ -20,41 +20,41 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.dal.cache; - -import org.onap.aai.sparky.dal.rest.OperationResult; +package org.onap.aai.sparky.editattributes.exception; /** - * The Interface EntityCache. - * - * @author davea. + * The Class AttributeUpdateException. */ -public interface EntityCache { +public class AttributeUpdateException extends Exception { - /** - * Gets the. - * - * @param entityKey the entity key - * @param link the link - * @return the operation result - */ - public OperationResult get(String entityKey, String link); + private static final long serialVersionUID = 1L; /** - * Put. + * Attribute Edit specific Exception Class. * - * @param entityKey the entity key - * @param result the result + * @param exc the exc */ - public void put(String entityKey, OperationResult result); + + public AttributeUpdateException(Exception exc) { + super(exc); + } /** - * Shutdown. + * Instantiates a new attribute update exception. + * + * @param message the message */ - public void shutdown(); + public AttributeUpdateException(String message) { + super(message); + } /** - * Clear. + * Instantiates a new attribute update exception. + * + * @param message the message + * @param exc the exc */ - public void clear(); + public AttributeUpdateException(String message, Exception exc) { + super(message, exc); + } } diff --git a/src/main/java/org/onap/aai/sparky/inventory/EntityHistoryQueryBuilder.java b/src/main/java/org/onap/aai/sparky/inventory/EntityHistoryQueryBuilder.java new file mode 100644 index 0000000..b765dc8 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/inventory/EntityHistoryQueryBuilder.java @@ -0,0 +1,143 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.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/onap/aai/sparky/inventory/GeoVisualizationProcessor.java b/src/main/java/org/onap/aai/sparky/inventory/GeoVisualizationProcessor.java new file mode 100644 index 0000000..c356191 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/inventory/GeoVisualizationProcessor.java @@ -0,0 +1,202 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.inventory; + +import java.io.IOException; + +import org.apache.camel.Exchange; +import org.apache.camel.component.restlet.RestletConstants; +import org.json.JSONArray; +import org.json.JSONObject; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; +import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.logging.util.ServletUtils; +import org.onap.aai.sparky.util.NodeUtils; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ClientInfo; +import org.restlet.data.Form; +import org.restlet.data.MediaType; +import org.restlet.data.Parameter; +import org.restlet.data.Status; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The Class GeoVisualizationServlet. + */ +public class GeoVisualizationProcessor { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(GeoVisualizationProcessor.class); + + private ObjectMapper mapper; + private SearchAdapter search = null; + private ElasticSearchConfig elasticConfig = null; + + private static final String SEARCH_STRING = "_search"; + private static final String SEARCH_PARAMETER = + "?filter_path=hits.hits._source&_source=location&size=5000&q=entityType:"; + private static final String PARAMETER_KEY = "entity"; + + /** + * Instantiates a new geo visualization processor + */ + public GeoVisualizationProcessor() { + this.mapper = new ObjectMapper(); + + try { + if (elasticConfig == null) { + elasticConfig = ElasticSearchConfig.getConfig(); + } + if (search == null) { + search = new SearchAdapter(); + } + this.mapper = new ObjectMapper(); + } catch (Exception exc) { + + } + } + + public void setSearch(SearchAdapter search) { + this.search = search; + } + + public void setElasticConfig(ElasticSearchConfig elasticConfig) { + this.elasticConfig = elasticConfig; + } + + /** + * 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(Exchange exchange) throws Exception { + OperationResult operationResult = new OperationResult(); + + + Object xTransactionId = exchange.getIn().getHeader("X-TransactionId"); + if (xTransactionId == null) { + xTransactionId = NodeUtils.getRandomTxnId(); + } + + Object partnerName = exchange.getIn().getHeader("X-FromAppId"); + if (partnerName == null) { + partnerName = "Browser"; + } + + Request request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class); + + /* + * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log + * entry which includes client (e.g. browser) information + */ + request.setLoggable(false); + + ClientInfo clientInfo = request.getClientInfo(); + MdcContext.initialize((String) xTransactionId, "AAI-UI", "", (String) partnerName, + clientInfo.getAddress() + ":" + clientInfo.getPort()); + + String entityType = ""; + + Form form = request.getResourceRef().getQueryAsForm(); + for (Parameter parameter : form) { + if (PARAMETER_KEY.equals(parameter.getName())) { + entityType = parameter.getName(); + } + } + + 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 = search.doGet(fullUrlStr, "application/json"); + + JSONObject finalOutputJson = formatOutput(opResult.getResult()); + + Response response = + exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class); + response.setStatus(Status.SUCCESS_OK); + response.setEntity(String.valueOf(finalOutputJson), MediaType.APPLICATION_JSON); + exchange.getOut().setBody(response); + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, "Error processing Geo Visualization request"); + } + + 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/onap/aai/sparky/inventory/entity/GeoIndexDocument.java b/src/main/java/org/onap/aai/sparky/inventory/entity/GeoIndexDocument.java new file mode 100644 index 0000000..3596c54 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/inventory/entity/GeoIndexDocument.java @@ -0,0 +1,292 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.inventory.entity; + +import java.io.Serializable; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; +import org.onap.aai.sparky.sync.entity.IndexDocument; +import org.onap.aai.sparky.util.NodeUtils; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The Class GeoIndexDocument. + */ +public class GeoIndexDocument implements Serializable, IndexDocument { + + @JsonIgnore + private static final long serialVersionUID = -5188479658230319058L; + + protected String entityType; + protected String entityPrimaryKeyValue; + protected String entityPrimaryKeyName; + protected String latitude; + protected String longitude; + protected String selfLink; + + @JsonIgnore + protected OxmEntityLookup oxmEntityLookup; + + @JsonIgnore + protected ObjectMapper mapper = new ObjectMapper(); + // generated, SHA-256 digest + @JsonIgnore + 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(); + } + + + @JsonIgnore + 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 + @JsonIgnore + public String getAsJson() throws JsonProcessingException { + + if (latitude != null && longitude != null) { + + /** + * A valid entry from this class is one that has both lat and long. If one or both is missing + * we shouldn't be indexing anything. + */ + + return NodeUtils.convertObjectToJson(this, true); + + } + + return null; + + } + + /* + * (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 = oxmEntityLookup.getEntityDescriptors().get(entityType); + String entityPrimaryKeyName = + NodeUtils.concatArray(descriptor.getPrimaryKeyAttributeNames(), "/"); + + 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 + @JsonIgnore + public String getId() { + return this.id; + } + + @JsonProperty("entityType") + public String getEntityType() { + return entityType; + } + + public void setEntityType(String entityType) { + this.entityType = entityType; + } + + @JsonProperty("entityPrimaryKeyValue") + public String getEntityPrimaryKeyValue() { + return entityPrimaryKeyValue; + } + + public void setEntityPrimaryKeyValue(String entityPrimaryKeyValue) { + this.entityPrimaryKeyValue = entityPrimaryKeyValue; + } + + @JsonProperty("entityPrimaryKeyName") + public String getEntityPrimaryKeyName() { + return entityPrimaryKeyName; + } + + public void setEntityPrimaryKeyName(String entityPrimaryKeyName) { + this.entityPrimaryKeyName = entityPrimaryKeyName; + } + + @JsonProperty("lat") + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } + + @JsonProperty("long") + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + @JsonProperty("link") + public String getSelfLink() { + return selfLink; + } + + public void setSelfLink(String selfLink) { + this.selfLink = selfLink; + } + + @JsonIgnore + public static long getSerialversionuid() { + return serialVersionUID; + } + + public void setId(String id) { + this.id = id; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/inventory/entity/TopographicalEntity.java b/src/main/java/org/onap/aai/sparky/inventory/entity/TopographicalEntity.java new file mode 100644 index 0000000..7736255 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/inventory/entity/TopographicalEntity.java @@ -0,0 +1,220 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.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/onap/aai/sparky/logging/AaiUiMsgs.java b/src/main/java/org/onap/aai/sparky/logging/AaiUiMsgs.java index c89f83c..5854bc7 100644 --- a/src/main/java/org/onap/aai/sparky/logging/AaiUiMsgs.java +++ b/src/main/java/org/onap/aai/sparky/logging/AaiUiMsgs.java @@ -22,10 +22,10 @@ */ package org.onap.aai.sparky.logging; -import com.att.eelf.i18n.EELFResourceManager; - import org.onap.aai.cl.eelf.LogMessageEnum; +import com.att.eelf.i18n.EELFResourceManager; + /** * The Enum AaiUiMsgs. */ @@ -52,6 +52,14 @@ public enum AaiUiMsgs implements LogMessageEnum { UNEXPECTED_TOKEN_COUNT, /** Arguments: {0} = Error/exception message. */ ADD_SEARCH_TARGET_ATTRIBUTES_FAILED, + /** Arguments: {0} = Error/exception message. */ + NODE_INTEGRITY_OVERLAY_ERROR, + /** Arguments: {0} = Node ID. */ + NODE_INTEGRITY_ALREADY_PROCESSED, + /** Arguments: {0} = Node ID. */ + SKIPPING_PROCESS_NODE_INTEGRITY, + /** Arguments: {0} = Error/exception message. */ + FAILED_TO_PROCESS_NODE_INTEGRITY, /** No argument */ MAX_EVALUATION_ATTEMPTS_EXCEEDED, /** Arguments: {0} = Error/exception message. */ @@ -244,10 +252,10 @@ public enum AaiUiMsgs implements LogMessageEnum { INTERRUPTED, /** Arguments: {0} = Entity Type {1} Entity */ GEO_SYNC_IGNORING_ENTITY, + /** Arguments: {0} = reason */ + OXM_LOADING_ERROR, /** Arguments: {0} = type */ - OXM_FAILED_RETRIEVAL, - /** Arguments: {0} = Directory. */ - OXM_FILE_NOT_FOUND, + OXM_FAILED_RETRIEVAL, OXM_FILE_NOT_FOUND, /** No argument */ OXM_READ_ERROR_NONVERBOSE, /** Arguments: {0} = OXM File name */ @@ -256,7 +264,7 @@ public enum AaiUiMsgs implements LogMessageEnum { OXM_PARSE_ERROR_NONVERBOSE, /** Arguments: {0} = OXM File name {1} = Exception */ OXM_PARSE_ERROR_VERBOSE, - /** No argument */ + /** Arguments: {0} = Numerical value for loaded OXM version */ OXM_LOAD_SUCCESS, /** Arguments: {0} = Entity {1} = Found property-value */ OXM_PROP_DEF_ERR_CROSS_ENTITY_REF, @@ -302,6 +310,20 @@ public enum AaiUiMsgs implements LogMessageEnum { AAI_RETRIEVAL_FAILED_GENERIC, /** Arguments: {0} = Self Link */ AAI_RETRIEVAL_FAILED_FOR_SELF_LINK, + /** Arguments: {0} = Exception */ + ATTRIBUTES_NOT_UPDATED_EXCEPTION, + /** Arguments: {0} = Message */ + ATTRIBUTES_NOT_UPDATED_MESSAGE, + /** Arguments: {0} = Exception */ + ATTRIBUTES_ERROR_GETTING_AAI_CONFIG_OR_ADAPTER, + /** Arguments: {0} = Schema File URI */ + ATTRIBUTES_ERROR_LOADING_MODEL_VERSION, + /** Arguments: {0} = Request URI {1} = Edit Request Body */ + ATTRIBUTES_HANDLING_EDIT, + /** Arguments: {0} = Object URI {1} = Attribute ID {2} Attribute Values */ + ATTRIBUTES_UPDATE_METHOD_CALLED, + /** Arguments: {0} = Attribute ID */ + ATTRIBUTES_USER_NOT_AUTHORIZED_TO_UPDATE, /** Arguments: {0} = Cookie */ COOKIE_FOUND, /** No argument */ @@ -404,7 +426,7 @@ public enum AaiUiMsgs implements LogMessageEnum { /** 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, + ERROR_INVALID_HASH, ERROR_HASH_NOT_FOUND, ERROR_FILTERS_NOT_FOUND, ERROR_READING_HTTP_REQ_PARAMS, /** Arguments: {0} = Exception */ ERROR_D3_GRAPH_VISUALIZATION, /** Arguments: {0} = Exception */ @@ -417,8 +439,25 @@ public enum AaiUiMsgs implements LogMessageEnum { VIEW_NAME_NOT_SUPPORTED, /** Arguments: {0} = response code, {1} = filter name */ ERROR_FETCHING_FILTER_VALUES, + /** Arguments: {0} = query type, {1} = view name */ + ERROR_PROCESSING_WIDGET_REQUEST, + /** Arguments: {0} = Time in ms */ + DR_PROCESSING_TIME, + /** Arguments: {0} = Response code {1} = payload */ + DR_PROCESSING_FAILURE, + /** Arguments: {0} = request uri */ + DR_REQUEST_URI_FOR_PROXY_UNKNOWN, + /** Arguments: {0} = origin-url {1} = dr-url */ + DR_PROXY_FROM_TO, + /** Arguments: {0} = Exception */ + URI_DECODING_EXCEPTION, + /** Arguments: {0} = Value {1} = Error */ + ENCRYPTION_ERROR, + /** Arguments: {0} = Encrypted value {1} = Error */ + DECRYPTION_ERROR, /** Arguments: {0} = URI */ RESOURCE_NOT_FOUND; + /** * Static initializer to ensure the resource bundles for this class are loaded... */ diff --git a/src/main/java/org/onap/aai/sparky/util/ServletUtils.java b/src/main/java/org/onap/aai/sparky/logging/util/ServletUtils.java similarity index 98% rename from src/main/java/org/onap/aai/sparky/util/ServletUtils.java rename to src/main/java/org/onap/aai/sparky/logging/util/ServletUtils.java index 2a8159e..dd040a2 100644 --- a/src/main/java/org/onap/aai/sparky/util/ServletUtils.java +++ b/src/main/java/org/onap/aai/sparky/logging/util/ServletUtils.java @@ -20,18 +20,18 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.util; +package org.onap.aai.sparky.logging.util; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.http.HttpServletResponse; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.cl.api.Logger; /** * The Class ServletUtils. diff --git a/src/main/java/org/onap/aai/sparky/search/EntityCountHistoryProcessor.java b/src/main/java/org/onap/aai/sparky/search/EntityCountHistoryProcessor.java new file mode 100644 index 0000000..e2eef7a --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/search/EntityCountHistoryProcessor.java @@ -0,0 +1,417 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.search; + +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 org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.apache.camel.component.restlet.RestletConstants; +import org.json.JSONArray; +import org.json.JSONObject; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; +import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.onap.aai.sparky.inventory.EntityHistoryQueryBuilder; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.logging.util.ServletUtils; +import org.onap.aai.sparky.util.NodeUtils; +import org.onap.aai.sparky.util.RestletUtils; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ClientInfo; +import org.restlet.data.MediaType; +import org.restlet.data.Status; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +/** + * Receives and processes Entity Count History requests + */ +public class EntityCountHistoryProcessor implements Processor { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(EntityCountHistoryProcessor.class); + + private static final long serialVersionUID = 1L; + + private SearchAdapter search = null; + private ElasticSearchConfig elasticConfig = null; + private VisualizationConfigs visualConfigs = null; + private ObjectMapper mapper; + + private static final String SEARCH_STRING = "_search"; + private static final String TYPE = "type"; + private static final String TABLE = "table"; + private static final String GRAPH = "graph"; + + private List vnfEntityTypesToSummarize; + private boolean summarizevnf = false; + + private RestletUtils restletUtils = new RestletUtils(); + + /** + * Instantiates a new Entity Count History + */ + + public EntityCountHistoryProcessor(VisualizationConfigs visualizationConfigs) { + + this.visualConfigs = visualizationConfigs; + vnfEntityTypesToSummarize = + Arrays.asList(visualConfigs.getVnfEntityTypes().toLowerCase().split("[\\s,]+")); + summarizevnf = visualConfigs.getEntityTypesToSummarize().toLowerCase().contains("vnf"); + try { + if (elasticConfig == null) { + elasticConfig = ElasticSearchConfig.getConfig(); + } + + if (search == null) { + search = new SearchAdapter(); + } + this.mapper = new ObjectMapper(); + this.mapper.configure(SerializationFeature.INDENT_OUTPUT, true); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.CONFIGURATION_ERROR, exc.getLocalizedMessage()); + } + } + + /** + * Processes a entity count history search request + * + * @param exchange The Exchange object generated by Apache Camel for the incoming request + */ + + @Override + public void process(Exchange exchange) throws Exception { + + Request request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class); + Response restletResponse = + exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class); + + Object xTransactionId = exchange.getIn().getHeader("X-TransactionId"); + if (xTransactionId == null) { + xTransactionId = NodeUtils.getRandomTxnId(); + } + + Object partnerName = exchange.getIn().getHeader("X-FromAppId"); + if (partnerName == null) { + partnerName = "Browser"; + } + + /* + * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log + * entry which includes client (e.g. browser) information + */ + request.setLoggable(false); + + ClientInfo clientInfo = request.getClientInfo(); + MdcContext.initialize((String) xTransactionId, "AAI-UI", "", (String) partnerName, + clientInfo.getAddress() + ":" + clientInfo.getPort()); + + String typeParameter = getTypeParameter(exchange); + + if (null != typeParameter && !typeParameter.isEmpty()) { + OperationResult operationResult = null; + + try { + operationResult = getResults(restletResponse, typeParameter); + restletResponse.setEntity(operationResult.getResult(), MediaType.APPLICATION_JSON); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.CONFIGURATION_ERROR, exc.getLocalizedMessage()); + } + } else { + LOG.error(AaiUiMsgs.RESOURCE_NOT_FOUND, request.getOriginalRef().toString()); + String errorMessage = + restletUtils.generateJsonErrorResponse("Unsupported request. Resource not found."); + restletResponse.setEntity(errorMessage, MediaType.APPLICATION_JSON); + restletResponse.setStatus(Status.CLIENT_ERROR_NOT_FOUND); + } + + exchange.getOut().setBody(restletResponse); + } + + + /** + * Format line graph output + * + * @param results The results + * @return The JSON object + * @throws JsonProcessingException The JSON processing exception + */ + public 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); + } + + /** + * Format table output + * + * @param results The results + * @return The JSON object + * @throws JsonProcessingException The JSON processing exception + */ + public 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; + } + + /** + * Gets the results + * + * @param response The response + * @param type The type + * @return The results + */ + public OperationResult getResults(Response response, String type) { + 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 = + restletUtils.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.setEntity(finalOutput.toString(), MediaType.APPLICATION_JSON); + operationResult.setResult(finalOutput.toString()); + } + } catch (JsonProcessingException exc) { + restletUtils.handleRestletErrors(LOG, "Unable to map JSONpayload", exc, response); + } + + return operationResult; + } + + /** + * Gets the buckets node + * + * @param node The node + * @return The buckets node + * @throws Exception The exception + */ + public 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 = visualConfigs.getEntityTypesToSummarize().split(","); + for (String entity : entityTypes) { + entityMap.put(entity, (long) 0); + } + + return entityMap; + } + + /** + * Extracts the "type" query parameter from the request URI + * + * @param exchange + * @return String containing the value of the "type" query parameter of the request. Returns null + * if no "type" parameter found + */ + public String getTypeParameter(Exchange exchange) { + String typeParameter = null; + + String requestUriParameterString = exchange.getIn().getHeader("CamelHttpQuery", String.class); + + if (null != requestUriParameterString) { + String[] requestParameterParts = requestUriParameterString.split("&"); + + String[] parameter = requestParameterParts[0].split("="); + String currentParameterKey = parameter[0]; + + if (null != currentParameterKey && !currentParameterKey.isEmpty()) { + // Check if we're looking at the "type" parameter key + if (currentParameterKey.equals(TYPE)) { + boolean uriIncludesTypeParameterValue = + (parameter.length >= 2) && !parameter[1].isEmpty(); + + if (uriIncludesTypeParameterValue) { + String typeParameterValue = parameter[1]; + + // Is the parameter value one that we return data for? + if (typeParameterValue.equalsIgnoreCase(TABLE) + || typeParameterValue.equalsIgnoreCase(GRAPH)) { + typeParameter = typeParameterValue; + } + } + } + } + } + + return typeParameter; + } + + public void setElasticConfig(ElasticSearchConfig elasticConfig) { + this.elasticConfig = elasticConfig; + } + + public void setRestletUtils(RestletUtils restletUtils) { + this.restletUtils = restletUtils; + } + + public void setSearch(SearchAdapter search) { + this.search = search; + } +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SearchResponse.java b/src/main/java/org/onap/aai/sparky/search/SearchResponse.java similarity index 78% rename from src/main/java/org/onap/aai/sparky/viewandinspect/entity/SearchResponse.java rename to src/main/java/org/onap/aai/sparky/search/SearchResponse.java index 7daf471..cddce49 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SearchResponse.java +++ b/src/main/java/org/onap/aai/sparky/search/SearchResponse.java @@ -20,12 +20,12 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.viewandinspect.entity; +package org.onap.aai.sparky.search; import java.util.ArrayList; import java.util.List; -import org.onap.aai.sparky.suggestivesearch.SuggestionEntity; +import org.onap.aai.sparky.search.entity.SearchSuggestion; /** * The Class SearchResponse. @@ -35,13 +35,13 @@ public class SearchResponse { private long processingTimeInMs; private int totalFound; - private List suggestions; + private List suggestions; /** * Instantiates a new search response. */ public SearchResponse() { - this.suggestions = new ArrayList(); + this.suggestions = new ArrayList(); this.processingTimeInMs = 0; this.totalFound = 0; } @@ -62,11 +62,11 @@ public class SearchResponse { this.totalFound = totalFound; } - public List getSuggestions() { + public List getSuggestions() { return suggestions; } - public void setSuggestions(List suggestions) { + public void setSuggestions(List suggestions) { this.suggestions = suggestions; } @@ -75,7 +75,7 @@ public class SearchResponse { * * @param suggestionEntry that will be converted to JSON */ - public void addSuggestion(SuggestionEntity suggestionEntity) { + public void addSuggestion(SearchSuggestion suggestionEntity) { suggestions.add(suggestionEntity); } @@ -87,4 +87,13 @@ public class SearchResponse { public void addToTotalFound(int additionalCount) { totalFound += additionalCount; } + + @Override + public String toString() { + return "SearchResponse [processingTimeInMs=" + processingTimeInMs + ", totalFound=" + totalFound + + ", " + (suggestions != null ? "suggestions=" + suggestions : "") + "]"; + } + + + } diff --git a/src/main/java/org/onap/aai/sparky/search/UnifiedSearchProcessor.java b/src/main/java/org/onap/aai/sparky/search/UnifiedSearchProcessor.java new file mode 100644 index 0000000..2983163 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/search/UnifiedSearchProcessor.java @@ -0,0 +1,212 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.search; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import org.apache.camel.Exchange; +import org.apache.camel.component.restlet.RestletConstants; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.search.api.SearchProvider; +import org.onap.aai.sparky.search.entity.QuerySearchEntity; +import org.onap.aai.sparky.search.entity.SearchSuggestion; +import org.onap.aai.sparky.search.registry.SearchProviderRegistry; +import org.onap.aai.sparky.util.NodeUtils; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ClientInfo; +import org.restlet.data.MediaType; +import org.restlet.data.Status; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class UnifiedSearchProcessor { + + protected static final String HASH_ID_KEY = "hashId"; + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(UnifiedSearchProcessor.class); + + protected SearchProviderRegistry searchProviderRegistry; + protected ObjectMapper mapper; + protected boolean useOrderedSearchProviderKeys; + + public UnifiedSearchProcessor() { + mapper = new ObjectMapper(); + this.useOrderedSearchProviderKeys = false; + } + + public boolean isUseOrderedSearchProviderKeys() { + return useOrderedSearchProviderKeys; + } + + public void setUseOrderedSearchProviderKeys(boolean useOrderedSearchProviderKeys) { + this.useOrderedSearchProviderKeys = useOrderedSearchProviderKeys; + } + + public void search(Exchange exchange) { + + Object xTransactionId = exchange.getIn().getHeader("X-TransactionId"); + if (xTransactionId == null) { + xTransactionId = NodeUtils.getRandomTxnId(); + } + + Object partnerName = exchange.getIn().getHeader("X-FromAppId"); + if (partnerName == null) { + partnerName = "Browser"; + } + + Request request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class); + + /* + * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log + * entry which includes client (e.g. browser) information + */ + request.setLoggable(false); + + ClientInfo clientInfo = request.getClientInfo(); + MdcContext.initialize((String) xTransactionId, "AAI-UI", "", (String) partnerName, + clientInfo.getAddress() + ":" + clientInfo.getPort()); + + SearchResponse searchResponse = new SearchResponse(); + long processTime = System.currentTimeMillis(); + int totalAdded = 0; + + try { + String payload = exchange.getIn().getBody(String.class); + + if (payload == null || payload.isEmpty()) { + + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, "Request Payload is empty"); + + /* + * Don't throw back an error, just return an empty set + */ + + } else { + + QuerySearchEntity searchRequest = mapper.readValue(payload, QuerySearchEntity.class); + int maxResultsPerSearch = Integer.valueOf(searchRequest.getMaxResults()); + + Map> searchProviderSuggestions = + new HashMap>(); + + int totalSuggestionsFromProviders = 0; + List suggestions = null; + for (SearchProvider searchProvider : searchProviderRegistry.getSearchProviders()) { + suggestions = searchProvider.search(searchRequest); + totalSuggestionsFromProviders += suggestions.size(); + searchProviderSuggestions.put(searchProvider.getClass().getCanonicalName(), suggestions); + } + + /* + * Using ordered search provider keys allows us to deterministically calculate how many + * results from each provider should be returned. At the moment, this behavior is primarily + * only beneficial to test classes. As there is a cost to sorted-collections in the call + * processing path, this behavior has been made optional. + */ + + if (useOrderedSearchProviderKeys) { + searchProviderSuggestions = + new TreeMap>(searchProviderSuggestions); + } + + if (totalSuggestionsFromProviders > 0) { + + int suggestionIndex = 0; + + Set>> searchProviderResults = + searchProviderSuggestions.entrySet(); + + while (totalAdded < maxResultsPerSearch && (totalAdded < totalSuggestionsFromProviders)) { + + for (Entry> searchProviderResultList : searchProviderResults) { + + if ((suggestionIndex <= (searchProviderResultList.getValue().size() - 1))) { + + if (totalAdded < maxResultsPerSearch) { + searchResponse + .addSuggestion(searchProviderResultList.getValue().get(suggestionIndex)); + totalAdded++; + } + } + + } + + suggestionIndex++; + + } + + } + + } + + searchResponse.addToTotalFound(totalAdded); + String searchResponseJson = NodeUtils.convertObjectToJson(searchResponse, true); + + processTime = System.currentTimeMillis() - processTime; + searchResponse.setProcessingTimeInMs(processTime); + + Response response = + exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class); + response.setStatus(Status.SUCCESS_OK); + response.setEntity(searchResponseJson, MediaType.APPLICATION_JSON); + exchange.getOut().setBody(response); + + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, + "Query search failed with error = " + exc.getMessage()); + exchange.getOut().setBody( + generateJsonErrorResponse("Error while building response. Error = " + exc.getMessage()), + String.class); + } + } + + public SearchProviderRegistry getSearchProviderRegistry() { + return searchProviderRegistry; + } + + public void setSearchProviderRegistry(SearchProviderRegistry searchProviderRegistry) { + this.searchProviderRegistry = searchProviderRegistry; + } + + + /* + * 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); + } + +} diff --git a/src/main/java/org/onap/aai/sparky/search/VnfSearchService.java b/src/main/java/org/onap/aai/sparky/search/VnfSearchService.java deleted file mode 100644 index 654aad0..0000000 --- a/src/main/java/org/onap/aai/sparky/search/VnfSearchService.java +++ /dev/null @@ -1,348 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.dal.elasticsearch.HashQueryResponse; -import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; -import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.suggestivesearch.SuggestionEntity; -import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; -import org.onap.aai.sparky.viewandinspect.entity.QuerySearchEntity; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; - - -/** - * 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; - } -} diff --git a/src/main/java/org/onap/aai/sparky/search/api/SearchProvider.java b/src/main/java/org/onap/aai/sparky/search/api/SearchProvider.java new file mode 100644 index 0000000..e593c3e --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/search/api/SearchProvider.java @@ -0,0 +1,34 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.search.api; + +import java.util.List; + +import org.onap.aai.sparky.search.entity.QuerySearchEntity; +import org.onap.aai.sparky.search.entity.SearchSuggestion; + +public interface SearchProvider { + + List search(QuerySearchEntity queryRequest); + +} diff --git a/src/main/java/org/onap/aai/sparky/search/config/SuggestionConfig.java b/src/main/java/org/onap/aai/sparky/search/config/SuggestionConfig.java index 5ce4d3c..9208354 100644 --- a/src/main/java/org/onap/aai/sparky/search/config/SuggestionConfig.java +++ b/src/main/java/org/onap/aai/sparky/search/config/SuggestionConfig.java @@ -54,7 +54,7 @@ public class SuggestionConfig { private String defaultPairingValue; - private SuggestionConfig() {} + public SuggestionConfig() {} /** * Returns initialized instance as per singleton pattern. @@ -69,6 +69,10 @@ public class SuggestionConfig { return config; } + public static void setConfig(SuggestionConfig config) { + SuggestionConfig.config = config; + } + public void initializeConfigProperties() { Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); diff --git a/src/main/java/org/onap/aai/sparky/search/entity/ExternalSearchRequestEntity.java b/src/main/java/org/onap/aai/sparky/search/entity/ExternalSearchRequestEntity.java new file mode 100644 index 0000000..465eadc --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/search/entity/ExternalSearchRequestEntity.java @@ -0,0 +1,69 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.search.entity; + +public class ExternalSearchRequestEntity { + private String view; + private String entityId; + private String entityType; + + public ExternalSearchRequestEntity() { + this.view = ""; + this.entityId = ""; + this.entityType = ""; + } + + public String getView() { + return view; + } + + public void setView(String view) { + this.view = view; + } + + 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 createQueryString() { + return entityId + " " + entityType; + } + + @Override + public String toString() { + return "ExternalRequestEntitySearchEntity [view=" + view + ", entityId=" + entityId + + ", entityType=" + entityType + "]"; + } +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/QuerySearchEntity.java b/src/main/java/org/onap/aai/sparky/search/entity/QuerySearchEntity.java similarity index 97% rename from src/main/java/org/onap/aai/sparky/viewandinspect/entity/QuerySearchEntity.java rename to src/main/java/org/onap/aai/sparky/search/entity/QuerySearchEntity.java index 222a2f7..d90e329 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/QuerySearchEntity.java +++ b/src/main/java/org/onap/aai/sparky/search/entity/QuerySearchEntity.java @@ -20,7 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.viewandinspect.entity; +package org.onap.aai.sparky.search.entity; import com.fasterxml.jackson.annotation.JsonIgnore; @@ -31,7 +31,6 @@ public class QuerySearchEntity { private static final String DEFAULT_MAX_RESULTS = "10"; public String maxResults; - public String queryStr; /** diff --git a/src/main/java/org/onap/aai/sparky/search/entity/SearchSuggestion.java b/src/main/java/org/onap/aai/sparky/search/entity/SearchSuggestion.java new file mode 100644 index 0000000..823cf5a --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/search/entity/SearchSuggestion.java @@ -0,0 +1,37 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.search.entity; + +public interface SearchSuggestion { + public String getHashId(); + + public void setHashId(String hashId); + + public String getRoute(); + + public void setRoute(String route); + + public String getText(); + + public void setText(String searchText); +} diff --git a/src/main/java/org/onap/aai/sparky/search/filters/FilterElasticSearchAdapter.java b/src/main/java/org/onap/aai/sparky/search/filters/FilterElasticSearchAdapter.java index a846e88..5f5dc74 100644 --- a/src/main/java/org/onap/aai/sparky/search/filters/FilterElasticSearchAdapter.java +++ b/src/main/java/org/onap/aai/sparky/search/filters/FilterElasticSearchAdapter.java @@ -34,6 +34,7 @@ import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.onap.aai.sparky.dataintegrity.config.DiUiConstants; import org.onap.aai.sparky.logging.AaiUiMsgs; import org.onap.aai.sparky.search.filters.config.UiFilterDataSourceConfig; import org.onap.aai.sparky.search.filters.entity.UiFilterEntity; @@ -42,13 +43,15 @@ import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; /** * Performs all Elasticsearch related queries for filters related to the Sparky-FE. + * + * @author RICHARV */ public class FilterElasticSearchAdapter { private static ElasticSearchConfig esConfig = null; private static SearchAdapter search = null; private static final String ES_SEARCH_API = TierSupportUiConstants.ES_SEARCH_API; - private static final String APP_JSON = "application/json"; + private static final String APP_JSON = DiUiConstants.APP_JSON; private static final Logger LOG = LoggerFactory.getInstance().getLogger(FilterElasticSearchAdapter.class); private static final String AGGS = "aggregations"; @@ -104,7 +107,7 @@ public class FilterElasticSearchAdapter { FilterQueryBuilder.createFilterValueQueryObject(dataSourceConfig.getFieldName()); } - org.onap.aai.sparky.dal.rest.OperationResult opResult = + OperationResult opResult = search.doPost(getFullUrl(dataSourceConfig.getIndexName(), ES_SEARCH_API), filterValueQuery.toString(), APP_JSON); diff --git a/src/main/java/org/onap/aai/sparky/search/filters/FilterProcessor.java b/src/main/java/org/onap/aai/sparky/search/filters/FilterProcessor.java index fdcf6b2..b22db96 100644 --- a/src/main/java/org/onap/aai/sparky/search/filters/FilterProcessor.java +++ b/src/main/java/org/onap/aai/sparky/search/filters/FilterProcessor.java @@ -32,7 +32,6 @@ import org.apache.camel.component.restlet.RestletConstants; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.search.filters.FilteredSearchHelper; import org.onap.aai.sparky.search.filters.entity.UiFilterEntity; import org.onap.aai.sparky.search.filters.entity.UiFiltersEntity; import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; diff --git a/src/main/java/org/onap/aai/sparky/search/filters/config/FiltersConfig.java b/src/main/java/org/onap/aai/sparky/search/filters/config/FiltersConfig.java index 3853913..b202684 100644 --- a/src/main/java/org/onap/aai/sparky/search/filters/config/FiltersConfig.java +++ b/src/main/java/org/onap/aai/sparky/search/filters/config/FiltersConfig.java @@ -27,7 +27,7 @@ import java.io.File; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/main/java/org/onap/aai/sparky/search/registry/SearchProviderRegistry.java b/src/main/java/org/onap/aai/sparky/search/registry/SearchProviderRegistry.java new file mode 100644 index 0000000..d3cca45 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/search/registry/SearchProviderRegistry.java @@ -0,0 +1,74 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.search.registry; + +import java.util.ArrayList; +import java.util.List; + +import org.onap.aai.sparky.search.api.SearchProvider; + +/** + * Make this a java-scoped singleton to resolve the contextual issue spanning a Spring Context and + * accessing the SPR in other parts of the code that are not directly instantiated by a Spring Bean. + * Eventually the SPR doesn’t have to be a real singleton, it could simply be a Spring bean scoped + * as a singleton and then wired in via dependency injection to the classes that need it. But I’m + * not there yet. This will get a demonstrable extension mechanism in place quickly at practically + * no cost, beyond what’s already in the email plus some testing. + */ + +public class SearchProviderRegistry { + + private List searchProviders; + + public SearchProviderRegistry() { + searchProviders = new ArrayList(); + } + + public List getSearchProviders() { + return searchProviders; + } + + public final void addSearchProvider(SearchProvider searchProvider) { + + if (searchProvider == null) { + return; + } + + if (!searchProviders.contains(searchProvider)) { + searchProviders.add(searchProvider); + } + } + + public final void addSearchProviders(List searchProviders) { + + if (searchProviders == null) { + return; + } + + for (SearchProvider searchProvider : searchProviders) { + addSearchProvider(searchProvider); + } + + } + +} diff --git a/src/main/java/org/onap/aai/sparky/security/EcompSso.java b/src/main/java/org/onap/aai/sparky/security/EcompSso.java index 16e01c0..de74a5a 100644 --- a/src/main/java/org/onap/aai/sparky/security/EcompSso.java +++ b/src/main/java/org/onap/aai/sparky/security/EcompSso.java @@ -25,13 +25,12 @@ package org.onap.aai.sparky.security; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.security.portal.config.PortalAuthenticationConfig; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; -import org.openecomp.portalsdk.core.onboarding.util.PortalApiProperties; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.security.portal.config.PortalAuthenticationConfig; import org.openecomp.portalsdk.core.onboarding.util.CipherUtil; - +import org.openecomp.portalsdk.core.onboarding.util.PortalApiProperties; /** * Provides authentication services for onboarded ECOMP applications. @@ -80,7 +79,7 @@ public class EcompSso { * then searches for a CSP cookie; if not found, for a WebJunction header. * * @param request - * @return User ID if the ECOMP cookie is present and the sign-on process established an User ID; + * @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) { @@ -100,23 +99,23 @@ public class EcompSso { } /** - * Searches the specified request for the CSP cookie, decodes it and gets the User ID. + * Searches the specified request for the CSP cookie, decodes it and gets the ATT UID. * * @param request - * @return User ID if the cookie is present in the request and can be decoded successfully - * (expired cookies do not decode); else null. + * @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 userid = null; + String attuid = null; try { String[] cspFields = getCspData(request); if (cspFields != null && cspFields.length > 5) - userid = cspFields[5]; + attuid = cspFields[5]; } catch (Throwable t) { LOG.info(AaiUiMsgs.LOGIN_FILTER_INFO, "getLoginIdFromCookie failed " + t.getLocalizedMessage()); } - return userid; + return attuid; } /** diff --git a/src/main/java/org/onap/aai/sparky/security/filter/CspCookieFilter.java b/src/main/java/org/onap/aai/sparky/security/filter/CspCookieFilter.java new file mode 100644 index 0000000..51e77bb --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/security/filter/CspCookieFilter.java @@ -0,0 +1,274 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.security.filter; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +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.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.util.NodeUtils; +import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; + +// 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/onap/aai/sparky/security/filter/LoginFilter.java b/src/main/java/org/onap/aai/sparky/security/filter/LoginFilter.java index 445cfba..2ec6b47 100644 --- a/src/main/java/org/onap/aai/sparky/security/filter/LoginFilter.java +++ b/src/main/java/org/onap/aai/sparky/security/filter/LoginFilter.java @@ -36,11 +36,11 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.ws.rs.core.HttpHeaders; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.sparky.logging.AaiUiMsgs; import org.onap.aai.sparky.security.EcompSso; import org.onap.aai.sparky.security.portal.config.PortalAuthenticationConfig; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.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; @@ -126,10 +126,17 @@ public class LoginFilter implements Filter { // 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."; + if (request.getRequestURI().contains("/editAttributes")) { + // If request is for Edit Attributes UI, redirect straight to the application. + String appPath = request.getRequestURI().substring(request.getContextPath().length() + 1) + + (request.getQueryString() != null ? ("?" + request.getQueryString()) : ""); + redirectURL = SSOUtil.getECOMPSSORedirectURL(request, response, appPath); + logMessage = "Unauthenticated Edit Attributes UI login attempt."; + } else { + // 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: " diff --git a/src/main/java/org/onap/aai/sparky/security/portal/PortalRestAPIServiceImpl.java b/src/main/java/org/onap/aai/sparky/security/portal/PortalRestAPIServiceImpl.java index 050d558..d3ffac3 100644 --- a/src/main/java/org/onap/aai/sparky/security/portal/PortalRestAPIServiceImpl.java +++ b/src/main/java/org/onap/aai/sparky/security/portal/PortalRestAPIServiceImpl.java @@ -48,34 +48,6 @@ 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}]"; - /** - * @return the userManager - */ - public UserManager getUserManager() { - return userManager; - } - - /** - * @param userManager the userManager to set - */ - public void setUserManager(UserManager userManager) { - this.userManager = userManager; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - - /** - * @return the errorMessage - */ - public static String getErrorMessage() { - return ERROR_MESSAGE; - } - private UserManager userManager; /** @@ -175,8 +147,11 @@ public class PortalRestAPIServiceImpl implements IPortalRestAPIService { ///////////////////////////////////////////////////////////////////////////// // Role interface ///////////////////////////////////////////////////////////////////////////// + public List getAvailableRoles() throws PortalAPIException { + LOG.debug("Get available roles"); + return UserManager.getRoles(); + } - @Override public List getAvailableRoles(String requestedLoginId) throws PortalAPIException { LOG.debug("Get available roles"); return UserManager.getRoles(); diff --git a/src/main/java/org/onap/aai/sparky/security/portal/config/PortalAuthenticationConfig.java b/src/main/java/org/onap/aai/sparky/security/portal/config/PortalAuthenticationConfig.java index 6f103d0..f58fc31 100644 --- a/src/main/java/org/onap/aai/sparky/security/portal/config/PortalAuthenticationConfig.java +++ b/src/main/java/org/onap/aai/sparky/security/portal/config/PortalAuthenticationConfig.java @@ -28,6 +28,7 @@ import org.onap.aai.sparky.util.ConfigHelper; import org.onap.aai.sparky.util.Encryptor; import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; + /** * Provides Portal authentication configuration. */ diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/AbstractEntitySynchronizer.java b/src/main/java/org/onap/aai/sparky/sync/AbstractEntitySynchronizer.java similarity index 85% rename from src/main/java/org/onap/aai/sparky/synchronizer/AbstractEntitySynchronizer.java rename to src/main/java/org/onap/aai/sparky/sync/AbstractEntitySynchronizer.java index dde633c..bf1a7ee 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/AbstractEntitySynchronizer.java +++ b/src/main/java/org/onap/aai/sparky/sync/AbstractEntitySynchronizer.java @@ -20,30 +20,28 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.sync; -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.onap.aai.sparky.config.oxm.OxmModelLoader; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider; import org.onap.aai.sparky.dal.aai.ActiveInventoryEntityStatistics; import org.onap.aai.sparky.dal.aai.ActiveInventoryProcessingExceptionStatistics; -import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; -import org.onap.aai.sparky.dal.elasticsearch.ElasticSearchDataProvider; import org.onap.aai.sparky.dal.elasticsearch.ElasticSearchEntityStatistics; import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.dal.rest.RestOperationalStatistics; import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.mdc.MdcContext; + import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -58,7 +56,6 @@ public abstract class AbstractEntitySynchronizer { protected final Logger logger; protected ObjectMapper mapper; - protected OxmModelLoader oxmModelLoader; protected long syncDurationInMs; /** @@ -70,8 +67,8 @@ public abstract class AbstractEntitySynchronizer { protected EnumSet enabledStatFlags; - protected ActiveInventoryDataProvider aaiDataProvider; - protected ElasticSearchDataProvider esDataProvider; + protected ElasticSearchAdapter elasticSearchAdapter; + protected ActiveInventoryAdapter aaiAdapter; protected ExecutorService synchronizerExecutor; protected ExecutorService aaiExecutor; @@ -87,8 +84,8 @@ public abstract class AbstractEntitySynchronizer { private TaskProcessingStats aaiTaskProcessingStats; private TaskProcessingStats esTaskProcessingStats; - private TransactionRateController aaiTransactionRateController; - private TransactionRateController esTransactionRateController; + private TransactionRateMonitor aaiTransactionRateController; + private TransactionRateMonitor esTransactionRateController; protected AtomicInteger aaiWorkOnHand; protected AtomicInteger esWorkOnHand; @@ -255,7 +252,8 @@ public abstract class AbstractEntitySynchronizer { * @throws Exception the exception */ protected AbstractEntitySynchronizer(Logger logger, String syncName, int numSyncWorkers, - int numActiveInventoryWorkers, int numElasticsearchWorkers, String indexName) + int numActiveInventoryWorkers, int numElasticsearchWorkers, String indexName, + NetworkStatisticsConfig aaiStatConfig, NetworkStatisticsConfig esStatConfig) throws Exception { this.logger = logger; this.synchronizerExecutor = @@ -265,22 +263,19 @@ public abstract class AbstractEntitySynchronizer { 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.esEntityStats = new ElasticSearchEntityStatistics(); this.aaiRestStats = new RestOperationalStatistics(); - this.aaiEntityStats = new ActiveInventoryEntityStatistics(oxmModelLoader); + this.aaiEntityStats = new ActiveInventoryEntityStatistics(); this.aaiProcessingExceptionStats = new ActiveInventoryProcessingExceptionStatistics(); - this.aaiTaskProcessingStats = - new TaskProcessingStats(ActiveInventoryConfig.getConfig().getTaskProcessorConfig()); - this.esTaskProcessingStats = - new TaskProcessingStats(ElasticSearchConfig.getConfig().getProcessorConfig()); + this.aaiTaskProcessingStats = new TaskProcessingStats(aaiStatConfig); + this.esTaskProcessingStats = new TaskProcessingStats(esStatConfig); this.aaiTransactionRateController = - new TransactionRateController(ActiveInventoryConfig.getConfig().getTaskProcessorConfig()); + new TransactionRateMonitor(numActiveInventoryWorkers, aaiStatConfig); this.esTransactionRateController = - new TransactionRateController(ElasticSearchConfig.getConfig().getProcessorConfig()); + new TransactionRateMonitor(numElasticsearchWorkers, esStatConfig); this.aaiWorkOnHand = new AtomicInteger(0); this.esWorkOnHand = new AtomicInteger(0); @@ -338,11 +333,19 @@ public abstract class AbstractEntitySynchronizer { */ protected void shutdownExecutors() { try { - synchronizerExecutor.shutdown(); - aaiExecutor.shutdown(); - esExecutor.shutdown(); - aaiDataProvider.shutdown(); - esDataProvider.shutdown(); + + if (synchronizerExecutor != null) { + synchronizerExecutor.shutdown(); + } + + if (aaiExecutor != null) { + aaiExecutor.shutdown(); + } + + if (esExecutor != null) { + esExecutor.shutdown(); + } + } catch (Exception exc) { logger.error(AaiUiMsgs.ERROR_SHUTDOWN_EXECUTORS, exc); } @@ -351,26 +354,22 @@ public abstract class AbstractEntitySynchronizer { /** * Clear cache. */ - public void clearCache() { - if (aaiDataProvider != null) { - aaiDataProvider.clearCache(); - } - } + public void clearCache() {} - protected ActiveInventoryDataProvider getAaiDataProvider() { - return aaiDataProvider; + public ElasticSearchAdapter getElasticSearchAdapter() { + return elasticSearchAdapter; } - public void setAaiDataProvider(ActiveInventoryDataProvider aaiDataProvider) { - this.aaiDataProvider = aaiDataProvider; + public void setElasticSearchAdapter(ElasticSearchAdapter elasticSearchAdapter) { + this.elasticSearchAdapter = elasticSearchAdapter; } - protected ElasticSearchDataProvider getEsDataProvider() { - return esDataProvider; + public ActiveInventoryAdapter getAaiAdapter() { + return aaiAdapter; } - public void setEsDataProvider(ElasticSearchDataProvider provider) { - this.esDataProvider = provider; + public void setAaiAdapter(ActiveInventoryAdapter aaiAdapter) { + this.aaiAdapter = aaiAdapter; } /** @@ -472,10 +471,9 @@ public abstract class AbstractEntitySynchronizer { if (enabledStatFlags.contains(StatFlag.ES_TASK_PROCESSING_STATS)) { - esTransactionRateController.trackResponseTime(txn.getOperationResult().getResponseTimeInMs()); + esTransactionRateController.trackResponseTime(txn.getOpTimeInMs()); - esTaskProcessingStats - .updateTaskResponseStatsHistogram(txn.getOperationResult().getResponseTimeInMs()); + esTaskProcessingStats.updateTaskResponseStatsHistogram(txn.getOpTimeInMs()); esTaskProcessingStats.updateTaskAgeStatsHistogram(txn.getTaskAgeInMs()); // don't know the cost of the lengh calc, we'll see if it causes a @@ -533,11 +531,9 @@ public abstract class AbstractEntitySynchronizer { } if (enabledStatFlags.contains(StatFlag.AAI_TASK_PROCESSING_STATS)) { - aaiTransactionRateController - .trackResponseTime(txn.getOperationResult().getResponseTimeInMs()); + aaiTransactionRateController.trackResponseTime(txn.getOpTimeInMs()); - aaiTaskProcessingStats - .updateTaskResponseStatsHistogram(txn.getOperationResult().getResponseTimeInMs()); + aaiTaskProcessingStats.updateTaskResponseStatsHistogram(txn.getOpTimeInMs()); aaiTaskProcessingStats.updateTaskAgeStatsHistogram(txn.getTaskAgeInMs()); // don't know the cost of the lengh calc, we'll see if it causes a diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/ElasticSearchIndexCleaner.java b/src/main/java/org/onap/aai/sparky/sync/ElasticSearchIndexCleaner.java similarity index 65% rename from src/main/java/org/onap/aai/sparky/synchronizer/ElasticSearchIndexCleaner.java rename to src/main/java/org/onap/aai/sparky/sync/ElasticSearchIndexCleaner.java index 59942dc..e1785d4 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/ElasticSearchIndexCleaner.java +++ b/src/main/java/org/onap/aai/sparky/sync/ElasticSearchIndexCleaner.java @@ -20,13 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.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; +package org.onap.aai.sparky.sync; import java.io.IOException; import java.util.ArrayList; @@ -34,14 +28,24 @@ import java.util.Collection; import java.util.Iterator; import java.util.List; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.rest.RestDataProvider; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.entity.ObjectIdCollection; -import org.onap.aai.sparky.synchronizer.entity.SearchableEntity; -import org.onap.aai.sparky.synchronizer.enumeration.OperationState; +import javax.ws.rs.core.MediaType; + import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.entity.ObjectIdCollection; +import org.onap.aai.sparky.sync.entity.SearchableEntity; +import org.onap.aai.sparky.sync.enumeration.OperationState; + +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; /** * The Class ElasticSearchIndexCleaner. @@ -57,16 +61,10 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { 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; + private ElasticSearchAdapter esAdapter; + private ElasticSearchEndpointConfig endpointConfig; + private ElasticSearchSchemaConfig schemaConfig; /** * Instantiates a new elastic search index cleaner. @@ -79,25 +77,20 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { * @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; + public ElasticSearchIndexCleaner(ElasticSearchAdapter esAdapter, + ElasticSearchEndpointConfig endpointConfig, ElasticSearchSchemaConfig schemaConfig) { + this.esAdapter = esAdapter; this.before = null; this.after = null; - this.indexName = indexName; - this.indexType = indexType; + this.endpointConfig = endpointConfig; + this.schemaConfig = schemaConfig; this.mapper = new ObjectMapper(); - this.host = host; - this.port = port; - this.scrollContextTimeToLiveInMinutes = scrollContextTimeToLiveInMinutes; - this.numItemsToGetBulkRequest = numItemsToGetBulkRequest; } /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexCleaner#populatePreOperationCollection() + * @see org.openecomp.sparky.synchronizer.IndexCleaner#populatePreOperationCollection() */ @Override public OperationState populatePreOperationCollection() { @@ -106,7 +99,7 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { before = retrieveAllDocumentIdentifiers(); return OperationState.OK; } catch (Exception exc) { - LOG.error(AaiUiMsgs.ES_PRE_SYNC_FAILURE, indexName, exc.getMessage()); + LOG.error(AaiUiMsgs.ES_PRE_SYNC_FAILURE, schemaConfig.getIndexName(), exc.getMessage()); return OperationState.ERROR; } @@ -115,7 +108,7 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexCleaner#populatePostOperationCollection() + * @see org.openecomp.sparky.synchronizer.IndexCleaner#populatePostOperationCollection() */ @Override public OperationState populatePostOperationCollection() { @@ -123,7 +116,7 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { after = retrieveAllDocumentIdentifiers(); return OperationState.OK; } catch (Exception exc) { - LOG.error(AaiUiMsgs.ES_PRE_SYNC_FAILURE, indexName, exc.getMessage()); + LOG.error(AaiUiMsgs.ES_PRE_SYNC_FAILURE, schemaConfig.getIndexName(), exc.getMessage()); return OperationState.ERROR; } } @@ -131,12 +124,12 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexCleaner#performCleanup() + * @see org.openecomp.sparky.synchronizer.IndexCleaner#performCleanup() */ @Override public OperationState performCleanup() { // TODO Auto-generated method stub - LOG.info(AaiUiMsgs.ES_SYNC_CLEAN_UP, indexName); + LOG.info(AaiUiMsgs.ES_SYNC_CLEAN_UP, schemaConfig.getIndexName()); int sizeBefore = before.getSize(); int sizeAfter = after.getSize(); @@ -151,12 +144,12 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { if (sizeAfter > 0) { - Collection presyncIds = before.getImportedObjectIdsAsValues(); - presyncIds.removeAll(after.getImportedObjectIdsAsValues()); + Collection presyncIds = before.getImportedObjectIds(); + presyncIds.removeAll(after.getImportedObjectIds()); try { - LOG.info(AaiUiMsgs.ES_SYNC_SELECTIVE_DELETE, indexName, indexType, - String.valueOf(presyncIds.size())); + LOG.info(AaiUiMsgs.ES_SYNC_SELECTIVE_DELETE, schemaConfig.getIndexName(), + schemaConfig.getIndexDocType(), String.valueOf(presyncIds.size())); ObjectIdCollection bulkIds = new ObjectIdCollection(); @@ -169,10 +162,10 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { bulkIds.addObjectId(it.next()); numItemsInBulkRequest++; - if (numItemsInBulkRequest >= this.numItemsToGetBulkRequest) { - LOG.info(AaiUiMsgs.ES_BULK_DELETE, indexName, String.valueOf(bulkIds.getSize())); - OperationResult bulkDeleteResult = bulkDelete(bulkIds.getImportedObjectIdsAsValues()); - // pegCountersForElasticBulkDelete(bulkDeleteResult); + if (numItemsInBulkRequest >= endpointConfig.getScrollContextBatchRequestSize()) { + LOG.info(AaiUiMsgs.ES_BULK_DELETE, schemaConfig.getIndexName(), + String.valueOf(bulkIds.getSize())); + bulkDelete(bulkIds.getImportedObjectIds()); numItemsRemainingToBeDeleted -= numItemsInBulkRequest; numItemsInBulkRequest = 0; bulkIds.clear(); @@ -180,14 +173,15 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { } if (numItemsRemainingToBeDeleted > 0) { - LOG.info(AaiUiMsgs.ES_BULK_DELETE, indexName, String.valueOf(bulkIds.getSize())); - OperationResult bulkDeleteResult = bulkDelete(bulkIds.getImportedObjectIdsAsValues()); - // pegCountersForElasticBulkDelete(bulkDeleteResult); + LOG.info(AaiUiMsgs.ES_BULK_DELETE, schemaConfig.getIndexName(), + String.valueOf(bulkIds.getSize())); + bulkDelete(bulkIds.getImportedObjectIds()); } } catch (Exception exc) { - LOG.error(AaiUiMsgs.ES_BULK_DELETE_ERROR, indexName, exc.getLocalizedMessage()); + LOG.error(AaiUiMsgs.ES_BULK_DELETE_ERROR, schemaConfig.getIndexName(), + exc.getLocalizedMessage()); } } @@ -197,11 +191,7 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { @Override public String getIndexName() { - return indexName; - } - - public void setIndexName(String indexName) { - this.indexName = indexName; + return schemaConfig.getIndexName(); } /** @@ -352,7 +342,8 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { * @return the full url */ private String getFullUrl(String resourceUrl) { - return String.format("http://%s:%s%s", host, port, resourceUrl); + return String.format("http://%s:%s%s", endpointConfig.getEsIpAddress(), + endpointConfig.getEsServerPort(), resourceUrl); } /** @@ -372,13 +363,14 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { // fields.add("entityType"); String scrollRequestPayload = - buildInitialScrollRequestPayload(this.numItemsToGetBulkRequest, fields); + buildInitialScrollRequestPayload(endpointConfig.getScrollContextBatchRequestSize(), fields); - final String fullUrlStr = getFullUrl("/" + indexName + "/" + indexType + "/_search?scroll=" - + this.scrollContextTimeToLiveInMinutes + "m"); + final String fullUrlStr = + getFullUrl("/" + schemaConfig.getIndexName() + "/" + schemaConfig.getIndexDocType() + + "/_search?scroll=" + endpointConfig.getScrollContextTimeToLiveInMinutes() + "m"); OperationResult result = - restDataProvider.doPost(fullUrlStr, scrollRequestPayload, "application/json"); + esAdapter.doPost(fullUrlStr, scrollRequestPayload, MediaType.APPLICATION_JSON_TYPE); if (result.wasSuccessful()) { @@ -435,33 +427,18 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { 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); + (totalRecordsRemainingToFetch / endpointConfig.getScrollContextBatchRequestSize()); /* * Do an additional fetch for the remaining items (if needed) */ - if (totalRecordsRemainingToFetch % numItemsToGetBulkRequest != 0) { + if (totalRecordsRemainingToFetch % endpointConfig.getScrollContextBatchRequestSize() != 0) { numRequiredAdditionalFetches += 1; } @@ -511,15 +488,13 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { private OperationState collectItemsFromScrollContext(String scrollId, ObjectIdCollection objectIds) throws IOException { - // ObjectIdCollection documentIdCollection = new ObjectIdCollection(); - - String requestPayload = - buildSubsequentScrollContextRequestPayload(scrollId, scrollContextTimeToLiveInMinutes); + String requestPayload = buildSubsequentScrollContextRequestPayload(scrollId, + endpointConfig.getScrollContextTimeToLiveInMinutes()); final String fullUrlStr = getFullUrl("/_search/scroll"); OperationResult opResult = - restDataProvider.doPost(fullUrlStr, requestPayload, "application/json"); + esAdapter.doPost(fullUrlStr, requestPayload, MediaType.APPLICATION_JSON_TYPE); if (opResult.getResultCode() >= 300) { LOG.warn(AaiUiMsgs.ES_SCROLL_CONTEXT_ERROR, opResult.getResult()); @@ -527,6 +502,11 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { } 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 @@ -534,11 +514,6 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { */ if (rootNode != null) { - 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"); if (timedOut) { LOG.info(AaiUiMsgs.COLLECT_TIME_WITH_ERROR, "Scroll Context", String.valueOf(tookInMs)); @@ -566,25 +541,9 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { 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); } } } } - */ - } } - } else { - // scroll context get failed, nothing else to do - LOG.error(AaiUiMsgs.ERROR_GENERIC, opResult.toString()); } return OperationState.OK; @@ -629,163 +588,16 @@ public class ElasticSearchIndexCleaner implements IndexCleaner { StringBuilder sb = new StringBuilder(128); for (String id : docIds) { - sb.append( - String.format(BULK_OP_LINE_TEMPLATE, buildDeleteDataObject(indexName, indexType, id))); + sb.append(String.format(BULK_OP_LINE_TEMPLATE, + buildDeleteDataObject(schemaConfig.getIndexName(), schemaConfig.getIndexDocType(), id))); } sb.append("\n"); final String fullUrlStr = getFullUrl("/_bulk"); - return restDataProvider.doPost(fullUrlStr, sb.toString(), "application/x-www-form-urlencoded"); - - } - - /** - * @return the before - */ - public ObjectIdCollection getBefore() { - return before; - } - - /** - * @param before the before to set - */ - public void setBefore(ObjectIdCollection before) { - this.before = before; - } - - /** - * @return the after - */ - public ObjectIdCollection getAfter() { - return after; - } - - /** - * @param after the after to set - */ - public void setAfter(ObjectIdCollection after) { - this.after = after; - } - - /** - * @return the host - */ - public String getHost() { - return host; - } - - /** - * @param host the host to set - */ - public void setHost(String host) { - this.host = host; - } - - /** - * @return the port - */ - public String getPort() { - return port; - } - - /** - * @param port the port to set - */ - public void setPort(String port) { - this.port = port; - } - - /** - * @return the indexType - */ - public String getIndexType() { - return indexType; - } - - /** - * @param indexType the indexType to set - */ - public void setIndexType(String indexType) { - this.indexType = indexType; - } - - /** - * @return the scrollContextTimeToLiveInMinutes - */ - public int getScrollContextTimeToLiveInMinutes() { - return scrollContextTimeToLiveInMinutes; - } - - /** - * @param scrollContextTimeToLiveInMinutes the scrollContextTimeToLiveInMinutes to set - */ - public void setScrollContextTimeToLiveInMinutes(int scrollContextTimeToLiveInMinutes) { - this.scrollContextTimeToLiveInMinutes = scrollContextTimeToLiveInMinutes; - } + return esAdapter.doPost(fullUrlStr, sb.toString(), MediaType.APPLICATION_FORM_URLENCODED_TYPE); - /** - * @return the numItemsToGetBulkRequest - */ - public int getNumItemsToGetBulkRequest() { - return numItemsToGetBulkRequest; - } - - /** - * @param numItemsToGetBulkRequest the numItemsToGetBulkRequest to set - */ - public void setNumItemsToGetBulkRequest(int numItemsToGetBulkRequest) { - this.numItemsToGetBulkRequest = numItemsToGetBulkRequest; - } - - /** - * @return the restDataProvider - */ - public RestDataProvider getRestDataProvider() { - return restDataProvider; - } - - /** - * @param restDataProvider the restDataProvider to set - */ - public void setRestDataProvider(RestDataProvider restDataProvider) { - this.restDataProvider = restDataProvider; - } - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } - - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - - /** - * @return the bulkOpLineTemplate - */ - public static String getBulkOpLineTemplate() { - return BULK_OP_LINE_TEMPLATE; - } - - /** - * @return the timestampFormat - */ - public static String getTimestampFormat() { - return TIMESTAMP_FORMAT; } /* diff --git a/src/main/java/org/onap/aai/sparky/sync/ElasticSearchSchemaFactory.java b/src/main/java/org/onap/aai/sparky/sync/ElasticSearchSchemaFactory.java new file mode 100644 index 0000000..9013600 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/ElasticSearchSchemaFactory.java @@ -0,0 +1,109 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync; + +import java.io.IOException; + +import org.onap.aai.sparky.dal.exception.ElasticSearchOperationException; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.util.ConfigHelper; +import org.onap.aai.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; + +public class ElasticSearchSchemaFactory { + + private static final String SETTINGS = "settings"; + private static final String MAPPINGS = "mappings"; + + private static ObjectMapper mapper = new ObjectMapper(); + + protected static 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 static String getIndexSchema(ElasticSearchSchemaConfig schemaConfig) + throws ElasticSearchOperationException { + + JsonNode esSettingsNode = null; + JsonNode esMappingsNodes = null; + + try { + + if (schemaConfig.getIndexSettingsFileName() != null) { + esSettingsNode = mapper.readTree(getConfigAsString(SETTINGS, + TierSupportUiConstants.getConfigPath(schemaConfig.getIndexSettingsFileName()))); + } + + if (schemaConfig.getIndexMappingsFileName() != null) { + esMappingsNodes = mapper.readTree(getConfigAsString(MAPPINGS, + TierSupportUiConstants.getConfigPath(schemaConfig.getIndexMappingsFileName()))); + } + + } catch (IOException e1) { + + throw new ElasticSearchOperationException( + "Caught an exception building initial ES index. Error: " + e1.getMessage()); + } + + ObjectNode esConfig = null; + + ObjectNode mappings = + (ObjectNode) mapper.createObjectNode().set(schemaConfig.getIndexDocType(), esMappingsNodes); + + if (esSettingsNode == null) { + esConfig = (ObjectNode) mapper.createObjectNode().set(MAPPINGS, mappings); + } else { + esConfig = (ObjectNode) mapper.createObjectNode().set(SETTINGS, esSettingsNode); + esConfig.set(MAPPINGS, mappings); + } + + try { + return mapper.writeValueAsString(esConfig); + } catch (JsonProcessingException exc) { + throw new ElasticSearchOperationException("Error getting object node as string", exc); + } + + } + + +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/IndexCleaner.java b/src/main/java/org/onap/aai/sparky/sync/IndexCleaner.java similarity index 93% rename from src/main/java/org/onap/aai/sparky/synchronizer/IndexCleaner.java rename to src/main/java/org/onap/aai/sparky/sync/IndexCleaner.java index 4edab03..3b0ec57 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/IndexCleaner.java +++ b/src/main/java/org/onap/aai/sparky/sync/IndexCleaner.java @@ -20,9 +20,9 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.sync; -import org.onap.aai.sparky.synchronizer.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.OperationState; /** * The Interface IndexCleaner. diff --git a/src/main/java/org/onap/aai/sparky/sync/IndexIntegrityValidator.java b/src/main/java/org/onap/aai/sparky/sync/IndexIntegrityValidator.java new file mode 100644 index 0000000..a6941ad --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/IndexIntegrityValidator.java @@ -0,0 +1,176 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync; + +import javax.ws.rs.core.MediaType; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; + +/** + * The Class IndexIntegrityValidator. + */ +public class IndexIntegrityValidator implements IndexValidator { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(IndexIntegrityValidator.class); + + private ElasticSearchEndpointConfig endpointConfig; + private ElasticSearchSchemaConfig schemaConfig; + private String tableConfigJson; + + private final ElasticSearchAdapter esAdapter; + + /** + * 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(ElasticSearchAdapter esAdapter, + ElasticSearchSchemaConfig esSchemaConfig, ElasticSearchEndpointConfig esEndpointConfig, + String tableConfigJson) { + + this.esAdapter = esAdapter; + this.schemaConfig = esSchemaConfig; + this.endpointConfig = esEndpointConfig; + this.tableConfigJson = tableConfigJson; + } + + public ElasticSearchEndpointConfig getEndpointConfig() { + return endpointConfig; + } + + public void setEndpointConfig(ElasticSearchEndpointConfig endpointConfig) { + this.endpointConfig = endpointConfig; + } + + public ElasticSearchSchemaConfig getSchemaConfig() { + return schemaConfig; + } + + public void setSchemaConfig(ElasticSearchSchemaConfig schemaConfig) { + this.schemaConfig = schemaConfig; + } + + public ElasticSearchAdapter getEsAdapter() { + return esAdapter; + } + + @Override + public String getIndexName() { + return schemaConfig.getIndexName(); + } + + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexValidator#exists() + */ + @Override + public boolean exists() { + final String fullUrlStr = getFullUrl("/" + schemaConfig.getIndexName() + "/"); + OperationResult existsResult = esAdapter.doHead(fullUrlStr, MediaType.APPLICATION_JSON_TYPE); + + int rc = existsResult.getResultCode(); + + if (rc >= 200 && rc < 300) { + LOG.info(AaiUiMsgs.INDEX_EXISTS, schemaConfig.getIndexName()); + return true; + } else { + LOG.info(AaiUiMsgs.INDEX_NOT_EXIST, schemaConfig.getIndexName()); + return false; + } + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexValidator#integrityValid() + */ + @Override + public boolean integrityValid() { + return true; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexValidator#createOrRepair() + */ + @Override + public void createOrRepair() { + + String message = + "IndexIntegrityValidator.createOrRepair() for indexName = " + schemaConfig.getIndexName(); + LOG.info(AaiUiMsgs.INFO_GENERIC, message); + + final String fullUrlStr = getFullUrl("/" + schemaConfig.getIndexName() + "/"); + OperationResult createResult = + esAdapter.doPut(fullUrlStr, tableConfigJson, MediaType.APPLICATION_JSON_TYPE); + + int rc = createResult.getResultCode(); + + if (rc >= 200 && rc < 300) { + LOG.info(AaiUiMsgs.INDEX_RECREATED, schemaConfig.getIndexName()); + } else if (rc == 400) { + LOG.info(AaiUiMsgs.INDEX_ALREADY_EXISTS, schemaConfig.getIndexName()); + } else { + LOG.warn(AaiUiMsgs.INDEX_INTEGRITY_CHECK_FAILED, schemaConfig.getIndexName(), + createResult.getResult()); + } + + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexValidator#destroyIndex() + */ + @Override + public void destroyIndex() { + // 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", endpointConfig.getEsIpAddress(), + endpointConfig.getEsServerPort(), resourceUrl); + } + +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/IndexSynchronizer.java b/src/main/java/org/onap/aai/sparky/sync/IndexSynchronizer.java similarity index 90% rename from src/main/java/org/onap/aai/sparky/synchronizer/IndexSynchronizer.java rename to src/main/java/org/onap/aai/sparky/sync/IndexSynchronizer.java index f1c6741..6e581f6 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/IndexSynchronizer.java +++ b/src/main/java/org/onap/aai/sparky/sync/IndexSynchronizer.java @@ -20,10 +20,10 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.sync; -import org.onap.aai.sparky.synchronizer.enumeration.OperationState; -import org.onap.aai.sparky.synchronizer.enumeration.SynchronizerState; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; /** * The Interface IndexSynchronizer. diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/IndexValidator.java b/src/main/java/org/onap/aai/sparky/sync/IndexValidator.java similarity index 97% rename from src/main/java/org/onap/aai/sparky/synchronizer/IndexValidator.java rename to src/main/java/org/onap/aai/sparky/sync/IndexValidator.java index ae2f6f9..e78d95c 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/IndexValidator.java +++ b/src/main/java/org/onap/aai/sparky/sync/IndexValidator.java @@ -20,7 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.sync; /** * The Interface IndexValidator. diff --git a/src/main/java/org/onap/aai/sparky/sync/SyncController.java b/src/main/java/org/onap/aai/sparky/sync/SyncController.java new file mode 100644 index 0000000..f482c66 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/SyncController.java @@ -0,0 +1,96 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync; + +import java.util.Calendar; +import java.util.Date; + +import org.onap.aai.sparky.sync.SyncControllerImpl.SyncActions; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; + +public interface SyncController { + + String getControllerName(); + + boolean isPeriodicSyncEnabled(); + + boolean isRunOnceSyncEnabled(); + + /** + * Perform action. + * + * @param requestedAction the requested action + * @return + */ + OperationState performAction(SyncActions requestedAction); + + /** + * Register entity synchronizer. + * + * @param entitySynchronizer the entity synchronizer + */ + void registerEntitySynchronizer(IndexSynchronizer entitySynchronizer); + + /** + * Register index validator. + * + * @param indexValidator the index validator + */ + void registerIndexValidator(IndexValidator indexValidator); + + /** + * Register index cleaner. + * + * @param indexCleaner the index cleaner + */ + void registerIndexCleaner(IndexCleaner indexCleaner); + + /** + * Shutdown. + */ + void shutdown(); + + SynchronizerState getState(); + + long getDelayInMs(); + + void setDelayInMs(long delayInMs); + + long getSyncFrequencyInMs(); + + void setSyncFrequencyInMs(long syncFrequencyInMs); + + Date getSyncStartTime(); + + void setSyncStartTime(Date syncStartTime); + + Date getLastExecutionDate(); + + void setLastExecutionDate(Date lastExecutionDate); + + Calendar getCreationTime(); + + String getNextSyncTime(); + +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/SyncController.java b/src/main/java/org/onap/aai/sparky/sync/SyncControllerImpl.java similarity index 61% rename from src/main/java/org/onap/aai/sparky/synchronizer/SyncController.java rename to src/main/java/org/onap/aai/sparky/sync/SyncControllerImpl.java index 0f61923..1c3d425 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/SyncController.java +++ b/src/main/java/org/onap/aai/sparky/sync/SyncControllerImpl.java @@ -20,28 +20,35 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.sync; import static java.util.concurrent.CompletableFuture.supplyAsync; +import java.util.Calendar; import java.util.Collection; +import java.util.Date; import java.util.LinkedHashSet; +import java.util.TimeZone; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.enumeration.SynchronizerState; -import org.onap.aai.sparky.util.NodeUtils; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.config.SyncControllerConfig; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.onap.aai.sparky.util.NodeUtils; /** * The Class SyncController. * * @author davea. */ -public class SyncController { - private static final Logger LOG = LoggerFactory.getInstance().getLogger(SyncController.class); +public class SyncControllerImpl implements SyncController { + private static final Logger LOG = LoggerFactory.getInstance().getLogger(SyncControllerImpl.class); /** * The Enum InternalState. @@ -63,7 +70,22 @@ public class SyncController { private InternalState currentInternalState; private ExecutorService syncControllerExecutor; private ExecutorService statReporterExecutor; - private final String controllerName; + + private long delayInMs; + private long syncFrequencyInMs; + private Date syncStartTime; + + private Date lastExecutionDate; + private AtomicInteger runCount; + private Semaphore performingActionGate; + private Calendar creationTime; + + private String syncStartTimeWithTimeZone; + private String controllerName; + + protected SyncControllerConfig syncControllerConfig; + + /** * Instantiates a new sync controller. @@ -71,24 +93,47 @@ public class SyncController { * @param name the name * @throws Exception the exception */ - public SyncController(String name) throws Exception { + public SyncControllerImpl(SyncControllerConfig syncControllerConfig) throws Exception { + this(syncControllerConfig, null); + } - this.controllerName = name; - /* - * Does LHS result in a non-duplicated object collection?? What happens if you double-add an - * object? - */ + public SyncControllerImpl(SyncControllerConfig syncControllerConfig, String targetEntityType) + throws Exception { + + this.syncControllerConfig = syncControllerConfig; + this.delayInMs = 0L; + this.syncFrequencyInMs = 86400000L; + this.syncStartTime = null; + this.lastExecutionDate = null; + this.runCount = new AtomicInteger(0); + this.performingActionGate = new Semaphore(1); registeredSynchronizers = new LinkedHashSet(); registeredIndexValidators = new LinkedHashSet(); registeredIndexCleaners = new LinkedHashSet(); - this.syncControllerExecutor = NodeUtils.createNamedExecutor("SyncController", 5, LOG); - this.statReporterExecutor = NodeUtils.createNamedExecutor("StatReporter", 1, LOG); + String controllerName = syncControllerConfig.getControllerName(); + + if (targetEntityType != null) { + controllerName += " (" + targetEntityType + ")"; + } + + this.controllerName = controllerName; + + this.syncControllerExecutor = NodeUtils.createNamedExecutor("SyncController-" + controllerName, + syncControllerConfig.getNumSyncControllerWorkers(), LOG); + this.statReporterExecutor = + NodeUtils.createNamedExecutor("StatReporter-" + controllerName, 1, LOG); this.currentInternalState = InternalState.IDLE; + + this.creationTime = Calendar + .getInstance(TimeZone.getTimeZone(syncControllerConfig.getTimeZoneOfSyncStartTimeStamp())); + } + + /** * Change internal state. * @@ -104,36 +149,182 @@ public class SyncController { performStateAction(); } + + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncController2#getDelayInMs() + */ + @Override + public long getDelayInMs() { + return delayInMs; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncController2#setDelayInMs(long) + */ + @Override + public void setDelayInMs(long delayInMs) { + this.delayInMs = delayInMs; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncController2#getSyncFrequencyInMs() + */ + @Override + public long getSyncFrequencyInMs() { + return syncFrequencyInMs; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncController2#setSyncFrequencyInMs(long) + */ + @Override + public void setSyncFrequencyInMs(long syncFrequencyInMs) { + this.syncFrequencyInMs = syncFrequencyInMs; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncController2#getSyncStartTime() + */ + @Override + public Date getSyncStartTime() { + return syncStartTime; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncController2#setSyncStartTime(java.util.Date) + */ + @Override + public void setSyncStartTime(Date syncStartTime) { + this.syncStartTime = syncStartTime; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncController2#getLastExecutionDate() + */ + @Override + public Date getLastExecutionDate() { + return lastExecutionDate; + } + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncController2#setLastExecutionDate(java.util.Date) + */ + @Override + public void setLastExecutionDate(Date lastExecutionDate) { + this.lastExecutionDate = lastExecutionDate; + } + + @Override public String getControllerName() { return controllerName; } - /** - * Perform action. - * - * @param requestedAction the requested action - */ - public void performAction(SyncActions requestedAction) { + + + @Override + public OperationState performAction(SyncActions requestedAction) { if (currentInternalState == InternalState.IDLE) { try { + + /* + * non-blocking semaphore acquire used to guarantee only 1 execution of the synchronization + * at a time. + */ + switch (requestedAction) { case SYNCHRONIZE: - changeInternalState(InternalState.TEST_INDEX_INTEGRITY, requestedAction); + + if (performingActionGate.tryAcquire()) { + try { + + long opStartTime = System.currentTimeMillis(); + + LOG.info(AaiUiMsgs.INFO_GENERIC, + getControllerName() + " started synchronization at " + + SynchronizerConstants.SIMPLE_DATE_FORMAT.format(opStartTime).replaceAll( + SynchronizerConstants.TIME_STD, SynchronizerConstants.TIME_CONFIG_STD)); + + runCount.incrementAndGet(); + + changeInternalState(InternalState.TEST_INDEX_INTEGRITY, requestedAction); + + long opEndTime = System.currentTimeMillis(); + + long opTime = (opEndTime - opStartTime); + + String durationMessage = + String.format(getControllerName() + " synchronization took '%d' ms.", opTime); + + LOG.info(AaiUiMsgs.SYNC_DURATION, durationMessage); + + if (syncControllerConfig.isPeriodicSyncEnabled()) { + + LOG.info(AaiUiMsgs.INFO_GENERIC, + getControllerName() + " next sync to begin at " + getNextSyncTime()); + + TimeZone tz = + TimeZone.getTimeZone(syncControllerConfig.getTimeZoneOfSyncStartTimeStamp()); + + if (opTime > this.getSyncFrequencyInMs()) { + + String durationWasLongerMessage = String.format( + getControllerName() + " synchronization took '%d' ms which is larger than" + + " synchronization interval of '%d' ms.", + opTime, this.getSyncFrequencyInMs()); + + LOG.info(AaiUiMsgs.SYNC_DURATION, durationWasLongerMessage); + } + } + + } catch (Exception syncException) { + String message = "An error occurred while performing action = " + requestedAction + + ". Error = " + syncException.getMessage(); + LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + } finally { + performingActionGate.release(); + } + } else { + return OperationState.IGNORED_SYNC_NOT_IDLE; + } + break; default: break; } + return OperationState.OK; + } catch (Exception exc) { String message = "An error occurred while performing action = " + requestedAction + ". Error = " + exc.getMessage(); LOG.error(AaiUiMsgs.ERROR_GENERIC, message); + return OperationState.ERROR; + } finally { + } } else { LOG.error(AaiUiMsgs.SYNC_NOT_VALID_STATE_DURING_REQUEST, currentInternalState.toString()); + return OperationState.IGNORED_SYNC_NOT_IDLE; } } @@ -182,16 +373,15 @@ public class SyncController { break; } } catch (Exception exc) { + /* + * Perhaps we should abort the sync on an exception + */ 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 - */ + @Override public void registerEntitySynchronizer(IndexSynchronizer entitySynchronizer) { String indexName = entitySynchronizer.getIndexName(); @@ -205,11 +395,7 @@ public class SyncController { } - /** - * Register index validator. - * - * @param indexValidator the index validator - */ + @Override public void registerIndexValidator(IndexValidator indexValidator) { String indexName = indexValidator.getIndexName(); @@ -223,11 +409,7 @@ public class SyncController { } - /** - * Register index cleaner. - * - * @param indexCleaner the index cleaner - */ + @Override public void registerIndexCleaner(IndexCleaner indexCleaner) { String indexName = indexCleaner.getIndexName(); @@ -356,9 +538,12 @@ public class SyncController { } - /** - * Shutdown. + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncControllerInterface#shutdown() */ + @Override public void shutdown() { this.syncControllerExecutor.shutdown(); @@ -420,28 +605,32 @@ public class SyncController { boolean dumpPeriodicStatReport = false; while (!allDone) { - int totalFinished = 0; for (IndexSynchronizer synchronizer : registeredSynchronizers) { if (dumpPeriodicStatReport) { - if (synchronizer.getState() != SynchronizerState.IDLE) { + if (synchronizer.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { String statReport = synchronizer.getStatReport(false); + if (statReport != null) { LOG.info(AaiUiMsgs.INFO_GENERIC, statReport); } } - if (synchronizer.getState() == SynchronizerState.IDLE) { - totalFinished++; - } + } + + if (synchronizer.getState() == SynchronizerState.IDLE + || synchronizer.getState() == SynchronizerState.ABORTED) { + totalFinished++; } } + if (System.currentTimeMillis() > nextReportTimeStampInMs) { dumpPeriodicStatReport = true; nextReportTimeStampInMs = System.currentTimeMillis() + 30000L; } else { dumpPeriodicStatReport = false; } + allDone = (totalFinished == registeredSynchronizers.size()); try { @@ -457,6 +646,12 @@ public class SyncController { } + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.SyncControllerInterface#getState() + */ + @Override public SynchronizerState getState() { switch (currentInternalState) { @@ -473,4 +668,25 @@ public class SyncController { } + @Override + public Calendar getCreationTime() { + return creationTime; + } + + @Override + public String getNextSyncTime() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isPeriodicSyncEnabled() { + return syncControllerConfig.isPeriodicSyncEnabled(); + } + + @Override + public boolean isRunOnceSyncEnabled() { + return syncControllerConfig.isRunOnceSyncEnabled(); + } + } diff --git a/src/main/java/org/onap/aai/sparky/sync/SyncControllerRegistrar.java b/src/main/java/org/onap/aai/sparky/sync/SyncControllerRegistrar.java new file mode 100644 index 0000000..cb2f3ce --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/SyncControllerRegistrar.java @@ -0,0 +1,27 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync; + +public interface SyncControllerRegistrar { + public void registerController(); +} diff --git a/src/main/java/org/onap/aai/sparky/config/Configurable.java b/src/main/java/org/onap/aai/sparky/sync/SyncControllerRegistry.java similarity index 66% rename from src/main/java/org/onap/aai/sparky/config/Configurable.java rename to src/main/java/org/onap/aai/sparky/sync/SyncControllerRegistry.java index d108bef..90845e0 100644 --- a/src/main/java/org/onap/aai/sparky/config/Configurable.java +++ b/src/main/java/org/onap/aai/sparky/sync/SyncControllerRegistry.java @@ -20,24 +20,29 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.config; +package org.onap.aai.sparky.sync; -import org.onap.aai.sparky.config.exception.ConfigurationException; +import java.util.ArrayList; +import java.util.List; -/** - * The Interface Configurable. - */ -public interface Configurable { +public class SyncControllerRegistry { + + private List controllers; + + public SyncControllerRegistry() { + controllers = new ArrayList(); + } - public boolean isValid(); + public void registerSyncController(SyncController controller) { + controllers.add(controller); + } - public boolean isInitialized(); + public List getControllers() { + return controllers; + } - /** - * Load config. - * - * @throws ConfigurationException the configuration exception - */ - public void loadConfig() throws ConfigurationException; + public void setControllers(List controllers) { + this.controllers = controllers; + } } diff --git a/src/main/java/org/onap/aai/sparky/sync/SyncControllerService.java b/src/main/java/org/onap/aai/sparky/sync/SyncControllerService.java new file mode 100644 index 0000000..a137065 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/SyncControllerService.java @@ -0,0 +1,220 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.SyncControllerImpl.SyncActions; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +public class SyncControllerService implements ApplicationListener { + + private SyncControllerRegistry syncControllerRegistry; + private ExecutorService runonceSyncExecutor; + private ScheduledExecutorService periodicSyncExecutor; + private boolean syncStarted; + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(SyncControllerService.class); + + private class SyncControllerTask implements Runnable { + + private SyncController controller; + + public SyncControllerTask(SyncController controller) { + this.controller = controller; + } + + @Override + public void run() { + + try { + + if (controller.getState() == SynchronizerState.IDLE) { + + /* + * This is a blocking-call, but would be nicer if it was async internally within the + * controller but at the moment, that's not the way it works. + */ + + if (controller.performAction(SyncActions.SYNCHRONIZE) != OperationState.OK) { + + LOG.info(AaiUiMsgs.INFO_GENERIC, + controller.getControllerName() + " is not idle, sync attempt has been skipped."); + } + } else { + + LOG.info(AaiUiMsgs.INFO_GENERIC, + controller.getControllerName() + " is not idle, sync attempt has been skipped."); + } + + } catch (Exception exception) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, + "Error while attempting synchronization. Error = " + exception.getMessage()); + } + + } + + } + + public SyncControllerService(SyncControllerRegistry syncControllerRegistry, int numRunOnceWorkers, + int numPeriodicWorkers) { + this.syncControllerRegistry = syncControllerRegistry; + this.syncStarted = false; + + UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread thread, Throwable exc) { + LOG.error(AaiUiMsgs.ERROR_GENERIC, thread.getName() + ": " + exc); + } + }; + + runonceSyncExecutor = Executors.newFixedThreadPool(numRunOnceWorkers, + new ThreadFactoryBuilder().setNameFormat("RunonceSyncWorker-%d") + .setUncaughtExceptionHandler(uncaughtExceptionHandler).build()); + + + periodicSyncExecutor = Executors.newScheduledThreadPool(numPeriodicWorkers, + new ThreadFactoryBuilder().setNameFormat("PeriodicSyncWorker-%d") + .setUncaughtExceptionHandler(uncaughtExceptionHandler).build()); + + } + + public SyncControllerRegistry getSyncControllerRegistry() { + return syncControllerRegistry; + } + + public void startSync() { + + long syncInitialDelayInMs = 0; + + for (SyncController controller : syncControllerRegistry.getControllers()) { + + syncInitialDelayInMs = controller.getDelayInMs(); + + if (!controller.isPeriodicSyncEnabled()) { + + if (controller.isRunOnceSyncEnabled()) { + LOG.info(AaiUiMsgs.INFO_GENERIC, controller.getControllerName() + " is enabled."); + runonceSyncExecutor.submit(new SyncControllerTask(controller)); + } else { + LOG.info(AaiUiMsgs.INFO_GENERIC, controller.getControllerName() + " is disabled."); + } + + } else { + + /** + * Do both. We'll take one instance of the SyncController and wrap the object instance into + * two SyncControllerTasks. The responsibility for preventing a conflicting sync should live + * in the SyncController instance. If a sync is underway when the periodic sync kicks in, + * then it will be ignored by the SyncController which is already underway. + * + * The SyncController instance itself would then also be stateful such that it would know + * the last time it ran, and the next time it is supposed to run, the number times a sync + * has executed, etc. + */ + + if (controller.isRunOnceSyncEnabled()) { + LOG.info(AaiUiMsgs.INFO_GENERIC, + controller.getControllerName() + " run-once sync is enabled."); + runonceSyncExecutor.submit(new SyncControllerTask(controller)); + } else { + LOG.info(AaiUiMsgs.INFO_GENERIC, + controller.getControllerName() + " run-once sync is disabled."); + } + + /* + * The controller knows it's configuredfrequency and we can just ask it to tell us what the + * delay and frequency needs to be, rather than trying to calculate the configured frequency + * per controller which "could" be different for each controller. + */ + + if (controller.isPeriodicSyncEnabled()) { + + LOG.info(AaiUiMsgs.INFO_GENERIC, + controller.getControllerName() + " periodic sync is enabled and scheduled to start @ " + + controller.getNextSyncTime()); + + periodicSyncExecutor.scheduleAtFixedRate(new SyncControllerTask(controller), + controller.getDelayInMs(), controller.getSyncFrequencyInMs(), TimeUnit.MILLISECONDS); + + } else { + + LOG.info(AaiUiMsgs.INFO_GENERIC, + controller.getControllerName() + " periodic sync is disabled."); + + } + + } + + } + + } + + public void shutdown() { + + if (runonceSyncExecutor != null) { + runonceSyncExecutor.shutdown(); + } + + if (periodicSyncExecutor != null) { + periodicSyncExecutor.shutdown(); + } + + if (syncControllerRegistry != null) { + for (SyncController controller : syncControllerRegistry.getControllers()) { + controller.shutdown(); + } + } + + } + + @Override + public synchronized void onApplicationEvent(ApplicationContextEvent arg0) { + + /* + * Start sync service processing when spring-context-initialization has finished + */ + + if (!syncStarted) { + syncStarted = true; + startSync(); + } + + } + + +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/config/SynchronizerConstants.java b/src/main/java/org/onap/aai/sparky/sync/SynchronizerConstants.java similarity index 84% rename from src/main/java/org/onap/aai/sparky/synchronizer/config/SynchronizerConstants.java rename to src/main/java/org/onap/aai/sparky/sync/SynchronizerConstants.java index a548c30..73d34bc 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/config/SynchronizerConstants.java +++ b/src/main/java/org/onap/aai/sparky/sync/SynchronizerConstants.java @@ -20,8 +20,9 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.config; +package org.onap.aai.sparky.sync; +import java.text.SimpleDateFormat; import java.util.Date; /** @@ -31,6 +32,13 @@ 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); + public static final SimpleDateFormat SIMPLE_DATE_FORMAT = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + + 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"; // constants for scheduling synchronizer public static final int COMPONENTS_IN_TIMESTAMP = 2; @@ -48,11 +56,6 @@ public final class SynchronizerConstants { 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. */ diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/TaskProcessingStats.java b/src/main/java/org/onap/aai/sparky/sync/TaskProcessingStats.java similarity index 76% rename from src/main/java/org/onap/aai/sparky/synchronizer/TaskProcessingStats.java rename to src/main/java/org/onap/aai/sparky/sync/TaskProcessingStats.java index ef53a75..3e8a0ea 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/TaskProcessingStats.java +++ b/src/main/java/org/onap/aai/sparky/sync/TaskProcessingStats.java @@ -20,10 +20,10 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.sync; import org.onap.aai.sparky.analytics.AbstractStatistics; -import org.onap.aai.sparky.synchronizer.config.TaskProcessorConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; /** * The Class TaskProcessingStats. @@ -41,7 +41,7 @@ public class TaskProcessingStats extends AbstractStatistics { * * @param config the config */ - public TaskProcessingStats(TaskProcessorConfig config) { + public TaskProcessingStats(NetworkStatisticsConfig config) { addHistogram(TASK_AGE_STATS, config.getTaskAgeHistogramLabel(), config.getTaskAgeHistogramMaxYAxis(), config.getTaskAgeHistogramNumBins(), @@ -129,61 +129,5 @@ public class TaskProcessingStats extends AbstractStatistics { } - /** - * @return the tASK_AGE_STATS - */ - public static String getTASK_AGE_STATS() { - return TASK_AGE_STATS; - } - - /** - * @param tASK_AGE_STATS the tASK_AGE_STATS to set - */ - public static void setTASK_AGE_STATS(String tASK_AGE_STATS) { - TASK_AGE_STATS = tASK_AGE_STATS; - } - - /** - * @return the tASK_RESPONSE_STATS - */ - public static String getTASK_RESPONSE_STATS() { - return TASK_RESPONSE_STATS; - } - - /** - * @param tASK_RESPONSE_STATS the tASK_RESPONSE_STATS to set - */ - public static void setTASK_RESPONSE_STATS(String tASK_RESPONSE_STATS) { - TASK_RESPONSE_STATS = tASK_RESPONSE_STATS; - } - - /** - * @return the rESPONSE_SIZE_IN_BYTES - */ - public static String getRESPONSE_SIZE_IN_BYTES() { - return RESPONSE_SIZE_IN_BYTES; - } - - /** - * @param rESPONSE_SIZE_IN_BYTES the rESPONSE_SIZE_IN_BYTES to set - */ - public static void setRESPONSE_SIZE_IN_BYTES(String rESPONSE_SIZE_IN_BYTES) { - RESPONSE_SIZE_IN_BYTES = rESPONSE_SIZE_IN_BYTES; - } - - /** - * @return the tPS - */ - public static String getTPS() { - return TPS; - } - - /** - * @param tPS the tPS to set - */ - public static void setTPS(String tPS) { - TPS = tPS; - } - } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/TransactionRateController.java b/src/main/java/org/onap/aai/sparky/sync/TransactionRateMonitor.java similarity index 61% rename from src/main/java/org/onap/aai/sparky/synchronizer/TransactionRateController.java rename to src/main/java/org/onap/aai/sparky/sync/TransactionRateMonitor.java index 76deef3..a120661 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/TransactionRateController.java +++ b/src/main/java/org/onap/aai/sparky/sync/TransactionRateMonitor.java @@ -20,39 +20,28 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.sync; import java.util.concurrent.atomic.AtomicInteger; import org.onap.aai.sparky.analytics.AveragingRingBuffer; -import org.onap.aai.sparky.synchronizer.config.TaskProcessorConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; -/** - * TODO: Fill in description. - * - * @author davea. - */ -public class TransactionRateController { +public class TransactionRateMonitor { + private AtomicInteger numTransactions; 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) { + public TransactionRateMonitor(int numWorkerThreads, NetworkStatisticsConfig config) { - this.config = config; this.responseTimeTracker = new AveragingRingBuffer( - config.getNumSamplesPerThreadForRunningAverage() * config.getMaxConcurrentWorkers()); - this.msPerTransaction = 1000 / config.getTargetTps(); - this.numThreads = config.getMaxConcurrentWorkers(); + config.getNumSamplesPerThreadForRunningAverage() * numWorkerThreads); this.startTimeInMs = System.currentTimeMillis(); this.numTransactions = new AtomicInteger(0); } @@ -67,32 +56,6 @@ public class TransactionRateController { 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(); } diff --git a/src/main/java/org/onap/aai/sparky/search/SuggestionList.java b/src/main/java/org/onap/aai/sparky/sync/config/ElasticSearchEndpointConfig.java similarity index 50% rename from src/main/java/org/onap/aai/sparky/search/SuggestionList.java rename to src/main/java/org/onap/aai/sparky/sync/config/ElasticSearchEndpointConfig.java index 5548ffb..6bea1a4 100644 --- a/src/main/java/org/onap/aai/sparky/search/SuggestionList.java +++ b/src/main/java/org/onap/aai/sparky/sync/config/ElasticSearchEndpointConfig.java @@ -20,51 +20,51 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.search; +package org.onap.aai.sparky.sync.config; -import java.util.LinkedList; -import java.util.List; +public class ElasticSearchEndpointConfig { -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<>(); + private String esIpAddress; + private String esServerPort; + private int scrollContextTimeToLiveInMinutes; + private int scrollContextBatchRequestSize; + + public ElasticSearchEndpointConfig() { - public void addSuggestion(Suggestion suggestion) { - suggestions.add(suggestion); } - public List getSuggestions() { - return suggestions; + public String getEsIpAddress() { + return esIpAddress; } - public void setSuggestions(List suggestions) { - this.suggestions = suggestions; + public void setEsIpAddress(String esIpAddress) { + this.esIpAddress = esIpAddress; } - public Long getProcessingTimeInMs() { - return processingTimeInMs; + public String getEsServerPort() { + return esServerPort; } - public Long getTotalFound() { - return totalFound; + public void setEsServerPort(String esServerPort) { + this.esServerPort = esServerPort; } - public Long getNumReturned() { - return numReturned; + public int getScrollContextTimeToLiveInMinutes() { + return scrollContextTimeToLiveInMinutes; } - public void setProcessingTimeInMs(Long processingTimeInMs) { - this.processingTimeInMs = processingTimeInMs; + public void setScrollContextTimeToLiveInMinutes(int scrollContextTimeToLiveInMinutes) { + this.scrollContextTimeToLiveInMinutes = scrollContextTimeToLiveInMinutes; } - public void setTotalFound(Long totalFound) { - this.totalFound = totalFound; + public int getScrollContextBatchRequestSize() { + return scrollContextBatchRequestSize; } - public void setNumReturned(Long numReturned) { - this.numReturned = numReturned; + public void setScrollContextBatchRequestSize(int scrollContextBatchRequestSize) { + this.scrollContextBatchRequestSize = scrollContextBatchRequestSize; } + + + } diff --git a/src/main/java/org/onap/aai/sparky/sync/config/ElasticSearchSchemaConfig.java b/src/main/java/org/onap/aai/sparky/sync/config/ElasticSearchSchemaConfig.java new file mode 100644 index 0000000..1e4ba15 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/config/ElasticSearchSchemaConfig.java @@ -0,0 +1,75 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync.config; + +public class ElasticSearchSchemaConfig { + + private String indexName; + private String indexDocType; + private String indexSettingsFileName; + private String indexMappingsFileName; + + public String getIndexName() { + return indexName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + public String getIndexDocType() { + return indexDocType; + } + + public void setIndexDocType(String indexDocType) { + this.indexDocType = indexDocType; + } + + public String getIndexSettingsFileName() { + return indexSettingsFileName; + } + + public void setIndexSettingsFileName(String indexSettingsFileName) { + this.indexSettingsFileName = indexSettingsFileName; + } + + public String getIndexMappingsFileName() { + return indexMappingsFileName; + } + + public void setIndexMappingsFileName(String indexMappingsFileName) { + this.indexMappingsFileName = indexMappingsFileName; + } + + @Override + public String toString() { + return "ElasticSearchSchemaConfig [" + + (indexName != null ? "indexName=" + indexName + ", " : "") + + (indexDocType != null ? "indexDocType=" + indexDocType + ", " : "") + + (indexSettingsFileName != null ? "indexSettingsFileName=" + indexSettingsFileName + ", " + : "") + + (indexMappingsFileName != null ? "indexMappingsFileName=" + indexMappingsFileName : "") + + "]"; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/config/NetworkStatisticsConfig.java b/src/main/java/org/onap/aai/sparky/sync/config/NetworkStatisticsConfig.java similarity index 99% rename from src/main/java/org/onap/aai/sparky/synchronizer/config/NetworkStatisticsConfig.java rename to src/main/java/org/onap/aai/sparky/sync/config/NetworkStatisticsConfig.java index 31c8acd..34de88b 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/config/NetworkStatisticsConfig.java +++ b/src/main/java/org/onap/aai/sparky/sync/config/NetworkStatisticsConfig.java @@ -20,7 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.config; +package org.onap.aai.sparky.sync.config; public class NetworkStatisticsConfig { diff --git a/src/main/java/org/onap/aai/sparky/sync/config/SyncControllerConfig.java b/src/main/java/org/onap/aai/sparky/sync/config/SyncControllerConfig.java new file mode 100644 index 0000000..eb3a73f --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/config/SyncControllerConfig.java @@ -0,0 +1,303 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync.config; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.onap.aai.sparky.sync.SynchronizerConstants; + +public class SyncControllerConfig { + + private static final String UNKNOWN_CONTROLLER_NAME = "UnknownControllerName"; + + private String controllerName; + private boolean enabled; + private int syncTaskDelayInMs; + private int syncTaskFrequencyInDays; + + private int numSyncControllerWorkers; + private boolean runOnceSyncEnabled; + private boolean periodicSyncEnabled; + + private String targetSyncStartTimeStamp; + + private int numInternalSyncWorkers; + private int numSyncElasticWorkers; + private int numSyncActiveInventoryWorkers; + + /* + * calculated variables based on incoming config + */ + private String timeZoneOfSyncStartTimeStamp; + private int syncTaskStartTimeHr; + private int syncTaskStartTimeMin; + private int syncTaskStartTimeSec; + + + + public SyncControllerConfig() { + controllerName = UNKNOWN_CONTROLLER_NAME; + enabled = false; + syncTaskDelayInMs = 0; + syncTaskFrequencyInDays = 365; + numSyncControllerWorkers = 1; + runOnceSyncEnabled = false; + periodicSyncEnabled = false; + targetSyncStartTimeStamp = SynchronizerConstants.DEFAULT_START_TIMESTAMP; + numInternalSyncWorkers = 2; + numSyncElasticWorkers = 5; + numSyncActiveInventoryWorkers = 5; + } + + protected void initializeSyncTimeParameters() { + + if (syncTaskDelayInMs < 0) { + throw new IllegalArgumentException("syncTaskDelayInMs must >= 0"); + } + + Pattern pattern = Pattern.compile(SynchronizerConstants.TIMESTAMP24HOURS_PATTERN); + Matcher matcher = pattern.matcher(targetSyncStartTimeStamp); + if (!matcher.matches()) { + throw new IllegalArgumentException("Invalid time format for targetSyncStartTimeStamp"); + } + + List timestampVal = Arrays.asList(targetSyncStartTimeStamp.split(" ")); + + if (timestampVal.size() == SynchronizerConstants.COMPONENTS_IN_TIMESTAMP) { + + // Need both time and timezone offset + timeZoneOfSyncStartTimeStamp = 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 = null; + + try { + date = format.parse(time); + } catch (ParseException parseException) { + throw new IllegalArgumentException(parseException); + } + + 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 { + throw new IllegalArgumentException("Invalid timestamp format from targetSyncStartTimeStamp"); + } + + } + + + public int getNumInternalSyncWorkers() { + return numInternalSyncWorkers; + } + + public void setNumInternalSyncWorkers(int numInternalSyncWorkers) { + this.numInternalSyncWorkers = numInternalSyncWorkers; + } + + public int getNumSyncElasticWorkers() { + return numSyncElasticWorkers; + } + + public void setNumSyncElasticWorkers(int numSyncElasticWorkers) { + this.numSyncElasticWorkers = numSyncElasticWorkers; + } + + public int getNumSyncActiveInventoryWorkers() { + return numSyncActiveInventoryWorkers; + } + + public void setNumSyncActiveInventoryWorkers(int numSyncActiveInventoryWorkers) { + this.numSyncActiveInventoryWorkers = numSyncActiveInventoryWorkers; + } + + public String getTargetSyncStartTimeStamp() { + return targetSyncStartTimeStamp; + } + + public void setTargetSyncStartTimeStamp(String targetSyncStartTimeStamp) { + this.targetSyncStartTimeStamp = targetSyncStartTimeStamp; + initializeSyncTimeParameters(); + } + + public String getControllerName() { + return controllerName; + } + + public void setControllerName(String controllerName) { + this.controllerName = controllerName; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getSyncTaskDelayInMs() { + return syncTaskDelayInMs; + } + + public void setSyncTaskDelayInMs(int syncTaskDelayInMs) { + this.syncTaskDelayInMs = syncTaskDelayInMs; + } + + public int getSyncTaskFrequencyInDays() { + return syncTaskFrequencyInDays; + } + + public void setSyncTaskFrequencyInDays(int syncTaskFrequencyInDays) { + this.syncTaskFrequencyInDays = syncTaskFrequencyInDays; + } + + public int getNumSyncControllerWorkers() { + return numSyncControllerWorkers; + } + + public void setNumSyncControllerWorkers(int numSyncControllerWorkers) { + this.numSyncControllerWorkers = numSyncControllerWorkers; + } + + public boolean isRunOnceSyncEnabled() { + return runOnceSyncEnabled; + } + + public void setRunOnceSyncEnabled(boolean runOnceSyncEnabled) { + this.runOnceSyncEnabled = runOnceSyncEnabled; + } + + public boolean isPeriodicSyncEnabled() { + return periodicSyncEnabled; + } + + public void setPeriodicSyncEnabled(boolean periodicSyncEnabled) { + this.periodicSyncEnabled = periodicSyncEnabled; + } + + public long getSyncFrequencyInMs() { + + return (syncTaskFrequencyInDays * SynchronizerConstants.MILLISEC_IN_A_DAY); + + } + + public Calendar getTargetSyncTime() { + + TimeZone tz = TimeZone.getTimeZone(timeZoneOfSyncStartTimeStamp); + Calendar targetSyncTime = Calendar.getInstance(tz); + + targetSyncTime.set(Calendar.HOUR_OF_DAY, syncTaskStartTimeHr); + targetSyncTime.set(Calendar.MINUTE, syncTaskStartTimeMin); + targetSyncTime.set(Calendar.SECOND, syncTaskStartTimeSec); + + return targetSyncTime; + + } + + + public String getNextSyncTime() { + + int taskFrequencyInSeconds = 0; + if (getSyncFrequencyInMs() > 0) { + taskFrequencyInSeconds = (int) (getSyncFrequencyInMs() / 1000); + } + + if (taskFrequencyInSeconds < 86400) { + + TimeZone tz = TimeZone.getTimeZone(timeZoneOfSyncStartTimeStamp); + Calendar targetSyncTime = Calendar.getInstance(tz); + targetSyncTime.add(Calendar.SECOND, taskFrequencyInSeconds); + + return SynchronizerConstants.SIMPLE_DATE_FORMAT.format(targetSyncTime.getTimeInMillis()) + .replaceAll(SynchronizerConstants.TIME_STD, SynchronizerConstants.TIME_CONFIG_STD); + + } else { + + return SynchronizerConstants.SIMPLE_DATE_FORMAT + .format(getNextSyncTime(getTargetSyncTime(), taskFrequencyInSeconds)) + .replaceAll(SynchronizerConstants.TIME_STD, SynchronizerConstants.TIME_CONFIG_STD); + + } + + } + + public long getNextSyncTime(Calendar syncTime, int taskFrequencyInSeconds) { + + TimeZone tz = TimeZone.getTimeZone(timeZoneOfSyncStartTimeStamp); + Calendar timeNow = Calendar.getInstance(tz); + + return getNextSyncTime(syncTime, timeNow.getTimeInMillis(), taskFrequencyInSeconds); + } + + /** + * Gets the first sync time. + * + * @param calendar the calendar + * @param timeNow the time now in ms + * @param taskFrequencyInMs task period in ms + * @return the first sync time + */ + + public long getNextSyncTime(Calendar syncTime, long timeNowInMs, int taskFrequencyInSeconds) { + if (taskFrequencyInSeconds == 0) { + return 0; + } else if (timeNowInMs > syncTime.getTimeInMillis()) { + + /* + * If current time is after the scheduled sync start time, then we'll skip ahead to the next + * sync time period + */ + + syncTime.add(Calendar.SECOND, taskFrequencyInSeconds); + } + + return syncTime.getTimeInMillis(); + } + + public String getTimeZoneOfSyncStartTimeStamp() { + return timeZoneOfSyncStartTimeStamp; + } + + public void setTimeZoneOfSyncStartTimeStamp(String timeZoneOfSyncStartTimeStamp) { + this.timeZoneOfSyncStartTimeStamp = timeZoneOfSyncStartTimeStamp; + } + + + +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/AggregationEntity.java b/src/main/java/org/onap/aai/sparky/sync/entity/AggregationEntity.java similarity index 78% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/AggregationEntity.java rename to src/main/java/org/onap/aai/sparky/sync/entity/AggregationEntity.java index b2958b1..c4f805e 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/AggregationEntity.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/AggregationEntity.java @@ -20,12 +20,11 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; +package org.onap.aai.sparky.sync.entity; import java.util.HashMap; import java.util.Map; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; import org.onap.aai.sparky.util.NodeUtils; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,19 +44,10 @@ public class AggregationEntity extends IndexableEntity implements IndexDocument super(); } - /** - * Instantiates a new aggregation entity. - * - * @param loader the loader - */ - public AggregationEntity(OxmModelLoader loader) { - super(loader); - } - /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.entity.IndexDocument#deriveFields() + * @see org.openecomp.sparky.synchronizer.entity.IndexDocument#deriveFields() */ @Override public void deriveFields() { @@ -86,7 +76,7 @@ public class AggregationEntity extends IndexableEntity implements IndexDocument } @Override - public String getIndexDocumentJson() { + public String getAsJson() { ObjectNode rootNode = mapper.createObjectNode(); rootNode.put("link", this.getLink()); rootNode.put("lastmodTimestamp", this.getEntityTimeStamp()); @@ -96,40 +86,6 @@ public class AggregationEntity extends IndexableEntity implements IndexDocument return rootNode.toString(); } - /** - * @return the attributes - */ - public Map getAttributes() { - return attributes; - } - - /** - * @param attributes the attributes to set - */ - public void setAttributes(Map attributes) { - this.attributes = attributes; - } - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } - - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } - - @Override - public ObjectNode getBulkImportEntity() { - // TODO Auto-generated method stub - return null; - } - /* * (non-Javadoc) * diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/AggregationSuggestionEntity.java b/src/main/java/org/onap/aai/sparky/sync/entity/AggregationSuggestionEntity.java similarity index 66% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/AggregationSuggestionEntity.java rename to src/main/java/org/onap/aai/sparky/sync/entity/AggregationSuggestionEntity.java index 412798e..9ee6365 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/AggregationSuggestionEntity.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/AggregationSuggestionEntity.java @@ -20,59 +20,29 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; +package org.onap.aai.sparky.sync.entity; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONObject; +import org.onap.aai.sparky.search.filters.config.FiltersConfig; +import org.onap.aai.sparky.search.filters.config.UiFilterListItemConfig; +import org.onap.aai.sparky.search.filters.config.UiViewListItemConfig; import org.onap.aai.sparky.util.NodeUtils; -public class AggregationSuggestionEntity extends IndexableEntity implements IndexDocument { - - private List inputs = new ArrayList(); - - /** - * @return the inputs - */ - public List getInputs() { - return inputs; - } - - /** - * @param inputs the inputs to set - */ - public void setInputs(List inputs) { - this.inputs = inputs; - } - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } +import com.fasterxml.jackson.databind.ObjectMapper; - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } +public class AggregationSuggestionEntity extends IndexableEntity implements IndexDocument { - /** - * @return the outputString - */ - public String getOutputString() { - return outputString; - } + private static final String FILTER_ID = "filterId"; + private static final String FILTER_LIST = "filterList"; + private List inputs = new ArrayList<>(); private final String outputString = "VNFs"; protected ObjectMapper mapper = new ObjectMapper(); + List filterIds = new ArrayList<>(); public AggregationSuggestionEntity() { super(); @@ -86,8 +56,7 @@ public class AggregationSuggestionEntity extends IndexableEntity implements Inde } @Override - public String getIndexDocumentJson() { - + public String getAsJson() { JSONArray inputArray = new JSONArray(); for (String input : inputs) { input = input.replace(",", ""); @@ -101,7 +70,16 @@ public class AggregationSuggestionEntity extends IndexableEntity implements Inde entitySuggest.put("output", this.outputString); entitySuggest.put("weight", 100); + JSONArray payloadFilters = new JSONArray(); + + for (String filterId : filterIds) { + JSONObject filterPayload = new JSONObject(); + filterPayload.put(FILTER_ID, filterId); + payloadFilters.put(filterPayload); + } + JSONObject payloadNode = new JSONObject(); + payloadNode.put(FILTER_LIST, payloadFilters); entitySuggest.put("payload", payloadNode); JSONObject rootNode = new JSONObject(); @@ -110,10 +88,17 @@ public class AggregationSuggestionEntity extends IndexableEntity implements Inde return rootNode.toString(); } - @Override - public ObjectNode getBulkImportEntity() { - // TODO Auto-generated method stub - return null; + public void initializeFilters() { + for (UiViewListItemConfig view : FiltersConfig.getInstance().getViewsConfig().getViews()) { + if (view.getViewName().equals("vnfSearch")) { + for (UiFilterListItemConfig currentViewFilter : view.getFilters()) { + filterIds.add(currentViewFilter.getFilterId()); + } + } + } } + public void setFilterIds(List filterIds) { + this.filterIds = filterIds; + } } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexDocument.java b/src/main/java/org/onap/aai/sparky/sync/entity/IndexDocument.java similarity index 86% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexDocument.java rename to src/main/java/org/onap/aai/sparky/sync/entity/IndexDocument.java index 0633da4..f7818a4 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexDocument.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/IndexDocument.java @@ -20,9 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; - -import com.fasterxml.jackson.databind.node.ObjectNode; +package org.onap.aai.sparky.sync.entity; /** * The Interface IndexDocument. @@ -34,9 +32,8 @@ public interface IndexDocument { */ public void deriveFields(); - public String getIndexDocumentJson(); - public String getId(); - public ObjectNode getBulkImportEntity(); + public String getAsJson() throws Exception; + } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexableCrossEntityReference.java b/src/main/java/org/onap/aai/sparky/sync/entity/IndexableCrossEntityReference.java similarity index 55% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexableCrossEntityReference.java rename to src/main/java/org/onap/aai/sparky/sync/entity/IndexableCrossEntityReference.java index 3c454f6..cef7bfe 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexableCrossEntityReference.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/IndexableCrossEntityReference.java @@ -20,15 +20,14 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; +package org.onap.aai.sparky.sync.entity; import java.util.ArrayList; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; import org.onap.aai.sparky.util.NodeUtils; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; /** @@ -38,49 +37,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; public class IndexableCrossEntityReference extends IndexableEntity implements IndexDocument { protected String crossReferenceEntityValues; - - /** - * @return the crossReferenceEntityValues - */ - public String getCrossReferenceEntityValues() { - return crossReferenceEntityValues; - } - - /** - * @param crossReferenceEntityValues the crossReferenceEntityValues to set - */ - public void setCrossReferenceEntityValues(String crossReferenceEntityValues) { - this.crossReferenceEntityValues = crossReferenceEntityValues; - } - - /** - * @return the crossEntityReferenceCollection - */ - public ArrayList getCrossEntityReferenceCollection() { - return crossEntityReferenceCollection; - } - - /** - * @param crossEntityReferenceCollection the crossEntityReferenceCollection to set - */ - public void setCrossEntityReferenceCollection(ArrayList crossEntityReferenceCollection) { - this.crossEntityReferenceCollection = crossEntityReferenceCollection; - } - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } - - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } - protected ArrayList crossEntityReferenceCollection = new ArrayList(); protected ObjectMapper mapper = new ObjectMapper(); @@ -91,15 +47,6 @@ public class IndexableCrossEntityReference extends IndexableEntity implements In super(); } - /** - * Instantiates a new indexable cross entity reference. - * - * @param loader the loader - */ - public IndexableCrossEntityReference(OxmModelLoader loader) { - super(loader); - } - /** * Adds the cross entity reference value. * @@ -111,10 +58,18 @@ public class IndexableCrossEntityReference extends IndexableEntity implements In } } + public String getCrossReferenceEntityValues() { + return crossReferenceEntityValues; + } + + public void setCrossReferenceEntityValues(String crossReferenceEntityValues) { + this.crossReferenceEntityValues = crossReferenceEntityValues; + } + /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.entity.IndexDocument#deriveFields() + * @see org.openecomp.sparky.synchronizer.entity.IndexDocument#deriveFields() */ @Override public void deriveFields() { @@ -123,27 +78,12 @@ public class IndexableCrossEntityReference extends IndexableEntity implements In } @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(); - } + public String getAsJson() throws JsonProcessingException { + + return NodeUtils.convertObjectToJson(this, false); - @Override - public ObjectNode getBulkImportEntity() { - // TODO Auto-generated method stub - return null; } - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ @Override public String toString() { return "IndexableCrossEntityReference [" @@ -151,13 +91,9 @@ public class IndexableCrossEntityReference extends IndexableEntity implements In ? "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 : "") - + "]"; + + (mapper != null ? "mapper=" + mapper : "") + "]"; } + + } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexableEntity.java b/src/main/java/org/onap/aai/sparky/sync/entity/IndexableEntity.java similarity index 72% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexableEntity.java rename to src/main/java/org/onap/aai/sparky/sync/entity/IndexableEntity.java index deeac35..5ee9a9f 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/IndexableEntity.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/IndexableEntity.java @@ -20,12 +20,13 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; +package org.onap.aai.sparky.sync.entity; import java.sql.Timestamp; import java.text.SimpleDateFormat; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; /** * The Class IndexableEntity. @@ -35,44 +36,7 @@ public abstract class IndexableEntity { protected String entityType; protected String entityPrimaryKeyValue; protected String lastmodTimestamp; - - /** - * @return the lastmodTimestamp - */ - public String getLastmodTimestamp() { - return lastmodTimestamp; - } - - /** - * @param lastmodTimestamp the lastmodTimestamp to set - */ - public void setLastmodTimestamp(String lastmodTimestamp) { - this.lastmodTimestamp = lastmodTimestamp; - } - - /** - * @return the loader - */ - public OxmModelLoader getLoader() { - return loader; - } - - /** - * @param loader the loader to set - */ - public void setLoader(OxmModelLoader loader) { - this.loader = loader; - } - - /** - * @return the timestampFormat - */ - public static String getTimestampFormat() { - return TIMESTAMP_FORMAT; - } - protected String link; - protected OxmModelLoader loader; private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; @@ -86,28 +50,22 @@ public abstract class IndexableEntity { this.setEntityTimeStamp(currentFormattedTimeStamp); } - /** - * Instantiates a new indexable entity. - * - * @param loader the loader - */ - public IndexableEntity(OxmModelLoader loader) { - this(); - this.loader = loader; - } - + @JsonIgnore public String getId() { return id; } + @JsonProperty("entityType") public String getEntityType() { return entityType; } + @JsonProperty("entityPrimaryKeyValue") public String getEntityPrimaryKeyValue() { return entityPrimaryKeyValue; } + @JsonProperty("lastmodTimestamp") public String getEntityTimeStamp() { return lastmodTimestamp; } @@ -128,6 +86,7 @@ public abstract class IndexableEntity { this.lastmodTimestamp = lastmodTimestamp; } + @JsonProperty("link") public String getLink() { return link; } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/MergableEntity.java b/src/main/java/org/onap/aai/sparky/sync/entity/MergableEntity.java similarity index 87% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/MergableEntity.java rename to src/main/java/org/onap/aai/sparky/sync/entity/MergableEntity.java index f998872..10036b3 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/MergableEntity.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/MergableEntity.java @@ -20,27 +20,20 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; +package org.onap.aai.sparky.sync.entity; import java.util.HashMap; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; + /** * The Class MergableEntity. */ public class MergableEntity { private Map other = new HashMap(); - /** - * @param other the other to set - */ - public void setOther(Map other) { - this.other = other; - } - /** * Any. * @@ -51,10 +44,6 @@ public class MergableEntity { return other; } - public Map getOther() { - return other; - } - /** * Sets the. * diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/ObjectIdCollection.java b/src/main/java/org/onap/aai/sparky/sync/entity/ObjectIdCollection.java similarity index 80% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/ObjectIdCollection.java rename to src/main/java/org/onap/aai/sparky/sync/entity/ObjectIdCollection.java index 158cb1d..217dcdf 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/ObjectIdCollection.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/ObjectIdCollection.java @@ -20,7 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; +package org.onap.aai.sparky.sync.entity; import java.util.Collection; import java.util.List; @@ -34,21 +34,7 @@ public class ObjectIdCollection { protected ConcurrentHashMap importedObjectIds = new ConcurrentHashMap(); - /** - * @return the importedObjectIds - */ - public ConcurrentHashMap getImportedObjectIds() { - return importedObjectIds; - } - - /** - * @param importedObjectIds the importedObjectIds to set - */ - public void setImportedObjectIds(ConcurrentHashMap importedObjectIds) { - this.importedObjectIds = importedObjectIds; - } - - public Collection getImportedObjectIdsAsValues() { + public Collection getImportedObjectIds() { return importedObjectIds.values(); } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/SearchableEntity.java b/src/main/java/org/onap/aai/sparky/sync/entity/SearchableEntity.java similarity index 68% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/SearchableEntity.java rename to src/main/java/org/onap/aai/sparky/sync/entity/SearchableEntity.java index 08a80ea..dd52bd2 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/SearchableEntity.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/SearchableEntity.java @@ -20,66 +20,30 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; +package org.onap.aai.sparky.sync.entity; import java.util.ArrayList; import java.util.List; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; import org.onap.aai.sparky.util.NodeUtils; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * The Class SearchableEntity. */ public class SearchableEntity extends IndexableEntity implements IndexDocument { - protected List searchTagCollection = new ArrayList(); - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } - - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } - - /** - * @param searchTagCollection the searchTagCollection to set - */ - public void setSearchTagCollection(List searchTagCollection) { - this.searchTagCollection = searchTagCollection; - } - - /** - * @param searchTagIdCollection the searchTagIdCollection to set - */ - public void setSearchTagIdCollection(List searchTagIdCollection) { - this.searchTagIdCollection = searchTagIdCollection; - } - /** - * @param searchTags the searchTags to set - */ - public void setSearchTags(String searchTags) { - this.searchTags = searchTags; - } - - /** - * @param searchTagIDs the searchTagIDs to set - */ - public void setSearchTagIDs(String searchTagIDs) { - this.searchTagIDs = searchTagIDs; - } + @JsonIgnore + protected List searchTagCollection = new ArrayList(); + @JsonIgnore protected List searchTagIdCollection = new ArrayList(); + + @JsonIgnore protected ObjectMapper mapper = new ObjectMapper(); /** @@ -89,19 +53,12 @@ public class SearchableEntity extends IndexableEntity implements IndexDocument { 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; /** @@ -114,7 +71,7 @@ public class SearchableEntity extends IndexableEntity implements IndexDocument { /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.entity.IndexDocument#deriveFields() + * @see org.openecomp.sparky.synchronizer.entity.IndexDocument#deriveFields() */ @Override public void deriveFields() { @@ -145,34 +102,25 @@ public class SearchableEntity extends IndexableEntity implements IndexDocument { return searchTagCollection; } + @JsonProperty("searchTags") public String getSearchTags() { return searchTags; } + @JsonProperty("searchTagIDs") public String getSearchTagIDs() { return searchTagIDs; } + @JsonIgnore 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; + @JsonIgnore + public String getAsJson() throws JsonProcessingException { + return NodeUtils.convertObjectToJson(this, false); } /* diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/SelfLinkDescriptor.java b/src/main/java/org/onap/aai/sparky/sync/entity/SelfLinkDescriptor.java similarity index 98% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/SelfLinkDescriptor.java rename to src/main/java/org/onap/aai/sparky/sync/entity/SelfLinkDescriptor.java index 20e59ef..9d2886e 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/SelfLinkDescriptor.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/SelfLinkDescriptor.java @@ -20,7 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; +package org.onap.aai.sparky.sync.entity; /** * The Class SelfLinkDescriptor. diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/SuggestionSearchEntity.java b/src/main/java/org/onap/aai/sparky/sync/entity/SuggestionSearchEntity.java similarity index 51% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/SuggestionSearchEntity.java rename to src/main/java/org/onap/aai/sparky/sync/entity/SuggestionSearchEntity.java index d699031..fdabf86 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/SuggestionSearchEntity.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/SuggestionSearchEntity.java @@ -20,11 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; +package org.onap.aai.sparky.sync.entity; import java.util.ArrayList; import java.util.Arrays; @@ -34,117 +30,82 @@ import java.util.Map; import org.json.JSONArray; import org.json.JSONObject; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; +import org.onap.aai.sparky.config.oxm.SuggestionEntityLookup; +import org.onap.aai.sparky.search.filters.config.FiltersConfig; +import org.onap.aai.sparky.search.filters.config.FiltersDetailsConfig; +import org.onap.aai.sparky.search.filters.config.UiFilterConfig; import org.onap.aai.sparky.util.NodeUtils; +import org.onap.aai.sparky.util.SuggestionsPermutation; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; public class SuggestionSearchEntity extends IndexableEntity implements IndexDocument { + private static final String FILTER_ID = "filterId"; + private static final String FILTER_VALUE = "filterValue"; + private static final String FILTER_LIST = "filterList"; private String entityType; private List suggestionConnectorWords = new ArrayList(); private List suggestionAttributeTypes = new ArrayList(); - - /** - * @return the suggestionAttributeTypes - */ - public List getSuggestionAttributeTypes() { - return suggestionAttributeTypes; - } - - /** - * @param suggestionAttributeTypes the suggestionAttributeTypes to set - */ - public void setSuggestionAttributeTypes(List suggestionAttributeTypes) { - this.suggestionAttributeTypes = suggestionAttributeTypes; - } - - /** - * @return the suggestionTypeAliases - */ - public List getSuggestionTypeAliases() { - return suggestionTypeAliases; - } - - /** - * @param suggestionTypeAliases the suggestionTypeAliases to set - */ - public void setSuggestionTypeAliases(List suggestionTypeAliases) { - this.suggestionTypeAliases = suggestionTypeAliases; - } - - /** - * @return the suggestableAttr - */ - public List getSuggestableAttr() { - return suggestableAttr; - } - - /** - * @param suggestableAttr the suggestableAttr to set - */ - public void setSuggestableAttr(List suggestableAttr) { - this.suggestableAttr = suggestableAttr; - } - - /** - * @return the outputString - */ - public StringBuffer getOutputString() { - return outputString; - } - - /** - * @param outputString the outputString to set - */ - public void setOutputString(StringBuffer outputString) { - this.outputString = outputString; - } - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } - - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } - - 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 Map inputOutputData = new HashMap(); + Map filters = new HashMap(); + private JSONObject payload = new JSONObject(); + private JSONArray payloadFilters = new JSONArray(); private StringBuffer outputString = new StringBuffer(); private String aliasToUse; - public Map getPayload() { + private SuggestionEntityLookup entityLookup; + + public JSONObject getPayload() { return payload; } - public void setPayload(Map payload) { + public void setPayload(JSONObject payload) { this.payload = payload; } + protected ObjectMapper mapper = new ObjectMapper(); - public JSONObject getPayloadJsonNode() { - return payloadJsonNode; - } + public SuggestionSearchEntity() { + super(); - public void setPayloadJsonNode(JSONObject payloadJsonNode) { - this.payloadJsonNode = payloadJsonNode; + FiltersDetailsConfig filterConfigList = FiltersConfig.getInstance().getFiltersConfig(); + // Populate the map with keys that will match the suggestableAttr values + for (UiFilterConfig filter : filterConfigList.getFilters()) { + if (filter.getDataSource() != null) { + filters.put(filter.getDataSource().getFieldName(), filter); + } + } } + public SuggestionSearchEntity(SuggestionEntityLookup entityLookup) { - protected ObjectMapper mapper = new ObjectMapper(); + this.entityLookup = entityLookup; - public SuggestionSearchEntity() { - super(); + FiltersDetailsConfig filterConfigList = FiltersConfig.getInstance().getFiltersConfig(); + // Populate the map with keys that will match the suggestableAttr values + for (UiFilterConfig filter : filterConfigList.getFilters()) { + if (filter.getDataSource() != null) { + filters.put(filter.getDataSource().getFieldName(), filter); + } + } + } + + public SuggestionSearchEntity(SuggestionEntityLookup entityLookup, FiltersConfig config) { + + FiltersDetailsConfig filterConfigList = config.getFiltersConfig(); + // Populate the map with keys that will match the suggestableAttr values + for (UiFilterConfig filter : filterConfigList.getFilters()) { + if (filter.getDataSource() != null) { + filters.put(filter.getDataSource().getFieldName(), filter); + } + } } public void setSuggestableAttr(ArrayList attributes) { @@ -154,20 +115,67 @@ public class SuggestionSearchEntity extends IndexableEntity implements IndexDocu } public void setPayloadFromResponse(JsonNode node) { - Map nodePayload = new HashMap(); if (suggestableAttr != null) { + JSONObject nodePayload = new JSONObject(); for (String attribute : suggestableAttr) { if (node.get(attribute) != null) { - nodePayload.put(attribute, node.get(attribute).asText()); + inputOutputData.put(attribute, node.get(attribute).asText()); + this.payload.put(attribute, node.get(attribute).asText()); } } - this.setPayload(nodePayload); } } + public void setFilterBasedPayloadFromResponse(JsonNode node, String entityName, + ArrayList uniqueList) { + + HashMap desc = entityLookup.getSuggestionSearchEntityOxmModel().get(entityName); + + if (desc == null) { + return; + } + + String attr = desc.get("suggestibleAttributes"); + + if (attr == null) { + return; + } + + List suggestableAttrOxm = Arrays.asList(attr.split(",")); - public SuggestionSearchEntity(OxmModelLoader loader) { - super(loader); + /* + * Note: (1) 'uniqueList' is one item within the power set of the suggestable attributes. (2) + * 'inputeOutputData' is used to generate permutations of strings + */ + for (String selectiveAttr : uniqueList) { + if (node.get(selectiveAttr) != null) { + inputOutputData.put(selectiveAttr, node.get(selectiveAttr).asText()); + } + } + + if (suggestableAttrOxm != null) { + for (String attribute : suggestableAttrOxm) { + if (node.get(attribute) != null && uniqueList.contains(attribute)) { + UiFilterConfig filterConfig = filters.get(attribute); + if (filterConfig != null) { + JSONObject filterPayload = new JSONObject(); + filterPayload.put(FILTER_ID, filterConfig.getFilterId()); + filterPayload.put(FILTER_VALUE, node.get(attribute).asText()); + this.payloadFilters.put(filterPayload); + } else { + this.payload.put(attribute, node.get(attribute).asText()); + } + } else { + UiFilterConfig emptyValueFilterConfig = filters.get(attribute); + if (emptyValueFilterConfig != null) { + JSONObject emptyValueFilterPayload = new JSONObject(); + emptyValueFilterPayload.put(FILTER_ID, emptyValueFilterConfig.getFilterId()); + this.payloadFilters.put(emptyValueFilterPayload); + } + } + } + this.payload.put(FILTER_LIST, this.payloadFilters); + } } @Override @@ -222,10 +230,10 @@ public class SuggestionSearchEntity extends IndexableEntity implements IndexDocu public void generateSuggestionInputPermutations() { - List entityNames = new ArrayList<>(); entityNames.add(entityType); - HashMap desc = loader.getOxmModel().get(this.entityType); + HashMap desc = + entityLookup.getSuggestionSearchEntityOxmModel().get(this.entityType); String attr = desc.get("suggestionAliases"); String[] suggestionAliasesArray = attr.split(","); suggestionTypeAliases = Arrays.asList(suggestionAliasesArray); @@ -233,72 +241,39 @@ public class SuggestionSearchEntity extends IndexableEntity implements IndexDocu 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; - } + ArrayList listToPermutate = new ArrayList<>(inputOutputData.values()); - /** - * 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(" "); + for (String entity : entityNames) { + listToPermutate.add(entity); // add entity-name or alias in list to permutate + List> lists = SuggestionsPermutation.getListPermutations(listToPermutate); + for (List li : lists) { + suggestionInputPermutations.add(String.join(" ", li)); } - - 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); + // prepare for the next pass: remove the entity-name or alias from the list + listToPermutate.remove(entity); } } public boolean isSuggestableDoc() { - return this.getPayload().size() != 0; + return this.getPayload().length() != 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()) { + int entryCounter = 1; + for (Map.Entry outputValue : inputOutputData.entrySet()) { + if (outputValue.getValue() != null && outputValue.getValue().length() > 0) { + this.outputString.append(outputValue.getValue()); + if (entryCounter < inputOutputData.entrySet().size()) { this.outputString.append(" and "); } else { this.outputString.append(" "); } } - payloadEntryCounter++; + entryCounter++; } this.outputString.append(this.getAliasToUse()); @@ -306,7 +281,7 @@ public class SuggestionSearchEntity extends IndexableEntity implements IndexDocu } @Override - public String getIndexDocumentJson() { + public String getAsJson() { // TODO Auto-generated method stub JSONObject rootNode = new JSONObject(); @@ -319,18 +294,12 @@ public class SuggestionSearchEntity extends IndexableEntity implements IndexDocu entitySuggest.put("input", suggestionsArray); entitySuggest.put("output", this.outputString); - entitySuggest.put("payload", this.payloadJsonNode); + entitySuggest.put("payload", this.payload); rootNode.put("entity_suggest", entitySuggest); return rootNode.toString(); } - @Override - public ObjectNode getBulkImportEntity() { - // TODO Auto-generated method stub - return null; - } - public String getAliasToUse() { return aliasToUse; } @@ -339,6 +308,14 @@ public class SuggestionSearchEntity extends IndexableEntity implements IndexDocu this.aliasToUse = aliasToUse; } + public Map getInputOutputData() { + return inputOutputData; + } + + public void setInputOutputData(Map inputOutputData) { + this.inputOutputData = inputOutputData; + } + @Override public String toString() { return "SuggestionSearchEntity [entityType=" + entityType + ", suggestionConnectorWords=" diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/entity/TransactionStorageType.java b/src/main/java/org/onap/aai/sparky/sync/entity/TransactionStorageType.java similarity index 84% rename from src/main/java/org/onap/aai/sparky/synchronizer/entity/TransactionStorageType.java rename to src/main/java/org/onap/aai/sparky/sync/entity/TransactionStorageType.java index 635281e..8dd25a1 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/entity/TransactionStorageType.java +++ b/src/main/java/org/onap/aai/sparky/sync/entity/TransactionStorageType.java @@ -20,7 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.entity; +package org.onap.aai.sparky.sync.entity; /** * The Enum TransactionStorageType. @@ -30,21 +30,6 @@ public enum TransactionStorageType { "aaiOffline/active-inventory-query"); private Integer index; - - /** - * @param index the index to set - */ - public void setIndex(Integer index) { - this.index = index; - } - - /** - * @param outputFolder the outputFolder to set - */ - public void setOutputFolder(String outputFolder) { - this.outputFolder = outputFolder; - } - private String outputFolder; /** diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/enumeration/OperationState.java b/src/main/java/org/onap/aai/sparky/sync/enumeration/OperationState.java similarity index 91% rename from src/main/java/org/onap/aai/sparky/synchronizer/enumeration/OperationState.java rename to src/main/java/org/onap/aai/sparky/sync/enumeration/OperationState.java index 87d1b88..af25301 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/enumeration/OperationState.java +++ b/src/main/java/org/onap/aai/sparky/sync/enumeration/OperationState.java @@ -20,11 +20,11 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.enumeration; +package org.onap.aai.sparky.sync.enumeration; /** * The Enum OperationState. */ public enum OperationState { - INIT, OK, ERROR, ABORT, PENDING + INIT, OK, ERROR, ABORT, PENDING, IGNORED_SYNC_NOT_IDLE } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/enumeration/SynchronizerState.java b/src/main/java/org/onap/aai/sparky/sync/enumeration/SynchronizerState.java similarity index 92% rename from src/main/java/org/onap/aai/sparky/synchronizer/enumeration/SynchronizerState.java rename to src/main/java/org/onap/aai/sparky/sync/enumeration/SynchronizerState.java index 0ce5f70..12f0c0a 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/enumeration/SynchronizerState.java +++ b/src/main/java/org/onap/aai/sparky/sync/enumeration/SynchronizerState.java @@ -20,11 +20,11 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.enumeration; +package org.onap.aai.sparky.sync.enumeration; /** * The Enum SynchronizerState. */ public enum SynchronizerState { - IDLE, PERFORMING_SYNCHRONIZATION + IDLE, PERFORMING_SYNCHRONIZATION, ABORTED } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/PerformActiveInventoryRetrieval.java b/src/main/java/org/onap/aai/sparky/sync/task/PerformActiveInventoryRetrieval.java similarity index 56% rename from src/main/java/org/onap/aai/sparky/synchronizer/task/PerformActiveInventoryRetrieval.java rename to src/main/java/org/onap/aai/sparky/sync/task/PerformActiveInventoryRetrieval.java index 33d3610..55c8d47 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/PerformActiveInventoryRetrieval.java +++ b/src/main/java/org/onap/aai/sparky/sync/task/PerformActiveInventoryRetrieval.java @@ -20,17 +20,17 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.task; +package org.onap.aai.sparky.sync.task; import java.util.Map; import java.util.function.Supplier; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider; -import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.onap.aai.sparky.logging.AaiUiMsgs; import org.slf4j.MDC; /* @@ -43,10 +43,11 @@ import org.slf4j.MDC; */ public class PerformActiveInventoryRetrieval implements Supplier { - private static Logger logger = LoggerFactory.getLogger(PerformActiveInventoryRetrieval.class); + private static Logger logger = + LoggerFactory.getInstance().getLogger(PerformActiveInventoryRetrieval.class); private NetworkTransaction txn; - private ActiveInventoryDataProvider aaiProvider; + private ActiveInventoryAdapter aaiAdapter; private Map contextMap; /** @@ -56,9 +57,9 @@ public class PerformActiveInventoryRetrieval implements Supplier getContextMap() { - return contextMap; - } - - /** - * @param contextMap the contextMap to set - */ - public void setContextMap(Map contextMap) { - this.contextMap = contextMap; - } } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/PerformElasticSearchRetrieval.java b/src/main/java/org/onap/aai/sparky/sync/task/PerformElasticSearchRetrieval.java similarity index 63% rename from src/main/java/org/onap/aai/sparky/synchronizer/task/PerformElasticSearchRetrieval.java rename to src/main/java/org/onap/aai/sparky/sync/task/PerformElasticSearchRetrieval.java index f3f3c16..0f37a0d 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/PerformElasticSearchRetrieval.java +++ b/src/main/java/org/onap/aai/sparky/sync/task/PerformElasticSearchRetrieval.java @@ -20,14 +20,16 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.task; +package org.onap.aai.sparky.sync.task; import java.util.Map; import java.util.function.Supplier; +import javax.ws.rs.core.MediaType; + +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.rest.RestDataProvider; import org.slf4j.MDC; /** @@ -36,7 +38,7 @@ import org.slf4j.MDC; public class PerformElasticSearchRetrieval implements Supplier { private NetworkTransaction txn; - private RestDataProvider restDataProvider; + private ElasticSearchAdapter esAdapter; private Map contextMap; /** @@ -46,9 +48,9 @@ public class PerformElasticSearchRetrieval implements Supplier getContextMap() { - return contextMap; - } - - /** - * @param contextMap the contextMap to set - */ - public void setContextMap(Map contextMap) { - this.contextMap = contextMap; - } - } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/PerformElasticSearchUpdate.java b/src/main/java/org/onap/aai/sparky/sync/task/PerformElasticSearchUpdate.java similarity index 57% rename from src/main/java/org/onap/aai/sparky/synchronizer/task/PerformElasticSearchUpdate.java rename to src/main/java/org/onap/aai/sparky/sync/task/PerformElasticSearchUpdate.java index 72b48c9..1d8371f 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/PerformElasticSearchUpdate.java +++ b/src/main/java/org/onap/aai/sparky/sync/task/PerformElasticSearchUpdate.java @@ -20,14 +20,14 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.task; +package org.onap.aai.sparky.sync.task; import java.util.Map; import java.util.function.Supplier; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.elasticsearch.ElasticSearchDataProvider; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.slf4j.MDC; /** @@ -35,7 +35,7 @@ import org.slf4j.MDC; */ public class PerformElasticSearchUpdate implements Supplier { - private ElasticSearchDataProvider esDataProvider; + private ElasticSearchAdapter esAdapter; private NetworkTransaction operationTracker; private String updatePayload; private String updateUrl; @@ -50,10 +50,10 @@ public class PerformElasticSearchUpdate implements Supplier * @param transactionTracker the transaction tracker */ public PerformElasticSearchUpdate(String updateUrl, String updatePayload, - ElasticSearchDataProvider esDataProvider, NetworkTransaction transactionTracker) { + ElasticSearchAdapter esAdapter, NetworkTransaction transactionTracker) { this.updateUrl = updateUrl; this.updatePayload = updatePayload; - this.esDataProvider = esDataProvider; + this.esAdapter = esAdapter; this.contextMap = MDC.getCopyOfContextMap(); this.operationTracker = new NetworkTransaction(); operationTracker.setEntityType(transactionTracker.getEntityType()); @@ -69,84 +69,12 @@ public class PerformElasticSearchUpdate implements Supplier @Override public NetworkTransaction get() { operationTracker.setTaskAgeInMs(); - long startTimeInMs = System.currentTimeMillis(); MDC.setContextMap(contextMap); - OperationResult or = esDataProvider.doBulkOperation(updateUrl, updatePayload); - - or.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + long startTimeInMs = System.currentTimeMillis(); + OperationResult or = esAdapter.doBulkOperation(updateUrl, updatePayload); operationTracker.setOperationResult(or); - - return operationTracker; - } - - /** - * @return the esDataProvider - */ - public ElasticSearchDataProvider getEsDataProvider() { - return esDataProvider; - } - - /** - * @param esDataProvider the esDataProvider to set - */ - public void setEsDataProvider(ElasticSearchDataProvider esDataProvider) { - this.esDataProvider = esDataProvider; - } - - /** - * @return the operationTracker - */ - public NetworkTransaction getOperationTracker() { + operationTracker.setOpTimeInMs(System.currentTimeMillis() - startTimeInMs); return operationTracker; } - /** - * @param operationTracker the operationTracker to set - */ - public void setOperationTracker(NetworkTransaction operationTracker) { - this.operationTracker = operationTracker; - } - - /** - * @return the updatePayload - */ - public String getUpdatePayload() { - return updatePayload; - } - - /** - * @param updatePayload the updatePayload to set - */ - public void setUpdatePayload(String updatePayload) { - this.updatePayload = updatePayload; - } - - /** - * @return the updateUrl - */ - public String getUpdateUrl() { - return updateUrl; - } - - /** - * @param updateUrl the updateUrl to set - */ - public void setUpdateUrl(String updateUrl) { - this.updateUrl = updateUrl; - } - - /** - * @return the contextMap - */ - public Map getContextMap() { - return contextMap; - } - - /** - * @param contextMap the contextMap to set - */ - public void setContextMap(Map contextMap) { - this.contextMap = contextMap; - } - } diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/StoreDocumentTask.java b/src/main/java/org/onap/aai/sparky/sync/task/StoreDocumentTask.java similarity index 56% rename from src/main/java/org/onap/aai/sparky/synchronizer/task/StoreDocumentTask.java rename to src/main/java/org/onap/aai/sparky/sync/task/StoreDocumentTask.java index 3e31d12..4ef796d 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/StoreDocumentTask.java +++ b/src/main/java/org/onap/aai/sparky/sync/task/StoreDocumentTask.java @@ -20,15 +20,17 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer.task; +package org.onap.aai.sparky.sync.task; import java.util.Map; import java.util.function.Supplier; +import javax.ws.rs.core.MediaType; + +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.rest.RestDataProvider; -import org.onap.aai.sparky.synchronizer.entity.IndexDocument; +import org.onap.aai.sparky.sync.entity.IndexDocument; import org.slf4j.MDC; /** @@ -38,65 +40,9 @@ public class StoreDocumentTask implements Supplier { private IndexDocument doc; - /** - * @return the doc - */ - public IndexDocument getDoc() { - return doc; - } - - /** - * @param doc the doc to set - */ - public void setDoc(IndexDocument doc) { - this.doc = doc; - } - - /** - * @return the txn - */ - public NetworkTransaction getTxn() { - return txn; - } - - /** - * @param txn the txn to set - */ - public void setTxn(NetworkTransaction txn) { - this.txn = txn; - } - - /** - * @return the esDataProvider - */ - public RestDataProvider getEsDataProvider() { - return esDataProvider; - } - - /** - * @param esDataProvider the esDataProvider to set - */ - public void setEsDataProvider(RestDataProvider esDataProvider) { - this.esDataProvider = esDataProvider; - } - - /** - * @return the contextMap - */ - public Map getContextMap() { - return contextMap; - } - - /** - * @param contextMap the contextMap to set - */ - public void setContextMap(Map contextMap) { - this.contextMap = contextMap; - } - private NetworkTransaction txn; - private RestDataProvider esDataProvider; + private ElasticSearchAdapter esAdapter; private Map contextMap; /** @@ -107,10 +53,10 @@ public class StoreDocumentTask implements Supplier { * @param esDataProvider the es data provider */ public StoreDocumentTask(IndexDocument doc, NetworkTransaction txn, - RestDataProvider esDataProvider) { + ElasticSearchAdapter esAdapter) { this.doc = doc; this.txn = txn; - this.esDataProvider = esDataProvider; + this.esAdapter = esAdapter; this.contextMap = MDC.getCopyOfContextMap(); } @@ -125,11 +71,18 @@ public class StoreDocumentTask implements Supplier { long startTimeInMs = System.currentTimeMillis(); MDC.setContextMap(contextMap); - OperationResult or = - esDataProvider.doPut(txn.getLink(), doc.getIndexDocumentJson(), "application/json"); - or.setResponseTimeInMs(System.currentTimeMillis() - startTimeInMs); + OperationResult operationResult = null; + + try { + + operationResult = + esAdapter.doPut(txn.getLink(), doc.getAsJson(), MediaType.APPLICATION_JSON_TYPE); + txn.setOpTimeInMs(System.currentTimeMillis() - startTimeInMs); + } catch (Exception exception) { + operationResult.setResult(500, exception.getMessage()); + } - txn.setOperationResult(or); + txn.setOperationResult(operationResult); return txn; } diff --git a/src/main/java/org/onap/aai/sparky/sync/task/SyncControllerTask.java b/src/main/java/org/onap/aai/sparky/sync/task/SyncControllerTask.java new file mode 100644 index 0000000..959fed1 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/sync/task/SyncControllerTask.java @@ -0,0 +1,53 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.sync.task; + +import org.onap.aai.sparky.sync.SyncController; +import org.onap.aai.sparky.sync.SyncControllerImpl.SyncActions; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; + +public class SyncControllerTask implements Runnable { + + private SyncController controller; + + public SyncControllerTask(SyncController controller) { + this.controller = controller; + } + + @Override + public void run() { + + controller.performAction(SyncActions.SYNCHRONIZE); + + while (controller.getState() == SynchronizerState.PERFORMING_SYNCHRONIZATION) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // exit out of the sync-wait-loop + break; + } + } + + } + +} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/IndexIntegrityValidator.java b/src/main/java/org/onap/aai/sparky/synchronizer/IndexIntegrityValidator.java deleted file mode 100644 index b85dabc..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/IndexIntegrityValidator.java +++ /dev/null @@ -1,227 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.synchronizer; - -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.rest.RestDataProvider; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; - -/** - * The Class IndexIntegrityValidator. - * - * @author davea. - */ -public class IndexIntegrityValidator implements IndexValidator { - - private static final Logger LOG = - LoggerFactory.getInstance().getLogger(IndexIntegrityValidator.class); - - private String host; - - /** - * @return the host - */ - public String getHost() { - return host; - } - - /** - * @param host the host to set - */ - public void setHost(String host) { - this.host = host; - } - - /** - * @return the port - */ - public String getPort() { - return port; - } - - /** - * @param port the port to set - */ - public void setPort(String port) { - this.port = port; - } - - /** - * @return the tableConfigJson - */ - public String getTableConfigJson() { - return tableConfigJson; - } - - /** - * @param tableConfigJson the tableConfigJson to set - */ - public void setTableConfigJson(String tableConfigJson) { - this.tableConfigJson = tableConfigJson; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - - /** - * @return the restDataProvider - */ - public RestDataProvider getRestDataProvider() { - return restDataProvider; - } - - 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.onap.aai.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.onap.aai.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.onap.aai.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.onap.aai.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/onap/aai/sparky/synchronizer/MyErrorHandler.java b/src/main/java/org/onap/aai/sparky/synchronizer/MyErrorHandler.java deleted file mode 100644 index 7a55b15..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/MyErrorHandler.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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; - - /** - * @return the out - */ - public PrintWriter getOut() { - return out; - } - - /** - * @param out the out to set - */ - public void setOut(PrintWriter out) { - this.out = 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/onap/aai/sparky/synchronizer/SyncHelper.java b/src/main/java/org/onap/aai/sparky/synchronizer/SyncHelper.java deleted file mode 100644 index 9081d41..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/SyncHelper.java +++ /dev/null @@ -1,568 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.config.oxm.OxmEntityDescriptor; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.dal.aai.ActiveInventoryAdapter; -import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; -import org.onap.aai.sparky.dal.aai.config.ActiveInventoryRestConfig; -import org.onap.aai.sparky.dal.cache.EntityCache; -import org.onap.aai.sparky.dal.cache.InMemoryEntityCache; -import org.onap.aai.sparky.dal.cache.PersistentEntityCache; -import org.onap.aai.sparky.dal.elasticsearch.ElasticSearchAdapter; -import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; -import org.onap.aai.sparky.dal.rest.RestClientBuilder; -import org.onap.aai.sparky.dal.rest.RestfulDataAccessor; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.SyncController.SyncActions; -import org.onap.aai.sparky.synchronizer.config.SynchronizerConfiguration; -import org.onap.aai.sparky.synchronizer.config.SynchronizerConstants; -import org.onap.aai.sparky.synchronizer.enumeration.SynchronizerState; -import org.onap.aai.sparky.util.ErrorUtil; -import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; -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; - - public boolean isInitialSync() { - return isInitialSync; - } - - public void setInitialSync(boolean isInitialSync) { - this.isInitialSync = 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); - } - - } - - } - - - /** - * 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); - - 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); - - } 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(); - } - - // 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."); - } - } - - } 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); - } - } - - - /** - * 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/onap/aai/sparky/synchronizer/config/SynchronizerConfiguration.java b/src/main/java/org/onap/aai/sparky/synchronizer/config/SynchronizerConfiguration.java deleted file mode 100644 index 8762a0f..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/config/SynchronizerConfiguration.java +++ /dev/null @@ -1,544 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.synchronizer.config; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.text.ParseException; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Properties; -import java.util.TimeZone; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.util.ConfigHelper; -import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; - - -/** - * 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(); - } - - return instance; - } - - /** - * Instantiates a new synchronizer configuration. - */ - public SynchronizerConfiguration() - throws NumberFormatException, PatternSyntaxException, ParseException { - Properties props = ConfigHelper.loadConfigFromExplicitPath(CONFIG_FILE); - initialize(props); - } - - public SynchronizerConfiguration(Properties props) - throws NumberFormatException, PatternSyntaxException, ParseException { - initialize(props); - } - - /** - * Initialize. - * - * @throws Exception the exception - */ - protected void initialize(Properties props) - throws NumberFormatException, PatternSyntaxException, ParseException { - - // 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 NumberFormatException("Error. Sync Task Delay has to be positive"); - } - } catch (NumberFormatException 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 NumberFormatException("Error. Sync Task Frequency has to be positive"); - } - } catch (NumberFormatException 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 PatternSyntaxException("Pattern Mismatch", - "The erroneous pattern is not available", -1); - } - - 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 (ParseException 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")); - - 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; - } - - 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 autosuggestSynchronizationEnabled; - - 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; - } - - public Calendar getTargetSyncTime() { - - TimeZone tz = TimeZone.getTimeZone(getSyncTaskStartTimeTimeZone()); - Calendar targetSyncTime = Calendar.getInstance(tz); - - targetSyncTime.set(Calendar.HOUR_OF_DAY, getSyncTaskStartTimeHr()); - targetSyncTime.set(Calendar.MINUTE, getSyncTaskStartTimeMin()); - targetSyncTime.set(Calendar.SECOND, getSyncTaskStartTimeSec()); - - return targetSyncTime; - - } - - public long getDefaultInitialSyncDelayInMs(Calendar timeNow) { - - int taskFrequencyInDays = getSyncTaskFrequencyInDay(); - - long nextSyncTimeInMs = getNextSyncTime(getTargetSyncTime(), timeNow.getTimeInMillis(), - taskFrequencyInDays * 86400); - - /* - * If the the current time is after the scheduled start time, then delay by the initial task - * delay configuration value - */ - long delayUntilNextSyncInMs = - Math.max(getSyncTaskInitialDelayInMs(), nextSyncTimeInMs - timeNow.getTimeInMillis()); - - return delayUntilNextSyncInMs; - - } - - public long getNextSyncTime(Calendar syncTime, int taskFrequencyInSeconds) { - - TimeZone tz = TimeZone.getTimeZone(getSyncTaskStartTimeTimeZone()); - Calendar timeNow = Calendar.getInstance(tz); - - return getNextSyncTime(syncTime, timeNow.getTimeInMillis(), taskFrequencyInSeconds); - } - - /** - * Gets the first sync time. - * - * @param calendar the calendar - * @param timeNow the time now in ms - * @param taskFrequencyInMs task period in ms - * @return the first sync time - */ - public long getNextSyncTime(Calendar syncTime, long timeNowInMs, int taskFrequencyInSeconds) { - if (taskFrequencyInSeconds == 0) { - return 0; - } else if (timeNowInMs > syncTime.getTimeInMillis()) { - - /* - * If current time is after the scheduled sync start time, then we'll skip ahead to the next - * sync time period - */ - - syncTime.add(Calendar.SECOND, taskFrequencyInSeconds); - } - - return syncTime.getTimeInMillis(); - } - - /** - * @return the instance - */ - public static SynchronizerConfiguration getInstance() { - return instance; - } - - /** - * @param instance the instance to set - */ - public static void setInstance(SynchronizerConfiguration instance) { - SynchronizerConfiguration.instance = instance; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - - /** - * @return the configFile - */ - public static String getConfigFile() { - return CONFIG_FILE; - } - - /** - * @return the depthModifier - */ - public static String getDepthModifier() { - return DEPTH_MODIFIER; - } - - /** - * @return the depthAllModifier - */ - public static String getDepthAllModifier() { - return DEPTH_ALL_MODIFIER; - } - - /** - * @return the depthAndNodesOnlyModifier - */ - public static String getDepthAndNodesOnlyModifier() { - return DEPTH_AND_NODES_ONLY_MODIFIER; - } - - /** - * @return the nodesOnlyModifier - */ - public static String getNodesOnlyModifier() { - return NODES_ONLY_MODIFIER; - } - - /* - * (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/onap/aai/sparky/synchronizer/config/TaskProcessorConfig.java b/src/main/java/org/onap/aai/sparky/synchronizer/config/TaskProcessorConfig.java deleted file mode 100644 index 73f4f77..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/config/TaskProcessorConfig.java +++ /dev/null @@ -1,325 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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/onap/aai/sparky/synchronizer/filter/ElasticSearchSynchronizerFilter.java b/src/main/java/org/onap/aai/sparky/synchronizer/filter/ElasticSearchSynchronizerFilter.java deleted file mode 100644 index ef199f7..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/filter/ElasticSearchSynchronizerFilter.java +++ /dev/null @@ -1,136 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.SyncHelper; -import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; -import org.onap.aai.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); - } - - } - - /** - * @return the syncHelper - */ - public SyncHelper getSyncHelper() { - return syncHelper; - } - - /** - * @param syncHelper the syncHelper to set - */ - public void setSyncHelper(SyncHelper syncHelper) { - this.syncHelper = syncHelper; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - -} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/CollectEntitySelfLinkTask.java b/src/main/java/org/onap/aai/sparky/synchronizer/task/CollectEntitySelfLinkTask.java deleted file mode 100644 index b12a1d9..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/CollectEntitySelfLinkTask.java +++ /dev/null @@ -1,104 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.synchronizer.task; - -import java.util.function.Supplier; - -import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider; -import org.onap.aai.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; - } - - /** - * @return the txn - */ - public NetworkTransaction getTxn() { - return txn; - } - - /** - * @param txn the txn to set - */ - public void setTxn(NetworkTransaction txn) { - this.txn = txn; - } - - /** - * @return the provider - */ - public ActiveInventoryDataProvider getProvider() { - return provider; - } - - /** - * @param provider the provider to set - */ - public void setProvider(ActiveInventoryDataProvider provider) { - this.provider = provider; - } - -} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/CollectEntityTypeSelfLinksTask.java b/src/main/java/org/onap/aai/sparky/synchronizer/task/CollectEntityTypeSelfLinksTask.java deleted file mode 100644 index 712a2e3..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/CollectEntityTypeSelfLinksTask.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.synchronizer.task; - -import java.util.function.Supplier; - -import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider; -import org.onap.aai.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; - } - - /** - * @return the aaiProvider - */ - public ActiveInventoryDataProvider getAaiProvider() { - return aaiProvider; - } - - /** - * @param aaiProvider the aaiProvider to set - */ - public void setAaiProvider(ActiveInventoryDataProvider aaiProvider) { - this.aaiProvider = aaiProvider; - } - - /** - * @return the txn - */ - public NetworkTransaction getTxn() { - return txn; - } - - /** - * @param txn the txn to set - */ - public void setTxn(NetworkTransaction txn) { - this.txn = txn; - } - -} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/GetCrossEntityReferenceEntityTask.java b/src/main/java/org/onap/aai/sparky/synchronizer/task/GetCrossEntityReferenceEntityTask.java deleted file mode 100644 index 8c1e0b7..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/GetCrossEntityReferenceEntityTask.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.synchronizer.task; - -import java.util.function.Supplier; - -import org.onap.aai.sparky.dal.NetworkTransaction; -import org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider; -import org.onap.aai.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; - } - - /** - * @return the txn - */ - public NetworkTransaction getTxn() { - return txn; - } - - /** - * @param txn the txn to set - */ - public void setTxn(NetworkTransaction txn) { - this.txn = txn; - } - - /** - * @return the provider - */ - public ActiveInventoryDataProvider getProvider() { - return provider; - } - - /** - * @param provider the provider to set - */ - public void setProvider(ActiveInventoryDataProvider provider) { - this.provider = provider; - } - -} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/PersistOperationResultToDisk.java b/src/main/java/org/onap/aai/sparky/synchronizer/task/PersistOperationResultToDisk.java deleted file mode 100644 index 0ab331e..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/PersistOperationResultToDisk.java +++ /dev/null @@ -1,157 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.synchronizer.task; - -import java.io.File; -import java.util.Map; -import java.util.function.Supplier; - -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.cl.api.Logger; -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; - } - - /** - * @return the fullPath - */ - public String getFullPath() { - return fullPath; - } - - /** - * @param fullPath the fullPath to set - */ - public void setFullPath(String fullPath) { - this.fullPath = fullPath; - } - - /** - * @return the dataToStore - */ - public OperationResult getDataToStore() { - return dataToStore; - } - - /** - * @param dataToStore the dataToStore to set - */ - public void setDataToStore(OperationResult dataToStore) { - this.dataToStore = dataToStore; - } - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } - - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } - - /** - * @return the logger - */ - public Logger getLogger() { - return logger; - } - - /** - * @param logger the logger to set - */ - public void setLogger(Logger logger) { - this.logger = logger; - } - - /** - * @return the contextMap - */ - public Map getContextMap() { - return contextMap; - } - - /** - * @param contextMap the contextMap to set - */ - public void setContextMap(Map contextMap) { - this.contextMap = contextMap; - } - - - -} diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/task/RetrieveOperationResultFromDisk.java b/src/main/java/org/onap/aai/sparky/synchronizer/task/RetrieveOperationResultFromDisk.java deleted file mode 100644 index 0e11319..0000000 --- a/src/main/java/org/onap/aai/sparky/synchronizer/task/RetrieveOperationResultFromDisk.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.cl.api.Logger; - -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; - } - - /** - * @return the fullPath - */ - public String getFullPath() { - return fullPath; - } - - /** - * @param fullPath the fullPath to set - */ - public void setFullPath(String fullPath) { - this.fullPath = fullPath; - } - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } - - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } - - /** - * @return the logger - */ - public Logger getLogger() { - return logger; - } - - /** - * @param logger the logger to set - */ - public void setLogger(Logger logger) { - this.logger = logger; - } - -} diff --git a/src/main/java/org/onap/aai/sparky/topology/sync/GeoSyncController.java b/src/main/java/org/onap/aai/sparky/topology/sync/GeoSyncController.java new file mode 100644 index 0000000..a2acc06 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/topology/sync/GeoSyncController.java @@ -0,0 +1,95 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.topology.sync; + +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.sync.ElasticSearchIndexCleaner; +import org.onap.aai.sparky.sync.ElasticSearchSchemaFactory; +import org.onap.aai.sparky.sync.IndexCleaner; +import org.onap.aai.sparky.sync.IndexIntegrityValidator; +import org.onap.aai.sparky.sync.SyncControllerImpl; +import org.onap.aai.sparky.sync.SyncControllerRegistrar; +import org.onap.aai.sparky.sync.SyncControllerRegistry; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.config.SyncControllerConfig; + +public class GeoSyncController extends SyncControllerImpl implements SyncControllerRegistrar { + + private SyncControllerRegistry syncControllerRegistry; + + public GeoSyncController(SyncControllerConfig syncControllerConfig, + ActiveInventoryAdapter aaiAdapter, ElasticSearchAdapter esAdapter, + ElasticSearchSchemaConfig schemaConfig, ElasticSearchEndpointConfig endpointConfig, + NetworkStatisticsConfig aaiStatConfig, NetworkStatisticsConfig esStatConfig) + throws Exception { + super(syncControllerConfig); + + // final String controllerName = "Inventory Geo Synchronizer"; + + IndexIntegrityValidator indexValidator = new IndexIntegrityValidator(esAdapter, schemaConfig, + endpointConfig, ElasticSearchSchemaFactory.getIndexSchema(schemaConfig)); + + registerIndexValidator(indexValidator); + + GeoSynchronizer synchronizer = + new GeoSynchronizer(schemaConfig, syncControllerConfig.getNumInternalSyncWorkers(), + syncControllerConfig.getNumSyncActiveInventoryWorkers(), + syncControllerConfig.getNumSyncElasticWorkers(), aaiStatConfig, esStatConfig); + + synchronizer.setAaiAdapter(aaiAdapter); + synchronizer.setElasticSearchAdapter(esAdapter); + + registerEntitySynchronizer(synchronizer); + + + IndexCleaner indexCleaner = + new ElasticSearchIndexCleaner(esAdapter, endpointConfig, schemaConfig); + + registerIndexCleaner(indexCleaner); + + } + + public SyncControllerRegistry getSyncControllerRegistry() { + return syncControllerRegistry; + } + + public void setSyncControllerRegistry(SyncControllerRegistry syncControllerRegistry) { + this.syncControllerRegistry = syncControllerRegistry; + } + + @Override + public void registerController() { + + if (syncControllerRegistry != null) { + if (syncControllerConfig.isEnabled()) { + syncControllerRegistry.registerSyncController(this); + } + } + } + + + +} diff --git a/src/main/java/org/onap/aai/sparky/topology/sync/GeoSynchronizer.java b/src/main/java/org/onap/aai/sparky/topology/sync/GeoSynchronizer.java new file mode 100644 index 0000000..f075ff8 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/topology/sync/GeoSynchronizer.java @@ -0,0 +1,497 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.topology.sync; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +import java.io.IOException; +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.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.config.oxm.GeoEntityLookup; +import org.onap.aai.sparky.config.oxm.GeoOxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; +import org.onap.aai.sparky.dal.NetworkTransaction; +import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; +import org.onap.aai.sparky.dal.rest.HttpMethod; +import org.onap.aai.sparky.inventory.entity.GeoIndexDocument; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.AbstractEntitySynchronizer; +import org.onap.aai.sparky.sync.IndexSynchronizer; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.entity.SelfLinkDescriptor; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.onap.aai.sparky.sync.task.PerformActiveInventoryRetrieval; +import org.onap.aai.sparky.sync.task.StoreDocumentTask; +import org.onap.aai.sparky.util.NodeUtils; +import org.slf4j.MDC; + +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(ElasticSearchSchemaConfig schemaConfig, int internalSyncWorkers, + int aaiWorkers, int esWorkers, NetworkStatisticsConfig aaiStatConfig, + NetworkStatisticsConfig esStatConfig) throws Exception { + + super(LOG, "GEO", internalSyncWorkers, aaiWorkers, esWorkers, schemaConfig.getIndexName(), + aaiStatConfig, esStatConfig); + this.allWorkEnumerated = false; + this.selflinks = new ConcurrentLinkedDeque(); + this.synchronizerName = "Geo Synchronizer"; + this.geoDescriptorMap = GeoEntityLookup.getInstance().getGeoEntityDescriptors(); + this.aaiEntityStats.intializeEntityCounters(geoDescriptorMap.keySet()); + this.esEntityStats.intializeEntityCounters(geoDescriptorMap.keySet()); + this.syncDurationInMs = -1; + } + + + /* + * (non-Javadoc) + * + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() + */ + @Override + public OperationState doSync() { + this.syncDurationInMs = -1; + resetCounters(); + setShouldSkipSync(false); + 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()) { + setShouldSkipSync(true); + 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 = aaiAdapter.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 = OxmEntityLookup.getInstance().getEntityDescriptors() + .get(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, aaiAdapter), 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; + } + + GeoOxmEntityDescriptor descriptor = geoDescriptorMap.get(txn.getEntityType()); + + if (descriptor == null) { + return; + } + + try { + if (descriptor.hasGeoEntity()) { + + GeoIndexDocument geoDoc = new GeoIndexDocument(); + + 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, elasticSearchAdapter), 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) { + syncDurationInMs = System.currentTimeMillis() - syncStartedTimeStampInMs; + return this.getStatReport(syncDurationInMs, 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.getPrimaryKeyAttributeNames()) { + 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); + + GeoOxmEntityDescriptor descriptor = geoDescriptorMap.get(resultDescriptor.getEntityName()); + + String geoLatKey = descriptor.getGeoLatName(); + String geoLongKey = descriptor.getGeoLongName(); + + doc.setLatitude(NodeUtils.getNodeFieldAsText(entityNode, geoLatKey)); + doc.setLongitude(NodeUtils.getNodeFieldAsText(entityNode, geoLongKey)); + doc.deriveFields(); + + } + + @Override + protected boolean isSyncDone() { + if (shouldSkipSync()) { + syncDurationInMs = System.currentTimeMillis() - syncStartedTimeStampInMs; + return true; + } + + int totalWorkOnHand = aaiWorkOnHand.get() + esWorkOnHand.get(); + + if (totalWorkOnHand > 0 || !allWorkEnumerated) { + return false; + } + + return true; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/util/ConfigHelper.java b/src/main/java/org/onap/aai/sparky/util/ConfigHelper.java index bad916b..c9a2414 100644 --- a/src/main/java/org/onap/aai/sparky/util/ConfigHelper.java +++ b/src/main/java/org/onap/aai/sparky/util/ConfigHelper.java @@ -32,9 +32,9 @@ import java.io.InputStream; import java.util.Properties; import java.util.Set; -import org.onap.aai.sparky.logging.AaiUiMsgs; import org.onap.aai.cl.api.Logger; import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.logging.AaiUiMsgs; /** * The Class ConfigHelper. diff --git a/src/main/java/org/onap/aai/sparky/util/Encryptor.java b/src/main/java/org/onap/aai/sparky/util/Encryptor.java index 80aefd0..15b735b 100644 --- a/src/main/java/org/onap/aai/sparky/util/Encryptor.java +++ b/src/main/java/org/onap/aai/sparky/util/Encryptor.java @@ -28,17 +28,38 @@ 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; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.sparky.logging.AaiUiMsgs; /** * The Class Encryptor. */ public class Encryptor { + private static final Logger LOG = LoggerFactory.getInstance().getLogger(Encryptor.class); + /** * Instantiates a new encryptor. */ public Encryptor() {} + /** + * Encrypt value. + * + * @param value to encrypt + * @return the encrypted string + */ + public String encryptValue(String value) { + String encyptedValue = ""; + try { + encyptedValue = Password.obfuscate(value); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.ENCRYPTION_ERROR, value, exc.toString()); + } + return encyptedValue; + } + /** * Decrypt value. * @@ -47,8 +68,11 @@ public class Encryptor { */ public String decryptValue(String value) { String decyptedValue = ""; - - decyptedValue = Password.deobfuscate(value); + try { + decyptedValue = Password.deobfuscate(value); + } catch (Exception exc) { + LOG.error(AaiUiMsgs.DECRYPTION_ERROR, value, exc.toString()); + } return decyptedValue; } @@ -76,4 +100,54 @@ public class Encryptor { 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/onap/aai/sparky/util/ErrorUtil.java b/src/main/java/org/onap/aai/sparky/util/ErrorUtil.java index 0bf6d38..e661b73 100644 --- a/src/main/java/org/onap/aai/sparky/util/ErrorUtil.java +++ b/src/main/java/org/onap/aai/sparky/util/ErrorUtil.java @@ -20,7 +20,6 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ - package org.onap.aai.sparky.util; /** diff --git a/src/main/java/org/onap/aai/sparky/util/KeystoreBuilder.java b/src/main/java/org/onap/aai/sparky/util/KeystoreBuilder.java index d3ae421..05ba3d2 100644 --- a/src/main/java/org/onap/aai/sparky/util/KeystoreBuilder.java +++ b/src/main/java/org/onap/aai/sparky/util/KeystoreBuilder.java @@ -110,20 +110,6 @@ public class KeystoreBuilder { private List endpoints = new ArrayList(); - /** - * @return the endpoints - */ - public List getEndpoints() { - return endpoints; - } - - /** - * @param endpoints the endpoints to set - */ - public void setEndpoints(List endpoints) { - this.endpoints = endpoints; - } - /** * Initialize end points list. * @@ -209,7 +195,7 @@ public class KeystoreBuilder { } else { System.out.println("keystore file doesn't exist, preloading new file with jssecacerts"); } - password = keystorePassword; + password = "changeit"; } @@ -260,8 +246,8 @@ public class KeystoreBuilder { private X509Certificate[] getCertificateChainForRemoteEndpoint(String hostname, int port) throws UnknownHostException, IOException { - System.out.println("Opening connection to " + hostname + ":" + port + ".."); - SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(hostname, port); + System.out.println("Opening connection to localhost:8442.."); + SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket("aai-int1.dev.att.com", 8440); socket.setSoTimeout(10000); try { @@ -272,8 +258,6 @@ public class KeystoreBuilder { System.exit(0); } catch (SSLException exc) { System.out.println("\nCaught SSL exception, we are not authorized to access this server yet"); - throw new SSLException( - "\nCaught SSL exception, we are not authorized to access this server yet"); // e.printStackTrace(System.out); } @@ -523,6 +507,8 @@ public class KeystoreBuilder { */ 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 diff --git a/src/main/java/org/onap/aai/sparky/util/NodeUtils.java b/src/main/java/org/onap/aai/sparky/util/NodeUtils.java index 20e547f..a34c07d 100644 --- a/src/main/java/org/onap/aai/sparky/util/NodeUtils.java +++ b/src/main/java/org/onap/aai/sparky/util/NodeUtils.java @@ -27,8 +27,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.Thread.UncaughtExceptionHandler; +import java.net.URI; import java.nio.ByteBuffer; import java.security.SecureRandom; +import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -50,15 +52,17 @@ import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.xml.stream.XMLStreamConstants; +import org.onap.aai.cl.api.Logger; import org.onap.aai.sparky.logging.AaiUiMsgs; import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; -import org.onap.aai.cl.api.Logger; +import org.restlet.Request; 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.fasterxml.jackson.databind.ser.FilterProvider; import com.google.common.util.concurrent.ThreadFactoryBuilder; @@ -68,33 +72,8 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder; public class NodeUtils { private static SecureRandom sRandom = new SecureRandom(); - /** - * @return the sRandom - */ - public static SecureRandom getsRandom() { - return sRandom; - } - - /** - * @param sRandom the sRandom to set - */ - public static void setsRandom(SecureRandom sRandom) { - NodeUtils.sRandom = sRandom; - } - - /** - * @return the entityResourceKeyFormat - */ - public static String getEntityResourceKeyFormat() { - return ENTITY_RESOURCE_KEY_FORMAT; - } + private static final Pattern AAI_VERSION_PREFIX = Pattern.compile("/aai/v[0-9]+/(.*)"); - /** - * @return the timeBreakDownFormat - */ - public static String getTimeBreakDownFormat() { - return TIME_BREAK_DOWN_FORMAT; - } public static synchronized String getRandomTxnId() { byte bytes[] = new byte[6]; @@ -118,6 +97,31 @@ public class NodeUtils { return sb.toString(); } + + public static String extractRawPathWithoutVersion(String selfLinkUri) { + + try { + + String rawPath = new URI(selfLinkUri).getRawPath(); + + Matcher m = AAI_VERSION_PREFIX.matcher(rawPath); + + if (m.matches()) { + + // System.out.println(m.group(0)); + if (m.groupCount() >= 1) { + return m.group(1); + } + // System.out.println(m.group(2)); + + } + } catch (Exception e) { + } + + return null; + + } + /** * Checks if is numeric. * @@ -296,6 +300,14 @@ public class NodeUtils { return concatArray(list, " "); } + private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; + + public static String getCurrentTimeStamp() { + SimpleDateFormat dateFormat = new SimpleDateFormat(TIMESTAMP_FORMAT); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + return dateFormat.format(timestamp); + } + /** * Concat array. * @@ -395,12 +407,12 @@ public class NodeUtils { String resourceId = null; if ("/".equals(link.substring(linkLength - 1))) { // Use-case: - // https://:9292/aai/v7/business/customers/customer/1607_20160524Func_Ak1_01/service-subscriptions/service-subscription/uCPE-VMS/ + // 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://:9292/aai/v7/business/customers/customer/1607_20160524Func_Ak1_01/service-subscriptions/service-subscription/uCPE-VMS + // 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); } @@ -491,6 +503,33 @@ public class NodeUtils { return ow.writeValueAsString(object); } + /** + * Convert object to json by selectively choosing certain fields thru filters. Example use case: + * based on request type we might need to send different serialization of the UiViewFilterEntity + * + * @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, FilterProvider filters) + throws JsonProcessingException { + ObjectWriter ow = null; + + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + + if (pretty) { + ow = mapper.writer(filters).withDefaultPrettyPrinter(); + + } else { + ow = mapper.writer(filters); + } + + return ow.writeValueAsString(object); + } + + /** * Convert json str to json node. * @@ -687,13 +726,39 @@ public class NodeUtils { * @throws IOException Signals that an I/O exception has occurred. */ public static String getBody(HttpServletRequest request) throws IOException { + InputStream inputStream = request.getInputStream(); + return getBodyFromStream(inputStream); + } + + + + /** + * Gets the Restlet Request payload. + * + * @param request the request + * @return the body + * @throws IOException Signals that an I/O exception has occurred. + */ + public static String getBody(Request request) throws IOException { + InputStream inputStream = request.getEntity().getStream(); + return getBodyFromStream(inputStream); + } + + + /** + * Gets the payload from the input stream of a request. + * + * @param request the request + * @return the body + * @throws IOException Signals that an I/O exception has occurred. + */ + public static String getBodyFromStream(InputStream inputStream) 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]; @@ -719,4 +784,23 @@ public class NodeUtils { 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/onap/aai/sparky/util/RestletUtils.java b/src/main/java/org/onap/aai/sparky/util/RestletUtils.java new file mode 100644 index 0000000..06c8c05 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/util/RestletUtils.java @@ -0,0 +1,118 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.util; + +import javax.servlet.http.HttpServletResponse; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.restlet.Response; +import org.restlet.data.MediaType; +import org.restlet.data.Status; + +public class RestletUtils { + /** + * Returns an HttpServletResponse based on values from a Restlet Response + * + * @param restletResponse Restlet Response to be converted to an HttpServletResponse + * @return An HttpServletResponse object built from the values of a Restlet Response + */ + public HttpServletResponse convertRestletResponseToHttpServletResponse(Response restletResponse) { + return org.restlet.ext.servlet.ServletUtils.getResponse(restletResponse); + } + + /** + * Execute post query + * + * @param logger The logger + * @param search The searchAdapter + * @param response The response + * @param requestUrl The request URL + * @param requestJsonPayload The request JSON payload + * @return The operation result + */ + public OperationResult executePostQuery(Logger logger, SearchAdapter search, Response response, + String requestUrl, String requestJsonPayload) { + + OperationResult opResult = search.doPost(requestUrl, requestJsonPayload, "application/json"); + + if (opResult.getResultCode() > 300) { + setRestletResponse(logger, true, opResult.getResultCode(), response, opResult.getResult()); + } else { + response.setStatus(new Status(opResult.getResultCode())); + } + + return opResult; + } + + /** + * Generate JSON error response + * + * @param message The error message + * @return The error message formatted as a JSON string + */ + public String generateJsonErrorResponse(String message) { + return String.format("{ \"errorMessage\" : \"%s\" }", message); + } + + /** + * Log Restlet exceptions/errors & prepare Response object with exception/errors info + * + * @param logger The logger + * @param errorMsg The error message + * @param exc The exception + * @param response The response + */ + public void handleRestletErrors(Logger logger, String errorMsg, Exception exc, + Response response) { + String errorLogMsg = + (exc == null ? errorMsg : errorMsg + ". Error:" + exc.getLocalizedMessage()); + logger.error(AaiUiMsgs.ERROR_GENERIC, errorLogMsg); + response.setEntity(generateJsonErrorResponse(errorMsg), MediaType.APPLICATION_JSON); + } + + /** + * Sets the Restlet response + * + * @param logger The logger + * @param isError The error + * @param responseCode The response code + * @param response The response + * @param postPayload The post payload + */ + public void setRestletResponse(Logger logger, boolean isError, int responseCode, + Response response, String postPayload) { + + if (isError) { + logger.error(AaiUiMsgs.ERROR_GENERIC, postPayload); + } + + response.setStatus(new Status(responseCode)); + + if (postPayload != null) { + response.setEntity(postPayload, MediaType.APPLICATION_JSON); + } + } +} diff --git a/src/main/java/org/onap/aai/sparky/util/SuggestionsPermutation.java b/src/main/java/org/onap/aai/sparky/util/SuggestionsPermutation.java index 463d122..ba51254 100644 --- a/src/main/java/org/onap/aai/sparky/util/SuggestionsPermutation.java +++ b/src/main/java/org/onap/aai/sparky/util/SuggestionsPermutation.java @@ -36,44 +36,63 @@ public class SuggestionsPermutation { * * @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); + public static ArrayList> getUniqueListForSuggestions( + List originalList) { + ArrayList> lists = new ArrayList>(); + if (originalList.isEmpty()) { + lists.add(new ArrayList()); + return lists; + } + List list = new ArrayList(originalList); + String head = list.get(0); + ArrayList rest = new ArrayList(list.subList(1, list.size())); - 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++; + for (ArrayList activeList : getUniqueListForSuggestions(rest)) { + ArrayList newList = new ArrayList(); + newList.add(head); + newList.addAll(activeList); + lists.add(newList); + lists.add(activeList); } - return uniqueList; + return lists; + } + public static ArrayList> getNonEmptyUniqueLists(List list) { + ArrayList> lists = getUniqueListForSuggestions(list); + // remove empty list from the power set + for (ArrayList emptyList : lists) { + if (emptyList.isEmpty()) { + lists.remove(emptyList); + break; + } + } + return lists; } - private List truncateListUntill(List lists, int index) { - List truncatedList = new ArrayList<>(lists); - int counter = 0; - while (counter <= index) { - truncatedList.remove(0); - counter++; + public static List> getListPermutations(List list) { + List inputList = new ArrayList(); + inputList.addAll(list); + if (inputList.size() == 0) { + List> result = new ArrayList>(); + result.add(new ArrayList()); + return result; } - return truncatedList; + + List> listOfLists = new ArrayList>(); + + String firstElement = inputList.remove(0); + + List> recursiveReturn = getListPermutations(inputList); + for (List li : recursiveReturn) { + + for (int index = 0; index <= li.size(); index++) { + List temp = new ArrayList(li); + temp.add(index, firstElement); + listOfLists.add(temp); + } + + } + return listOfLists; } + } diff --git a/src/main/java/org/onap/aai/sparky/util/TreeWalker.java b/src/main/java/org/onap/aai/sparky/util/TreeWalker.java index 6306a30..2221475 100644 --- a/src/main/java/org/onap/aai/sparky/util/TreeWalker.java +++ b/src/main/java/org/onap/aai/sparky/util/TreeWalker.java @@ -22,17 +22,17 @@ */ package org.onap.aai.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; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; + /** * The Class TreeWalker. */ diff --git a/src/main/java/org/onap/aai/sparky/util/test/Encryptor.java b/src/main/java/org/onap/aai/sparky/util/test/Encryptor.java deleted file mode 100644 index c24f2c2..0000000 --- a/src/main/java/org/onap/aai/sparky/util/test/Encryptor.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.util.test; - -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); - } - -} diff --git a/src/main/java/org/onap/aai/sparky/util/test/KeystoreBuilder.java b/src/main/java/org/onap/aai/sparky/util/test/KeystoreBuilder.java deleted file mode 100644 index e771066..0000000 --- a/src/main/java/org/onap/aai/sparky/util/test/KeystoreBuilder.java +++ /dev/null @@ -1,541 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.sparky.util.test; - -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(); - - /** - * @return the endpoints - */ - public List getEndpoints() { - return endpoints; - } - - /** - * @param endpoints the endpoints to set - */ - public void setEndpoints(List endpoints) { - this.endpoints = endpoints; - } - - /** - * 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 " + hostname + ":" + port + ".."); - SSLSocket socket = (SSLSocket) sslSocketFactory.createSocket(hostname, port); - 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 { - - /* - * 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/onap/aai/sparky/viewandinspect/EntityTypeAggregation.java b/src/main/java/org/onap/aai/sparky/viewandinspect/EntityTypeAggregation.java index ff8d5d8..594b49f 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/EntityTypeAggregation.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/EntityTypeAggregation.java @@ -22,13 +22,10 @@ */ package org.onap.aai.sparky.viewandinspect; -import com.fasterxml.jackson.core.JsonProcessingException; - import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.sparky.viewandinspect.EntityTypeAggregation; + /** * The Class EntityTypeAggregation. @@ -37,13 +34,6 @@ public class EntityTypeAggregation { private ConcurrentHashMap counters; - /** - * @param counters the counters to set - */ - public void setCounters(ConcurrentHashMap counters) { - this.counters = counters; - } - /** * Instantiates a new entity type aggregation. */ @@ -64,4 +54,5 @@ public class EntityTypeAggregation { public ConcurrentHashMap getCounters() { return counters; } + } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/SchemaVisualizationProcessor.java b/src/main/java/org/onap/aai/sparky/viewandinspect/SchemaVisualizationProcessor.java new file mode 100644 index 0000000..822b14b --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/SchemaVisualizationProcessor.java @@ -0,0 +1,174 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect; + +import java.security.SecureRandom; +import java.util.concurrent.ExecutorService; + +import org.apache.camel.Exchange; +import org.apache.camel.component.restlet.RestletConstants; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.config.oxm.OxmModelLoader; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.util.NodeUtils; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; +import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode; +import org.onap.aai.sparky.viewandinspect.entity.JsonNode; +import org.onap.aai.sparky.viewandinspect.entity.NodeMeta; +import org.onap.aai.sparky.viewandinspect.entity.QueryRequest; +import org.onap.aai.sparky.viewandinspect.services.VisualizationContext; +import org.onap.aai.sparky.viewandinspect.services.VisualizationService; +import org.onap.aai.sparky.viewandinspect.services.VisualizationTransformer; +import org.onap.aai.sparky.viewinspect.sync.ViewInspectSyncController; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ClientInfo; +import org.restlet.data.MediaType; +import org.restlet.data.Status; + +public class SchemaVisualizationProcessor { + + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(SchemaVisualizationProcessor.class); + + private final VisualizationService visualizationService; + private VisualizationTransformer visualizationTransformer; + private VisualizationContext visualizationContext; + private NodeMeta nodeMeta; + private JsonNode jsonNode; + private ActiveInventoryNode activeInventoryNode; + private final ExecutorService tabularExecutorService; + private final ExecutorService aaiExecutorService; + private final SecureRandom secureRandom; + private ActiveInventoryAdapter aaiAdapter; + private ElasticSearchAdapter esAdapter; + private ElasticSearchEndpointConfig endpointConfig; + private ElasticSearchSchemaConfig schemaConfig; + private ActiveInventoryConfig aaiConfig; + + public SchemaVisualizationProcessor(VisualizationConfigs visualizationConfigs, + OxmModelLoader oxmModelLoader, ViewInspectSyncController viewInspectSynController) + throws Exception { + + this.aaiAdapter = viewInspectSynController.getAaiAdapter(); + this.esAdapter = viewInspectSynController.getElasticSearchAdapter(); + this.endpointConfig = viewInspectSynController.getendpointConfig(); + this.schemaConfig = viewInspectSynController.getschemaConfig(); + + this.visualizationService = new VisualizationService(oxmModelLoader, visualizationConfigs, + aaiAdapter, esAdapter, endpointConfig, schemaConfig); + this.activeInventoryNode = new ActiveInventoryNode(visualizationConfigs); + this.nodeMeta = new NodeMeta(visualizationConfigs); + secureRandom = new SecureRandom(); + this.tabularExecutorService = NodeUtils.createNamedExecutor("TABULAR-WORKER", + visualizationConfigs.getNumOfThreadsToFetchNodeIntegrity(), LOG); + /* + * Fix ActiveInvenotryConfig with properly wired in properties + */ + this.aaiConfig = ActiveInventoryConfig.getConfig(); + this.aaiExecutorService = NodeUtils.createNamedExecutor("SLNC-WORKER", + aaiConfig.getAaiRestConfig().getNumResolverWorkers(), LOG); + + this.visualizationContext = new VisualizationContext(secureRandom.nextLong(), aaiAdapter, + tabularExecutorService, aaiExecutorService, visualizationConfigs); + this.visualizationTransformer = new VisualizationTransformer(visualizationConfigs); + this.jsonNode = new JsonNode(activeInventoryNode, visualizationConfigs); + + } + + protected String generateJsonErrorResponse(String message) { + return String.format("{ \"errorMessage\" : %s }", message); + } + + public void processVisualizationRequest(Exchange exchange) { + + String visualizationPayload = ""; + QueryRequest hashId = null; + OperationResult operationResult = null; + Request request = null; + Response response = null; + Object xTransactionId = null; + Object partnerName = null; + + xTransactionId = exchange.getIn().getHeader("X-TransactionId"); + if (xTransactionId == null) { + xTransactionId = NodeUtils.getRandomTxnId(); + } + partnerName = exchange.getIn().getHeader("X-FromAppId"); + if (partnerName == null) { + partnerName = "Browser"; + } + + request = exchange.getIn().getHeader(RestletConstants.RESTLET_REQUEST, Request.class); + response = exchange.getIn().getHeader(RestletConstants.RESTLET_RESPONSE, Response.class); + + /* + * Disables automatic Apache Camel Restlet component logging which prints out an undesirable log + * entry which includes client (e.g. browser) information + */ + request.setLoggable(false); + + ClientInfo clientInfo = request.getClientInfo(); + MdcContext.initialize((String) xTransactionId, "AAI-UI", "", (String) partnerName, + clientInfo.getAddress() + ":" + clientInfo.getPort()); + + visualizationPayload = exchange.getIn().getBody(String.class); + hashId = visualizationService.analyzeQueryRequestBody(visualizationPayload); + + if (hashId != null) { + + operationResult = visualizationService.buildVisualizationUsingGenericQuery(hashId); + + if (operationResult.getResultCode() == Status.SUCCESS_OK.getCode()) { + + response.setStatus(Status.SUCCESS_OK); + } else { + response.setStatus(Status.SERVER_ERROR_INTERNAL); + LOG.error(AaiUiMsgs.FAILURE_TO_PROCESS_REQUEST, String + .format("Failed to process Visualization Schema Payload = '%s'", visualizationPayload)); + } + + } else { + operationResult = new OperationResult(); + operationResult.setResult(String + .format("Failed to analyze Visualization Schema Payload = '%s'", visualizationPayload)); + response.setStatus(Status.SERVER_ERROR_INTERNAL); + LOG.error(AaiUiMsgs.FAILED_TO_ANALYZE, String + .format("Failed to analyze Visualization Schema Payload = '%s'", visualizationPayload)); + + } + + + response.setEntity(operationResult.getResult(), MediaType.APPLICATION_JSON); + exchange.getOut().setBody(response); + } +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/config/TierSupportUiConstants.java b/src/main/java/org/onap/aai/sparky/viewandinspect/config/TierSupportUiConstants.java index 77a34da..7050595 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/config/TierSupportUiConstants.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/config/TierSupportUiConstants.java @@ -45,16 +45,28 @@ public class TierSupportUiConstants { public static String CONFIG_AUTH_LOCATION = CONFIG_HOME + "auth" + FILESEP; public static String HOST = "host"; + public static String IP_ADDRESS = "ipAddress"; public static String PORT = "port"; + public static String HTTP_PORT = "httpPort"; public static String RETRIES = "numRequestRetries"; public static String RESOURCE_VERSION = "resource-version"; public static String URI = "URI"; + public static String AUTHORIZED_USERS_FILE_LOCATION = + DYNAMIC_CONFIG_APP_LOCATION + "authorized-users.config"; 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"; + // Related to data-router properties + public static String DR_URI_SUFFIX = "uriSuffix"; + public static String DR_CERT_NAME = "cert-name"; + public static String DR_KEYSTORE_PASSWORD = "keystore-password"; + public static String DR_KEYSTORE = "keystore"; + public static String DR_CONNECT_TIMEOUT = "connectTimeoutMs"; + public static String DR_READ_TIMEOUT = "readTimeoutMs"; + public static final String ES_SUGGEST_API = "_suggest"; public static final String ES_COUNT_API = "_count"; public static final String ES_SEARCH_API = "_search"; @@ -74,10 +86,7 @@ public class TierSupportUiConstants { public static final String FILTER_MAPPING_FILE_DEFAULT = CONFIG_FILTERS_BASE_LOCATION + "filters" + FILESEP + "aaiui_views.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"; + public static final String SUGGESTION_TEXT_SEPARATOR = " -- "; // Injected Attributes public static String URI_ATTR_NAME = "uri"; @@ -88,339 +97,4 @@ public class TierSupportUiConstants { return AJSC_HOME + FILESEP + configFile; } - public static final String getAggregationIndexName(String entityType) { - return "aggregate_" + entityType + "_index"; - } - - /** - * @return the aPP_NAME - */ - public static String getAPP_NAME() { - return APP_NAME; - } - - /** - * @param aPP_NAME the aPP_NAME to set - */ - public static void setAPP_NAME(String aPP_NAME) { - APP_NAME = aPP_NAME; - } - - /** - * @return the cONFIG_HOME - */ - public static String getCONFIG_HOME() { - return CONFIG_HOME; - } - - /** - * @param cONFIG_HOME the cONFIG_HOME to set - */ - public static void setCONFIG_HOME(String cONFIG_HOME) { - CONFIG_HOME = cONFIG_HOME; - } - - /** - * @return the aJSC_HOME - */ - public static String getAJSC_HOME() { - return AJSC_HOME; - } - - /** - * @param aJSC_HOME the aJSC_HOME to set - */ - public static void setAJSC_HOME(String aJSC_HOME) { - AJSC_HOME = aJSC_HOME; - } - - /** - * @return the cONFIG_ROOT_LOCATION - */ - public static String getCONFIG_ROOT_LOCATION() { - return CONFIG_ROOT_LOCATION; - } - - /** - * @param cONFIG_ROOT_LOCATION the cONFIG_ROOT_LOCATION to set - */ - public static void setCONFIG_ROOT_LOCATION(String cONFIG_ROOT_LOCATION) { - CONFIG_ROOT_LOCATION = cONFIG_ROOT_LOCATION; - } - - /** - * @return the sTATIC_CONFIG_APP_LOCATION - */ - public static String getSTATIC_CONFIG_APP_LOCATION() { - return STATIC_CONFIG_APP_LOCATION; - } - - /** - * @param sTATIC_CONFIG_APP_LOCATION the sTATIC_CONFIG_APP_LOCATION to set - */ - public static void setSTATIC_CONFIG_APP_LOCATION(String sTATIC_CONFIG_APP_LOCATION) { - STATIC_CONFIG_APP_LOCATION = sTATIC_CONFIG_APP_LOCATION; - } - - /** - * @return the dYNAMIC_CONFIG_APP_LOCATION - */ - public static String getDYNAMIC_CONFIG_APP_LOCATION() { - return DYNAMIC_CONFIG_APP_LOCATION; - } - - /** - * @param dYNAMIC_CONFIG_APP_LOCATION the dYNAMIC_CONFIG_APP_LOCATION to set - */ - public static void setDYNAMIC_CONFIG_APP_LOCATION(String dYNAMIC_CONFIG_APP_LOCATION) { - DYNAMIC_CONFIG_APP_LOCATION = dYNAMIC_CONFIG_APP_LOCATION; - } - - /** - * @return the cONFIG_OXM_LOCATION - */ - public static String getCONFIG_OXM_LOCATION() { - return CONFIG_OXM_LOCATION; - } - - /** - * @param cONFIG_OXM_LOCATION the cONFIG_OXM_LOCATION to set - */ - public static void setCONFIG_OXM_LOCATION(String cONFIG_OXM_LOCATION) { - CONFIG_OXM_LOCATION = cONFIG_OXM_LOCATION; - } - - /** - * @return the cONFIG_FILTERS_BASE_LOCATION - */ - public static String getCONFIG_FILTERS_BASE_LOCATION() { - return CONFIG_FILTERS_BASE_LOCATION; - } - - /** - * @param cONFIG_FILTERS_BASE_LOCATION the cONFIG_FILTERS_BASE_LOCATION to set - */ - public static void setCONFIG_FILTERS_BASE_LOCATION(String cONFIG_FILTERS_BASE_LOCATION) { - CONFIG_FILTERS_BASE_LOCATION = cONFIG_FILTERS_BASE_LOCATION; - } - - - /** - * @return the cONFIG_AUTH_LOCATION - */ - public static String getCONFIG_AUTH_LOCATION() { - return CONFIG_AUTH_LOCATION; - } - - /** - * @param cONFIG_AUTH_LOCATION the cONFIG_AUTH_LOCATION to set - */ - public static void setCONFIG_AUTH_LOCATION(String cONFIG_AUTH_LOCATION) { - CONFIG_AUTH_LOCATION = cONFIG_AUTH_LOCATION; - } - - /** - * @return the hOST - */ - public static String getHOST() { - return HOST; - } - - /** - * @param hOST the hOST to set - */ - public static void setHOST(String hOST) { - HOST = hOST; - } - - /** - * @return the pORT - */ - public static String getPORT() { - return PORT; - } - - /** - * @param pORT the pORT to set - */ - public static void setPORT(String pORT) { - PORT = pORT; - } - - /** - * @return the rETRIES - */ - public static String getRETRIES() { - return RETRIES; - } - - /** - * @param rETRIES the rETRIES to set - */ - public static void setRETRIES(String rETRIES) { - RETRIES = rETRIES; - } - - /** - * @return the rESOURCE_VERSION - */ - public static String getRESOURCE_VERSION() { - return RESOURCE_VERSION; - } - - /** - * @param rESOURCE_VERSION the rESOURCE_VERSION to set - */ - public static void setRESOURCE_VERSION(String rESOURCE_VERSION) { - RESOURCE_VERSION = rESOURCE_VERSION; - } - - /** - * @return the uRI - */ - public static String getURI() { - return URI; - } - - /** - * @param uRI the uRI to set - */ - public static void setURI(String uRI) { - URI = uRI; - } - - /** - * @return the uSERS_FILE_LOCATION - */ - public static String getUSERS_FILE_LOCATION() { - return USERS_FILE_LOCATION; - } - - /** - * @param uSERS_FILE_LOCATION the uSERS_FILE_LOCATION to set - */ - public static void setUSERS_FILE_LOCATION(String uSERS_FILE_LOCATION) { - USERS_FILE_LOCATION = uSERS_FILE_LOCATION; - } - - /** - * @return the rOLES_FILE_LOCATION - */ - public static String getROLES_FILE_LOCATION() { - return ROLES_FILE_LOCATION; - } - - /** - * @param rOLES_FILE_LOCATION the rOLES_FILE_LOCATION to set - */ - public static void setROLES_FILE_LOCATION(String rOLES_FILE_LOCATION) { - ROLES_FILE_LOCATION = rOLES_FILE_LOCATION; - } - - /** - * @return the pORTAL_AUTHENTICATION_FILE_LOCATION - */ - public static String getPORTAL_AUTHENTICATION_FILE_LOCATION() { - return PORTAL_AUTHENTICATION_FILE_LOCATION; - } - - /** - * @param pORTAL_AUTHENTICATION_FILE_LOCATION the pORTAL_AUTHENTICATION_FILE_LOCATION to set - */ - public static void setPORTAL_AUTHENTICATION_FILE_LOCATION( - String pORTAL_AUTHENTICATION_FILE_LOCATION) { - PORTAL_AUTHENTICATION_FILE_LOCATION = pORTAL_AUTHENTICATION_FILE_LOCATION; - } - - /** - * @return the tEST_CONFIG_FILE - */ - public static String getTEST_CONFIG_FILE() { - return TEST_CONFIG_FILE; - } - - /** - * @param tEST_CONFIG_FILE the tEST_CONFIG_FILE to set - */ - public static void setTEST_CONFIG_FILE(String tEST_CONFIG_FILE) { - TEST_CONFIG_FILE = tEST_CONFIG_FILE; - } - - /** - * @return the uRI_ATTR_NAME - */ - public static String getURI_ATTR_NAME() { - return URI_ATTR_NAME; - } - - /** - * @param uRI_ATTR_NAME the uRI_ATTR_NAME to set - */ - public static void setURI_ATTR_NAME(String uRI_ATTR_NAME) { - URI_ATTR_NAME = uRI_ATTR_NAME; - } - - /** - * @return the filesep - */ - public static String getFilesep() { - return FILESEP; - } - - /** - * @return the esSuggestApi - */ - public static String getEsSuggestApi() { - return ES_SUGGEST_API; - } - - /** - * @return the esCountApi - */ - public static String getEsCountApi() { - return ES_COUNT_API; - } - - /** - * @return the esSearchApi - */ - public static String getEsSearchApi() { - return ES_SEARCH_API; - } - - /** - * @return the entityAutoSuggestIndexNameDefault - */ - public static String getEntityAutoSuggestIndexNameDefault() { - return ENTITY_AUTO_SUGGEST_INDEX_NAME_DEFAULT; - } - - /** - * @return the entityAutoSuggestSettingsFileDefault - */ - public static String getEntityAutoSuggestSettingsFileDefault() { - return ENTITY_AUTO_SUGGEST_SETTINGS_FILE_DEFAULT; - } - - /** - * @return the entityAutoSuggestMappingsFileDefault - */ - public static String getEntityAutoSuggestMappingsFileDefault() { - return ENTITY_AUTO_SUGGEST_MAPPINGS_FILE_DEFAULT; - } - - /** - * @return the entityDynamicMappingsFileDefault - */ - public static String getEntityDynamicMappingsFileDefault() { - return ENTITY_DYNAMIC_MAPPINGS_FILE_DEFAULT; - } - - /** - * @return the uriVersionRegexPattern - */ - public static String getUriVersionRegexPattern() { - return URI_VERSION_REGEX_PATTERN; - } - } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfig.java b/src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigs.java similarity index 68% rename from src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfig.java rename to src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigs.java index 77f3d97..9fc9030 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfig.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/config/VisualizationConfigs.java @@ -22,14 +22,10 @@ */ package org.onap.aai.sparky.viewandinspect.config; -import java.util.Properties; - -import org.onap.aai.sparky.util.ConfigHelper; - /** * The Class VisualizationConfig. */ -public class VisualizationConfig { +public class VisualizationConfigs { private int maxSelfLinkTraversalDepth; @@ -43,53 +39,17 @@ public class VisualizationConfig { private String selectedSearchedNodeClassName; + private int numOfThreadsToFetchNodeIntegrity; + 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")); - - } + public VisualizationConfigs() {} @@ -154,6 +114,14 @@ public class VisualizationConfig { return maxSelfLinkTraversalDepth; } + public int getNumOfThreadsToFetchNodeIntegrity() { + return numOfThreadsToFetchNodeIntegrity; + } + + public void setNumOfThreadsToFetchNodeIntegrity(int numOfThreadsToFetchNodeIntegrity) { + this.numOfThreadsToFetchNodeIntegrity = numOfThreadsToFetchNodeIntegrity; + } + public String getEntityTypesToSummarize() { return entityTypesToSummarize; } @@ -170,30 +138,11 @@ public class VisualizationConfig { this.vnfEntityTypes = vnfEntityTypes; } - /** - * @return the instance - */ - public static VisualizationConfig getInstance() { - return instance; - } - /** - * @param instance the instance to set - */ - public static void setInstance(VisualizationConfig instance) { - VisualizationConfig.instance = instance; - } - - /** - * @return the makeAllNeighborsBidirectional - */ - public boolean isMakeAllNeighborsBidirectional() { - return makeAllNeighborsBidirectional; - } @Override public String toString() { - return "VisualizationConfig [maxSelfLinkTraversalDepth=" + maxSelfLinkTraversalDepth + return "VisualizationConfigs [maxSelfLinkTraversalDepth=" + maxSelfLinkTraversalDepth + ", visualizationDebugEnabled=" + visualizationDebugEnabled + ", " + (aaiEntityNodeDescriptors != null ? "aaiEntityNodeDescriptors=" + aaiEntityNodeDescriptors + ", " : "") @@ -202,6 +151,7 @@ public class VisualizationConfig { + (searchNodeClassName != null ? "searchNodeClassName=" + searchNodeClassName + ", " : "") + (selectedSearchedNodeClassName != null ? "selectedSearchedNodeClassName=" + selectedSearchedNodeClassName + ", " : "") + + "numOfThreadsToFetchNodeIntegrity=" + numOfThreadsToFetchNodeIntegrity + ", " + (entityTypesToSummarize != null ? "entityTypesToSummarize=" + entityTypesToSummarize + ", " : "") + (vnfEntityTypes != null ? "vnfEntityTypes=" + vnfEntityTypes + ", " : "") diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java index d87aad8..8d74d68 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/ActiveInventoryNode.java @@ -36,14 +36,15 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; import org.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.viewandinspect.config.VisualizationConfig; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction; import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -70,7 +71,6 @@ public class ActiveInventoryNode { private int nodeDepth; private OperationResult opResult; - private boolean processingErrorOccurred; private List errorCauses; private boolean selflinkRetrievalFailure; @@ -88,14 +88,16 @@ public class ActiveInventoryNode { private boolean selfLinkDeterminationPending; private AtomicBoolean selfLinkProcessed; + private AtomicBoolean nodeIntegrityProcessed; private OxmModelLoader oxmModelLoader; - private VisualizationConfig visualizationConfig; + private VisualizationConfigs visualizationConfigs; private String entityType; private String primaryKeyName; private String primaryKeyValue; + private boolean nodeValidated; private boolean nodeIssue; private boolean ignoredByFilter; @@ -106,19 +108,12 @@ public class ActiveInventoryNode { 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) { + public ActiveInventoryNode(VisualizationConfigs visualizationConfigs) { this.nodeId = null; this.entityType = null; this.selfLink = null; @@ -127,13 +122,15 @@ public class ActiveInventoryNode { this.errorCauses = new ArrayList(); this.selflinkRetrievalFailure = false; this.nodeIssue = false; + this.nodeValidated = false; this.state = NodeProcessingState.INIT; this.selfLinkPendingResolve = false; this.selfLinkDeterminationPending = false; selfLinkProcessed = new AtomicBoolean(Boolean.FALSE); + nodeIntegrityProcessed = new AtomicBoolean(Boolean.FALSE); oxmModelLoader = null; - visualizationConfig = null; + this.visualizationConfigs = visualizationConfigs; isRootNode = false; inboundNeighbors = new ConcurrentLinkedDeque(); @@ -165,7 +162,7 @@ public class ActiveInventoryNode { public void addQueryParams(Collection params) { - if (params != null && !params.isEmpty()) { + if (params != null & params.size() > 0) { for (String param : params) { addQueryParam(param); @@ -215,8 +212,8 @@ public class ActiveInventoryNode { * * @return the visualization config */ - public VisualizationConfig getvisualizationConfig() { - return visualizationConfig; + public VisualizationConfigs getvisualizationConfigs() { + return visualizationConfigs; } public int getNodeDepth() { @@ -232,8 +229,8 @@ public class ActiveInventoryNode { * * @param visualizationConfig the new visualization config */ - public void setvisualizationConfig(VisualizationConfig visualizationConfig) { - this.visualizationConfig = visualizationConfig; + public void setvisualizationConfig(VisualizationConfigs visualizationConfigs) { + this.visualizationConfigs = visualizationConfigs; } public OxmModelLoader getOxmModelLoader() { @@ -252,6 +249,14 @@ public class ActiveInventoryNode { this.primaryKeyValue = primaryKeyValue; } + public boolean isNodeValidated() { + return nodeValidated; + } + + public void setNodeValidated(boolean nodeValidated) { + this.nodeValidated = nodeValidated; + } + public boolean isNodeIssue() { return nodeIssue; } @@ -338,7 +343,7 @@ public class ActiveInventoryNode { } public boolean isAtMaxDepth() { - return (nodeDepth >= VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()); + return (nodeDepth >= this.visualizationConfigs.getMaxSelfLinkTraversalDepth()); } public ConcurrentLinkedDeque getInboundNeighbors() { @@ -443,8 +448,16 @@ public class ActiveInventoryNode { this.selfLinkProcessed.set(selfLinkProcessed); } + public boolean getNodeIntegrityProcessed() { + return nodeIntegrityProcessed.get(); + } + + public void setNodeIntegrityProcessed(boolean nodeIntegrityProcessed) { + this.nodeIntegrityProcessed.set(nodeIntegrityProcessed); + } + public boolean isDirectSelfLink() { - // https://:8443/aai/v8/resources/id/2458124400 + // https://aai-int1.test.att.com:8443/aai/v8/resources/id/2458124400 return isDirectSelfLink(this.selfLink); } @@ -455,7 +468,7 @@ public class ActiveInventoryNode { * @return true, if is direct self link */ public static boolean isDirectSelfLink(String link) { - // https://:8443/aai/v8/resources/id/2458124400 + // https://aai-int1.test.att.com:8443/aai/v8/resources/id/2458124400 if (link == null) { return false; @@ -625,7 +638,7 @@ public class ActiveInventoryNode { * probably more likely just for array node types, but we'll see. */ - if (oxmModelLoader.getEntityDescriptor(fieldName) == null) { + if (OxmEntityLookup.getInstance().getEntityDescriptors().get(fieldName) == null) { /* * this is no an entity type as far as we can tell, so we can add it to our property * set. @@ -645,7 +658,8 @@ public class ActiveInventoryNode { * complex group or relationship. */ - if (oxmModelLoader.getEntityDescriptor(field.getKey()) == null) { + if (OxmEntityLookup.getInstance().getEntityDescriptors() + .get(field.getKey()) == null) { /* * this is no an entity type as far as we can tell, so we can add it to our property * set. diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/D3VisualizationOutput.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/D3VisualizationOutput.java index e29f6df..69971c5 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/D3VisualizationOutput.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/D3VisualizationOutput.java @@ -22,10 +22,6 @@ */ package org.onap.aai.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; @@ -91,67 +87,5 @@ public class D3VisualizationOutput { this.inlineMessage = inlineMessage; } - /** - * @return the nodes - */ - public List getNodes() { - return nodes; - } - - /** - * @param nodes the nodes to set - */ - public void setNodes(List nodes) { - this.nodes = nodes; - } - - /** - * @return the links - */ - public List getLinks() { - return links; - } - - /** - * @param links the links to set - */ - public void setLinks(List links) { - this.links = links; - } - - /** - * 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/onap/aai/sparky/suggestivesearch/SuggestionEntity.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GraphRequest.java similarity index 68% rename from src/main/java/org/onap/aai/sparky/suggestivesearch/SuggestionEntity.java rename to src/main/java/org/onap/aai/sparky/viewandinspect/entity/GraphRequest.java index 92be827..678a00f 100644 --- a/src/main/java/org/onap/aai/sparky/suggestivesearch/SuggestionEntity.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/GraphRequest.java @@ -1,61 +1,56 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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; - } -} +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect.entity; + +public class GraphRequest { + + private String hashId; + private boolean includeGraphMeta; + + public GraphRequest() { + + } + + public String getHashId() { + return hashId; + } + + public void setHashId(String hashId) { + this.hashId = hashId; + } + + public boolean isIncludeGraphMeta() { + return includeGraphMeta; + } + + public void setIncludeGraphMeta(boolean includeGraphMeta) { + this.includeGraphMeta = includeGraphMeta; + } + + @Override + public String toString() { + return "QueryRequest [" + (hashId != null ? "hashId=" + hashId + ", " : "") + + "includeGraphMeta=" + includeGraphMeta + "]"; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/JsonNode.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/JsonNode.java index 3f9d0f2..4d1c458 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/JsonNode.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/JsonNode.java @@ -22,13 +22,14 @@ */ package org.onap.aai.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; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; + +import com.fasterxml.jackson.annotation.JsonIgnore; /* * We can use annotations to differentiate between intermediate data we use to build the node, and @@ -77,12 +78,15 @@ public class JsonNode { @JsonIgnore private static final Logger LOG = Logger.getLogger(JsonNode.class); + private VisualizationConfigs visualizationConfigs; + + /** * Instantiates a new json node. * * @param ain the ain */ - public JsonNode(ActiveInventoryNode ain) { + public JsonNode(ActiveInventoryNode ain, VisualizationConfigs visualizationConfigs) { this.resourceKey = ain.getNodeId(); this.itemProperties = ain.getProperties(); this.setItemType(ain.getEntityType()); @@ -90,6 +94,7 @@ public class JsonNode { this.setItemNameValue(ain.getPrimaryKeyValue()); this.setId(ain.getNodeId()); this.isRootNode = ain.isRootNode(); + this.visualizationConfigs = visualizationConfigs; if (LOG.isDebugEnabled()) { LOG.debug("---"); @@ -100,9 +105,10 @@ public class JsonNode { inboundNeighbors = ain.getInboundNeighbors(); outboundNeighbors = ain.getOutboundNeighbors(); - nodeMeta = new NodeMeta(); + nodeMeta = new NodeMeta(this.visualizationConfigs); nodeMeta.setNodeIssue(ain.isNodeIssue()); + nodeMeta.setNodeValidated(ain.isNodeValidated()); nodeMeta.setNodeDepth(ain.getNodeDepth()); nodeMeta.setNumInboundNeighbors(ain.getInboundNeighbors().size()); @@ -177,55 +183,11 @@ public class JsonNode { return isRootNode; } - /** - * @return the inboundNeighbors - */ - public Collection getInboundNeighbors() { - return inboundNeighbors; - } - - /** - * @param inboundNeighbors the inboundNeighbors to set - */ - public void setInboundNeighbors(Collection inboundNeighbors) { - this.inboundNeighbors = inboundNeighbors; - } - - /** - * @return the outboundNeighbors - */ - public Collection getOutboundNeighbors() { - return outboundNeighbors; - } - - /** - * @param outboundNeighbors the outboundNeighbors to set + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() */ - public void setOutboundNeighbors(Collection outboundNeighbors) { - this.outboundNeighbors = outboundNeighbors; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - - /** - * @param itemProperties the itemProperties to set - */ - public void setItemProperties(Map itemProperties) { - this.itemProperties = itemProperties; - } - - /** - * @param isRootNode the isRootNode to set - */ - public void setRootNode(boolean isRootNode) { - this.isRootNode = isRootNode; - } - @Override public String toString() { return "JsonNode [" + (id != null ? "id=" + id + ", " : "") @@ -233,8 +195,8 @@ public class JsonNode { + (itemNameKey != null ? "itemNameKey=" + itemNameKey + ", " : "") + (itemNameValue != null ? "itemNameValue=" + itemNameValue + ", " : "") + (itemProperties != null ? "itemProperties=" + itemProperties + ", " : "") - + (nodeMeta != null ? "nodeMeta=" + nodeMeta + ", " : "") + "isRootNode=" + isRootNode - + ", " + (resourceKey != null ? "resourceKey=" + resourceKey + ", " : "") + + (nodeMeta != null ? "nodeMeta=" + nodeMeta + ", " : "") + + (resourceKey != null ? "resourceKey=" + resourceKey + ", " : "") + (inboundNeighbors != null ? "inboundNeighbors=" + inboundNeighbors + ", " : "") + (outboundNeighbors != null ? "outboundNeighbors=" + outboundNeighbors : "") + "]"; } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/NodeMeta.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/NodeMeta.java index c55f838..26a027f 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/NodeMeta.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/NodeMeta.java @@ -22,7 +22,7 @@ */ package org.onap.aai.sparky.viewandinspect.entity; -import org.onap.aai.sparky.viewandinspect.config.VisualizationConfig; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState; /** @@ -32,7 +32,6 @@ public class NodeMeta { private String className; - private boolean isEnrichableNode; private boolean isSearchTarget; private NodeDebug nodeDebug; @@ -51,14 +50,19 @@ public class NodeMeta { private NodeProcessingState processingState; + private VisualizationConfigs visualizationConfigs; + + + /** * Instantiates a new node meta. */ - public NodeMeta() { + public NodeMeta(VisualizationConfigs visualizationConfigs) { this.isSearchTarget = false; - this.isEnrichableNode = false; + this.visualizationConfigs = visualizationConfigs; + - if (VisualizationConfig.getConfig().isVisualizationDebugEnabled()) { + if (this.visualizationConfigs.isVisualizationDebugEnabled()) { nodeDebug = new NodeDebug(); } this.numInboundNeighbors = 0; @@ -166,10 +170,6 @@ public class NodeMeta { return selfLinkResponseTimeInMs; } - public boolean isEnrichableNode() { - return isEnrichableNode; - } - public boolean isNodeIssue() { return nodeIssue; } @@ -186,10 +186,6 @@ public class NodeMeta { this.className = className; } - public void setEnrichableNode(boolean isEnrichableNode) { - this.isEnrichableNode = isEnrichableNode; - } - public void setNodeIssue(boolean nodeIssue) { this.nodeIssue = nodeIssue; } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/NodeProcessingTransaction.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/NodeProcessingTransaction.java index ca55f09..22bea15 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/NodeProcessingTransaction.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/NodeProcessingTransaction.java @@ -22,7 +22,7 @@ */ package org.onap.aai.sparky.viewandinspect.entity; -import org.onap.aai.sparky.dal.rest.OperationResult; +import org.onap.aai.restclient.client.OperationResult; /** * The Class NodeProcessingTransaction. diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/Relationship.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/Relationship.java index 7e5519c..135ddcc 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/Relationship.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/Relationship.java @@ -22,10 +22,10 @@ */ package org.onap.aai.sparky.viewandinspect.entity; -import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.Arrays; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * The Class Relationship. */ diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/RelationshipList.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/RelationshipList.java index 8dd61d4..d758543 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/RelationshipList.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/RelationshipList.java @@ -22,10 +22,10 @@ */ package org.onap.aai.sparky.viewandinspect.entity; -import com.fasterxml.jackson.annotation.JsonProperty; - import java.util.Arrays; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * The Class RelationshipList. */ @@ -42,20 +42,6 @@ public class RelationshipList { this.relationship = relationship; } - /** - * @return the relationship - */ - public Relationship[] getRelationship() { - return relationship; - } - - /** - * @param relationship the relationship to set - */ - public void setRelationship(Relationship[] relationship) { - this.relationship = relationship; - } - /* * (non-Javadoc) * diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SearchableEntityList.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SearchableEntityList.java new file mode 100644 index 0000000..bed2602 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SearchableEntityList.java @@ -0,0 +1,115 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect.entity; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.onap.aai.sparky.sync.entity.SearchableEntity; + +import java.util.Set; + +public class SearchableEntityList { + + private List entities; + + public SearchableEntityList() { + entities = new ArrayList(); + } + + public List getEntities() { + return entities; + } + + public void setEntities(List entities) { + this.entities = entities; + } + + public void addEntity(SearchableEntity entity) { + + if (!entities.contains(entity)) { + entities.add(entity); + } + + } + + protected static SearchableEntity buildEntity(String entityType, String pkeyValue, String link, + Map searchTags) { + + SearchableEntity se = new SearchableEntity(); + + se.setEntityType(entityType); + se.setEntityPrimaryKeyValue(pkeyValue); + se.setLink(link); + + if (searchTags != null) { + + Set> entrySet = searchTags.entrySet(); + + for (Entry entry : entrySet) { + se.addSearchTagWithKey(entry.getKey(), entry.getValue()); + } + } + + se.deriveFields(); + + return se; + + } + + protected static Map getSearchTagMap(String... tags) { + + HashMap dataMap = new HashMap(); + + if (tags != null && tags.length >= 2) { + + int numTags = tags.length; + int index = 0; + + while (index < numTags) { + + if (index + 1 < numTags) { + // we have enough parameters for the current set + dataMap.put(tags[index], tags[index + 1]); + index += 2; + } else { + break; + } + } + + } + + return dataMap; + + + } + + @Override + public String toString() { + return "SearchableEntityList [" + (entities != null ? "entities=" + entities : "") + "]"; + } + +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SelfLinkDeterminationTransaction.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SelfLinkDeterminationTransaction.java index 21af9cf..204b930 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SelfLinkDeterminationTransaction.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/SelfLinkDeterminationTransaction.java @@ -22,7 +22,7 @@ */ package org.onap.aai.sparky.viewandinspect.entity; -import org.onap.aai.sparky.dal.rest.OperationResult; +import org.onap.aai.restclient.client.OperationResult; public class SelfLinkDeterminationTransaction { @@ -33,7 +33,6 @@ public class SelfLinkDeterminationTransaction { private OperationResult opResult; - public String getParentNodeId() { return parentNodeId; } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/Violations.java b/src/main/java/org/onap/aai/sparky/viewandinspect/entity/Violations.java deleted file mode 100644 index 4968de4..0000000 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/entity/Violations.java +++ /dev/null @@ -1,125 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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; - } - - /** - * @return the details - */ - public String getDetails() { - return details; - } - - /** - * @param details the details to set - */ - public void setDetails(String details) { - this.details = details; - } - - -} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/enumeration/NodeProcessingAction.java b/src/main/java/org/onap/aai/sparky/viewandinspect/enumeration/NodeProcessingAction.java index 2550ed7..b7038bf 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/enumeration/NodeProcessingAction.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/enumeration/NodeProcessingAction.java @@ -28,3 +28,4 @@ package org.onap.aai.sparky.viewandinspect.enumeration; 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/onap/aai/sparky/viewandinspect/search/ViewInspectSearchProvider.java b/src/main/java/org/onap/aai/sparky/viewandinspect/search/ViewInspectSearchProvider.java new file mode 100644 index 0000000..5101c28 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/search/ViewInspectSearchProvider.java @@ -0,0 +1,440 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewandinspect.search; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.common.search.CommonSearchSuggestion; +import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; +import org.onap.aai.sparky.config.oxm.OxmModelLoader; +import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; +import org.onap.aai.sparky.dal.sas.config.SearchServiceConfig; +import org.onap.aai.sparky.logging.AaiUiMsgs; +import org.onap.aai.sparky.search.api.SearchProvider; +import org.onap.aai.sparky.search.config.SuggestionConfig; +import org.onap.aai.sparky.search.entity.QuerySearchEntity; +import org.onap.aai.sparky.search.entity.SearchSuggestion; +import org.onap.aai.sparky.util.NodeUtils; +import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +public class ViewInspectSearchProvider implements SearchProvider { + + private static final Logger LOG = + LoggerFactory.getInstance().getLogger(ViewInspectSearchProvider.class); + + private SearchServiceConfig sasConfig = null; + private SearchAdapter search = null; + private OxmModelLoader oxmModelLoader; + private String additionalSearchSuggestionText; + + private static final String KEY_SEARCH_RESULT = "searchResult"; + private static final String KEY_HITS = "hits"; + private static final String KEY_DOCUMENT = "document"; + private static final String KEY_CONTENT = "content"; + + private static final String VI_SUGGESTION_ROUTE = "schema"; // TODO -> Read route from + // suggestive-search.properties + // instead of hard coding + + 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 VALUE_QUERY = "query"; + + public ViewInspectSearchProvider(OxmModelLoader oxmModelLoader) throws Exception { + + sasConfig = SearchServiceConfig.getConfig(); + search = new SearchAdapter(); + suggestionConfig = SuggestionConfig.getConfig(); + this.oxmModelLoader = oxmModelLoader; + additionalSearchSuggestionText = null; + + } + + @Override + public List search(QuerySearchEntity queryRequest) { + + 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(queryRequest.getQueryStr()); + + final String fullUrlStr = getSasFullUrl(sasConfig.getIndexName(), VALUE_QUERY, + sasConfig.getIpAddress(), sasConfig.getHttpPort(), sasConfig.getVersion()); + + String postBody = String.format(VIUI_SEARCH_TEMPLATE, + Integer.parseInt(queryRequest.getMaxResults()), queryStringWithoutStopWords); + + OperationResult opResult = search.doPost(fullUrlStr, postBody, "application/json"); + if (opResult.getResultCode() == 200) { + suggestionEntityList = + generateSuggestionsForSearchResponse(opResult.getResult(), queryRequest.getQueryStr()); + } + } catch (Exception exc) { + LOG.error(AaiUiMsgs.SEARCH_SERVLET_ERROR, + "View and inspect query failed with error = " + exc.getMessage()); + } + return suggestionEntityList; + + + } + + public String getAdditionalSearchSuggestionText() { + return additionalSearchSuggestionText; + } + + public void setAdditionalSearchSuggestionText(String additionalSearchSuggestionText) { + this.additionalSearchSuggestionText = additionalSearchSuggestionText; + } + + /** + * 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); + } + + + + /** + * Builds the search response. + * + * @param operationResult The Elasticsearch query result + * @param queryStr The string the user typed into the search bar + * @return A list of search suggestions and corresponding UI filter values + */ + 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; + CommonSearchSuggestion 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 CommonSearchSuggestion(); + 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 entityType = getValueFromNode(sourceNode, KEY_ENTITY_TYPE); + String link = getValueFromNode(sourceNode, KEY_LINK); + + 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 (getAdditionalSearchSuggestionText() != null) { + String suggestionText = suggestionEntity.getText(); + suggestionText += TierSupportUiConstants.SUGGESTION_TEXT_SEPARATOR + + getAdditionalSearchSuggestionText(); + suggestionEntity.setText(suggestionText); + } + + if (searchTags != null) { + suggestionEntityList.add(suggestionEntity); + } + + } + } + } catch (IOException exc) { + LOG.warn(AaiUiMsgs.SEARCH_RESPONSE_BUILDING_EXCEPTION, exc.getLocalizedMessage()); + } + return suggestionEntityList; + } + + + + /** + * 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 = OxmEntityLookup.getInstance().getEntityDescriptors().get(entityType); + + if (desc == null) { + LOG.error(AaiUiMsgs.ENTITY_NOT_FOUND_IN_OXM, entityType.toString()); + return searchTags; + } + + String primaryKeyName = NodeUtils.concatArray(desc.getPrimaryKeyAttributeNames(), "/"); + 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(); + + } + + /** + * 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; + + } + + /** + * 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; + + } + + 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\"" + "}" + "}" + "}]" + "}"; + + private SuggestionConfig suggestionConfig = null; + + /** + * @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); + } + +} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/services/SearchServiceWrapper.java b/src/main/java/org/onap/aai/sparky/viewandinspect/services/SearchServiceWrapper.java deleted file mode 100644 index ebce18e..0000000 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/services/SearchServiceWrapper.java +++ /dev/null @@ -1,980 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.config.oxm.OxmEntityDescriptor; -import org.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.dal.elasticsearch.HashQueryResponse; -import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.sas.config.SearchServiceConfig; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.search.VnfSearchService; -import org.onap.aai.sparky.search.config.SuggestionConfig; -import org.onap.aai.sparky.suggestivesearch.SuggestionEntity; -import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.sparky.viewandinspect.entity.QuerySearchEntity; -import org.onap.aai.sparky.viewandinspect.entity.SearchResponse; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; - -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(); - } - } - - /** - * @return the mapper - */ - public ObjectMapper getMapper() { - return mapper; - } - - /** - * @param mapper the mapper to set - */ - public void setMapper(ObjectMapper mapper) { - this.mapper = mapper; - } - - /** - * @return the serialversionuid - */ - public static long getSerialversionuid() { - return serialVersionUID; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - - /** - * @return the searchString - */ - public static String getSearchString() { - return SEARCH_STRING; - } - - /** - * @return the countString - */ - public static String getCountString() { - return COUNT_STRING; - } - - /** - * @return the querySearch - */ - public static String getQuerySearch() { - return QUERY_SEARCH; - } - - /** - * @return the summaryByEntityTypeApi - */ - public static String getSummaryByEntityTypeApi() { - return SUMMARY_BY_ENTITY_TYPE_API; - } - - /** - * @return the summaryByEntityTypeCountApi - */ - public static String getSummaryByEntityTypeCountApi() { - return SUMMARY_BY_ENTITY_TYPE_COUNT_API; - } - - /** - * @return the valueAnykey - */ - public static String getValueAnykey() { - return VALUE_ANYKEY; - } - - /** - * @return the valueQuery - */ - public static String getValueQuery() { - return VALUE_QUERY; - } - - /** - * @return the keyHashId - */ - public static String getKeyHashId() { - return KEY_HASH_ID; - } - - /** - * @return the keyGroupBy - */ - public static String getKeyGroupBy() { - return KEY_GROUP_BY; - } - - /** - * @return the keySearchResult - */ - public static String getKeySearchResult() { - return KEY_SEARCH_RESULT; - } - - /** - * @return the keyHits - */ - public static String getKeyHits() { - return KEY_HITS; - } - - /** - * @return the keyPayload - */ - public static String getKeyPayload() { - return KEY_PAYLOAD; - } - - /** - * @return the keyDocument - */ - public static String getKeyDocument() { - return KEY_DOCUMENT; - } - - /** - * @return the keyContent - */ - public static String getKeyContent() { - return KEY_CONTENT; - } - - /** - * @return the keySearchTagIds - */ - public static String getKeySearchTagIds() { - return KEY_SEARCH_TAG_IDS; - } - - /** - * @return the keySearchTags - */ - public static String getKeySearchTags() { - return KEY_SEARCH_TAGS; - } - - /** - * @return the keyLink - */ - public static String getKeyLink() { - return KEY_LINK; - } - - /** - * @return the keyEntityType - */ - public static String getKeyEntityType() { - return KEY_ENTITY_TYPE; - } - - /** - * @return the viSuggestionRoute - */ - public static String getViSuggestionRoute() { - return VI_SUGGESTION_ROUTE; - } - - /** - * @return the viuiSearchTemplate - */ - public static String getViuiSearchTemplate() { - return VIUI_SEARCH_TEMPLATE; - } - - - -} diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationContext.java b/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationContext.java index e3f469f..b2ed4a4 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationContext.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationContext.java @@ -36,16 +36,19 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicInteger; import org.apache.http.client.utils.URIBuilder; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; import org.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.entity.SearchableEntity; +import org.onap.aai.sparky.sync.entity.SearchableEntity; import org.onap.aai.sparky.util.NodeUtils; import org.onap.aai.sparky.viewandinspect.config.TierSupportUiConstants; -import org.onap.aai.sparky.viewandinspect.config.VisualizationConfig; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode; import org.onap.aai.sparky.viewandinspect.entity.InlineMessage; import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction; @@ -58,8 +61,6 @@ import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingAction; import org.onap.aai.sparky.viewandinspect.enumeration.NodeProcessingState; import org.onap.aai.sparky.viewandinspect.task.PerformNodeSelfLinkProcessingTask; import org.onap.aai.sparky.viewandinspect.task.PerformSelfLinkDeterminationTask; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.JsonNode; @@ -78,17 +79,18 @@ public class VisualizationContext { private static final Logger LOG = LoggerFactory.getInstance().getLogger(VisualizationContext.class); - private final ActiveInventoryDataProvider aaiProvider; + private final ActiveInventoryAdapter aaiAdapter; private int maxSelfLinkTraversalDepth; private AtomicInteger numLinksDiscovered; private AtomicInteger numSuccessfulLinkResolveFromCache; private AtomicInteger numSuccessfulLinkResolveFromFromServer; private AtomicInteger numFailedLinkResolve; + private AtomicInteger nodeIntegrityWorkOnHand; private AtomicInteger aaiWorkOnHand; private ActiveInventoryConfig aaiConfig; - private VisualizationConfig visualizationConfig; + private VisualizationConfigs visualizationConfigs; private List shallowEntities; private AtomicInteger totalLinksRetrieved; @@ -100,6 +102,7 @@ public class VisualizationContext { private ObjectMapper mapper; private InlineMessage inlineMessage = null; + private ExecutorService tabularExecutorService; private ExecutorService aaiExecutorService; /* @@ -115,14 +118,16 @@ public class VisualizationContext { * @param loader the loader * @throws Exception the exception */ - public VisualizationContext(long contextId, ActiveInventoryDataProvider aaiDataProvider, - ExecutorService aaiExecutorService, OxmModelLoader loader) throws Exception { + public VisualizationContext(long contextId, ActiveInventoryAdapter aaiAdapter, + ExecutorService tabularExecutorService, ExecutorService aaiExecutorService, + VisualizationConfigs visualizationConfigs) throws Exception { this.contextId = contextId; this.contextIdStr = "[Context-Id=" + contextId + "]"; - this.aaiProvider = aaiDataProvider; + this.aaiAdapter = aaiAdapter; + this.tabularExecutorService = tabularExecutorService; this.aaiExecutorService = aaiExecutorService; - this.loader = loader; + this.visualizationConfigs = visualizationConfigs; this.nodeCache = new ConcurrentHashMap(); this.numLinksDiscovered = new AtomicInteger(0); @@ -130,13 +135,13 @@ public class VisualizationContext { this.numSuccessfulLinkResolveFromCache = new AtomicInteger(0); this.numSuccessfulLinkResolveFromFromServer = new AtomicInteger(0); this.numFailedLinkResolve = new AtomicInteger(0); + this.nodeIntegrityWorkOnHand = 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.maxSelfLinkTraversalDepth = this.visualizationConfigs.getMaxSelfLinkTraversalDepth(); this.mapper = new ObjectMapper(); mapper.setSerializationInclusion(Include.NON_EMPTY); @@ -164,7 +169,8 @@ public class VisualizationContext { return queryParams; } - Map entityDescriptors = loader.getEntityDescriptors(); + Map entityDescriptors = + OxmEntityLookup.getInstance().getEntityDescriptors(); try { @@ -183,7 +189,7 @@ public class VisualizationContext { if (descriptor != null) { entityType = urlPathElements[index]; - primaryKeyNames = descriptor.getPrimaryKeyAttributeName(); + primaryKeyNames = descriptor.getPrimaryKeyAttributeNames(); /* * Make sure from what ever index we matched the parent entity-type on that we can extract @@ -270,7 +276,7 @@ public class VisualizationContext { * */ - ActiveInventoryNode newNode = new ActiveInventoryNode(); + ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs); newNode.setEntityType(entityType); /* @@ -337,7 +343,7 @@ public class VisualizationContext { */ String selfLinkQuery = - aaiProvider.getGenericQueryForSelfLink(entityType, newNode.getQueryParams()); + aaiAdapter.getGenericQueryForSelfLink(entityType, newNode.getQueryParams()); /** *
  • get the self-link @@ -355,7 +361,7 @@ public class VisualizationContext { txn.setNewNode(newNode); txn.setParentNodeId(ain.getNodeId()); aaiWorkOnHand.incrementAndGet(); - supplyAsync(new PerformSelfLinkDeterminationTask(txn, null, aaiProvider), + supplyAsync(new PerformSelfLinkDeterminationTask(txn, null, aaiAdapter), aaiExecutorService).whenComplete((nodeTxn, error) -> { aaiWorkOnHand.decrementAndGet(); if (error != null) { @@ -368,15 +374,13 @@ public class VisualizationContext { if (opResult != null && opResult.wasSuccessful()) { - if (opResult.isResolvedLinkFailure()) { + if (!opResult.wasSuccessful()) { numFailedLinkResolve.incrementAndGet(); } - if (opResult.isResolvedLinkFromCache()) { + if (opResult.isFromCache()) { numSuccessfulLinkResolveFromCache.incrementAndGet(); - } - - if (opResult.isResolvedLinkFromServer()) { + } else { numSuccessfulLinkResolveFromFromServer.incrementAndGet(); } @@ -425,7 +429,6 @@ public class VisualizationContext { newChildNode.setSelfLinkPendingResolve(false); newChildNode.setSelfLinkProcessed(true); - newChildNode.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED, NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK); @@ -542,7 +545,7 @@ public class VisualizationContext { if (nodeValue != null && nodeValue.isValueNode()) { - if (loader.getEntityDescriptor(fieldName) == null) { + if (OxmEntityLookup.getInstance().getEntityDescriptors().get(fieldName) == null) { /* * entity property name is not an entity, thus we can add this property name and value @@ -557,7 +560,7 @@ public class VisualizationContext { if (nodeValue.isArray()) { - if (loader.getEntityDescriptor(fieldName) == null) { + if (OxmEntityLookup.getInstance().getEntityDescriptors().get(fieldName) == null) { /* * entity property name is not an entity, thus we can add this property name and value @@ -623,10 +626,10 @@ public class VisualizationContext { */ ain.clearQueryParams(); ain.addQueryParams(extractQueryParamsFromSelfLink(ain.getSelfLink())); - ain.changeState(NodeProcessingState.NEIGHBORS_UNPROCESSED, NodeProcessingAction.SELF_LINK_RESPONSE_PARSE_OK); + } /** @@ -678,7 +681,7 @@ public class VisualizationContext { txn.setProcessingNode(ain); txn.setRequestParameters(depthModifier); aaiWorkOnHand.incrementAndGet(); - supplyAsync(new PerformNodeSelfLinkProcessingTask(txn, depthModifier, aaiProvider, aaiConfig), + supplyAsync(new PerformNodeSelfLinkProcessingTask(txn, depthModifier, aaiAdapter, aaiConfig), aaiExecutorService).whenComplete((nodeTxn, error) -> { aaiWorkOnHand.decrementAndGet(); if (error != null) { @@ -703,15 +706,13 @@ public class VisualizationContext { if (opResult != null && opResult.wasSuccessful()) { - if (opResult.isResolvedLinkFailure()) { + if (!opResult.wasSuccessful()) { numFailedLinkResolve.incrementAndGet(); } - if (opResult.isResolvedLinkFromCache()) { + if (opResult.isFromCache()) { numSuccessfulLinkResolveFromCache.incrementAndGet(); - } - - if (opResult.isResolvedLinkFromServer()) { + } else { numSuccessfulLinkResolveFromFromServer.incrementAndGet(); } @@ -871,7 +872,7 @@ public class VisualizationContext { * around the root node. */ - if (!rootNodeDiscovered || cacheNode.getNodeDepth() < VisualizationConfig.getConfig() + if (!rootNodeDiscovered || cacheNode.getNodeDepth() < this.visualizationConfigs .getMaxSelfLinkTraversalDepth()) { if (LOG.isDebugEnabled()) { @@ -959,7 +960,7 @@ public class VisualizationContext { LOG.debug(AaiUiMsgs.DEBUG_GENERIC, "Unexpected array type with a key = " + fieldName); } } else if (fieldValue.isValueNode()) { - if (loader.getEntityDescriptor(field.getKey()) == null) { + if (OxmEntityLookup.getInstance().getEntityDescriptors().get(field.getKey()) == null) { /* * property key is not an entity type, add it to our property set. */ @@ -1101,8 +1102,8 @@ public class VisualizationContext { return false; } - List pkeyNames = - loader.getEntityDescriptor(ain.getEntityType()).getPrimaryKeyAttributeName(); + List pkeyNames = OxmEntityLookup.getInstance().getEntityDescriptors() + .get(ain.getEntityType()).getPrimaryKeyAttributeNames(); if (pkeyNames == null || pkeyNames.size() == 0) { LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE_NODE_ID, "Primary key names is empty"); @@ -1179,10 +1180,9 @@ public class VisualizationContext { return false; } - OxmModelLoader modelLoader = OxmModelLoader.getInstance(); - Relationship[] relationshipArray = relationshipList.getRelationshipList(); OxmEntityDescriptor descriptor = null; + String repairedSelfLink = null; if (relationshipArray != null) { @@ -1203,7 +1203,7 @@ public class VisualizationContext { return false; } - newNode = new ActiveInventoryNode(); + newNode = new ActiveInventoryNode(this.visualizationConfigs); String entityType = r.getRelatedTo(); @@ -1213,7 +1213,7 @@ public class VisualizationContext { } } - descriptor = modelLoader.getEntityDescriptor(r.getRelatedTo()); + descriptor = OxmEntityLookup.getInstance().getEntityDescriptors().get(r.getRelatedTo()); newNode.setNodeId(nodeId); newNode.setEntityType(entityType); @@ -1223,7 +1223,7 @@ public class VisualizationContext { if (descriptor != null) { - List pkeyNames = descriptor.getPrimaryKeyAttributeName(); + List pkeyNames = descriptor.getPrimaryKeyAttributeNames(); newNode.changeState(NodeProcessingState.SELF_LINK_UNRESOLVED, NodeProcessingAction.SELF_LINK_SET); @@ -1337,7 +1337,7 @@ public class VisualizationContext { return; } - ActiveInventoryNode newNode = new ActiveInventoryNode(); + ActiveInventoryNode newNode = new ActiveInventoryNode(this.visualizationConfigs); newNode.setNodeId(searchTargetEntity.getId()); newNode.setEntityType(searchTargetEntity.getEntityType()); @@ -1399,7 +1399,7 @@ public class VisualizationContext { case NEIGHBORS_UNPROCESSED: { - if (n.getNodeDepth() < VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()) { + if (n.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) { /* * Only process our neighbors relationships if our current depth is less than the max * depth @@ -1528,7 +1528,7 @@ public class VisualizationContext { targetNode.addInboundNeighbor(srcNode.getNodeId()); - if (VisualizationConfig.getConfig().makeAllNeighborsBidirectional()) { + if (this.visualizationConfigs.makeAllNeighborsBidirectional()) { targetNode.addOutboundNeighbor(srcNode.getNodeId()); } @@ -1626,7 +1626,8 @@ public class VisualizationContext { return null; } - OxmEntityDescriptor descriptor = loader.getEntityDescriptor(entityType); + OxmEntityDescriptor descriptor = + OxmEntityLookup.getInstance().getEntityDescriptors().get(entityType); if (descriptor == null) { LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, @@ -1634,7 +1635,7 @@ public class VisualizationContext { return null; } - List pkeyNames = descriptor.getPrimaryKeyAttributeName(); + List pkeyNames = descriptor.getPrimaryKeyAttributeNames(); if (pkeyNames == null || pkeyNames.size() == 0) { LOG.error(AaiUiMsgs.FAILED_TO_DETERMINE, diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationService.java b/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationService.java index 0a9797f..69ef774 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationService.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationService.java @@ -30,30 +30,24 @@ import java.util.concurrent.ExecutorService; import javax.servlet.ServletException; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.dal.aai.ActiveInventoryAdapter; -import org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; -import org.onap.aai.sparky.dal.aai.config.ActiveInventoryRestConfig; -import org.onap.aai.sparky.dal.cache.EntityCache; -import org.onap.aai.sparky.dal.cache.PersistentEntityCache; -import org.onap.aai.sparky.dal.elasticsearch.ElasticSearchAdapter; -import org.onap.aai.sparky.dal.elasticsearch.ElasticSearchDataProvider; -import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.rest.RestClientBuilder; -import org.onap.aai.sparky.dal.rest.RestfulDataAccessor; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.entity.SearchableEntity; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.entity.SearchableEntity; import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.sparky.viewandinspect.config.VisualizationConfig; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode; import org.onap.aai.sparky.viewandinspect.entity.D3VisualizationOutput; import org.onap.aai.sparky.viewandinspect.entity.GraphMeta; import org.onap.aai.sparky.viewandinspect.entity.QueryParams; import org.onap.aai.sparky.viewandinspect.entity.QueryRequest; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.core.JsonProcessingException; @@ -66,63 +60,50 @@ 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 ActiveInventoryAdapter aaiAdapter; + private final ElasticSearchAdapter esAdapter; + private final ExecutorService tabularExecutorService; 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; + private VisualizationConfigs visualizationConfigs; + private ElasticSearchEndpointConfig endpointEConfig; + private ElasticSearchSchemaConfig schemaEConfig; - aaiRestConfig = ActiveInventoryConfig.getConfig().getAaiRestConfig(); + public VisualizationService(OxmModelLoader loader, VisualizationConfigs visualizationConfigs, + ActiveInventoryAdapter aaiAdapter, ElasticSearchAdapter esAdapter, + ElasticSearchEndpointConfig endpointConfig, ElasticSearchSchemaConfig schemaConfig) + throws Exception { - EntityCache cache = null; - secureRandom = new SecureRandom(); - - ActiveInventoryAdapter aaiAdapter = new ActiveInventoryAdapter(new RestClientBuilder()); - if (aaiRestConfig.isCacheEnabled()) { - cache = new PersistentEntityCache(aaiRestConfig.getStorageFolderOverride(), - aaiRestConfig.getNumCacheWorkers()); + this.visualizationConfigs = visualizationConfigs; + this.endpointEConfig = endpointConfig; + this.schemaEConfig = schemaConfig; - aaiAdapter.setCacheEnabled(true); - aaiAdapter.setEntityCache(cache); - } + secureRandom = new SecureRandom(); - this.aaiProvider = aaiAdapter; + /* + * Fix constructor with properly wired in properties + */ - RestClientBuilder esClientBuilder = new RestClientBuilder(); - esClientBuilder.setUseHttps(false); - RestfulDataAccessor nonCachingRestProvider = new RestfulDataAccessor(esClientBuilder); - this.esConfig = ElasticSearchConfig.getConfig(); - this.esProvider = new ElasticSearchAdapter(nonCachingRestProvider, this.esConfig); + this.aaiAdapter = aaiAdapter; + this.esAdapter = esAdapter; this.mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); this.contextMap = new ConcurrentHashMap(); - this.visualizationConfig = VisualizationConfig.getConfig(); + this.tabularExecutorService = NodeUtils.createNamedExecutor("TABULAR-WORKER", + this.visualizationConfigs.getNumOfThreadsToFetchNodeIntegrity(), LOG); 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; } /** @@ -222,7 +203,9 @@ public class VisualizationService { * 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()); + dataCollectionResult = esAdapter.retrieveEntityById(endpointEConfig.getEsIpAddress(), + endpointEConfig.getEsServerPort(), schemaEConfig.getIndexName(), + schemaEConfig.getIndexDocType(), queryRequest.getHashId()); sourceEntity = extractSearchableEntityFromElasticEntity(dataCollectionResult); if (sourceEntity != null) { @@ -243,7 +226,8 @@ public class VisualizationService { try { - d3OutputJsonOutput = getVisualizationOutputBasedonGenericQuery(sourceEntity, queryParams); + d3OutputJsonOutput = + getVisualizationOutputBasedonGenericQuery(sourceEntity, queryParams, queryRequest); if (LOG.isDebugEnabled()) { LOG.debug(AaiUiMsgs.DEBUG_GENERIC, @@ -270,22 +254,22 @@ public class VisualizationService { } + /** * 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 + * @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 @throws */ private String getVisualizationOutputBasedonGenericQuery(SearchableEntity searchtargetEntity, - QueryParams queryParams) throws ServletException { + QueryParams queryParams, QueryRequest request) throws ServletException { long opStartTimeInMs = System.currentTimeMillis(); VisualizationTransformer transformer = null; try { - transformer = new VisualizationTransformer(); + transformer = new VisualizationTransformer(visualizationConfigs); } catch (Exception exc) { throw new ServletException( "Failed to create VisualizationTransformer instance because of execption", exc); @@ -294,7 +278,8 @@ public class VisualizationService { VisualizationContext visContext = null; long contextId = secureRandom.nextLong(); try { - visContext = new VisualizationContext(contextId, aaiProvider, aaiExecutorService, loader); + visContext = new VisualizationContext(contextId, this.aaiAdapter, tabularExecutorService, + aaiExecutorService, this.visualizationConfigs); contextMap.putIfAbsent(contextId, visContext); } catch (Exception e1) { LOG.error(AaiUiMsgs.EXCEPTION_CAUGHT, @@ -349,9 +334,10 @@ public class VisualizationService { try { output = transformer .generateVisualizationOutput((System.currentTimeMillis() - opStartTimeInMs), graphMeta); - } catch (Exception exc) { - LOG.error(AaiUiMsgs.FAILURE_TO_PROCESS_REQUEST, exc.getLocalizedMessage()); + } 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()); @@ -378,8 +364,8 @@ public class VisualizationService { } public void shutdown() { - aaiProvider.shutdown(); + tabularExecutorService.shutdown(); aaiExecutorService.shutdown(); - esProvider.shutdown(); } + } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationTransformer.java b/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationTransformer.java index fdc078e..7c1d16d 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationTransformer.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/services/VisualizationTransformer.java @@ -27,21 +27,19 @@ 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.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; import org.onap.aai.sparky.logging.AaiUiMsgs; import org.onap.aai.sparky.util.ConfigHelper; -import org.onap.aai.sparky.viewandinspect.config.VisualizationConfig; +import org.onap.aai.sparky.viewandinspect.config.VisualizationConfigs; import org.onap.aai.sparky.viewandinspect.entity.ActiveInventoryNode; import org.onap.aai.sparky.viewandinspect.entity.D3VisualizationOutput; import org.onap.aai.sparky.viewandinspect.entity.GraphMeta; import org.onap.aai.sparky.viewandinspect.entity.JsonNode; import org.onap.aai.sparky.viewandinspect.entity.JsonNodeLink; import org.onap.aai.sparky.viewandinspect.entity.NodeDebug; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -63,7 +61,6 @@ public class VisualizationTransformer { 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 @@ -75,7 +72,7 @@ public class VisualizationTransformer { - private VisualizationConfig visualizationConfig; + private VisualizationConfigs visualizationConfigs; /** @@ -83,9 +80,8 @@ public class VisualizationTransformer { * * @throws Exception the exception */ - public VisualizationTransformer() throws Exception { - visualizationConfig = VisualizationConfig.getConfig(); - + public VisualizationTransformer(VisualizationConfigs visualizationConfigs) throws Exception { + this.visualizationConfigs = visualizationConfigs; } @@ -108,7 +104,7 @@ public class VisualizationTransformer { for (JsonNode n : flatNodeArray) { if (n.isRootNode()) { n.getNodeMeta().setSearchTarget(true); - n.getNodeMeta().setClassName(visualizationConfig.getSelectedSearchedNodeClassName()); + n.getNodeMeta().setClassName(this.visualizationConfigs.getSelectedSearchedNodeClassName()); } } @@ -160,7 +156,7 @@ public class VisualizationTransformer { ObjectMapper mapper = new ObjectMapper(); final String fileContent = ConfigHelper.getFileContents( - System.getProperty("AJSC_HOME") + visualizationConfig.getAaiEntityNodeDescriptors()); + System.getProperty("AJSC_HOME") + this.visualizationConfigs.getAaiEntityNodeDescriptors()); com.fasterxml.jackson.databind.JsonNode aaiEntityNodeDefinitions = mapper.readTree(fileContent); graphMeta.setAaiEntityNodeDescriptors(aaiEntityNodeDefinitions); @@ -211,7 +207,7 @@ public class VisualizationTransformer { * current node. */ - if (ain.getNodeDepth() < VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()) { + if (ain.getNodeDepth() < this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) { Collection outboundNeighbors = ain.getOutboundNeighbors(); @@ -266,17 +262,13 @@ public class VisualizationTransformer { for (ActiveInventoryNode n : nodeMap.values()) { - if (n.getNodeDepth() <= VisualizationConfig.getConfig().getMaxSelfLinkTraversalDepth()) { - - JsonNode jsonNode = new JsonNode(n); + if (n.getNodeDepth() <= this.visualizationConfigs.getMaxSelfLinkTraversalDepth()) { - if (this.isUriEnrichable(n.getSelfLink())) { - jsonNode.getNodeMeta().setEnrichableNode(true); - } + JsonNode jsonNode = new JsonNode(n, this.visualizationConfigs); - jsonNode.getNodeMeta().setClassName(visualizationConfig.getGeneralNodeClassName()); + jsonNode.getNodeMeta().setClassName(this.visualizationConfigs.getGeneralNodeClassName()); - if (VisualizationConfig.getConfig().isVisualizationDebugEnabled()) { + if (this.visualizationConfigs.isVisualizationDebugEnabled()) { NodeDebug nodeDebug = jsonNode.getNodeMeta().getNodeDebug(); @@ -295,92 +287,4 @@ public class VisualizationTransformer { } } - /** - * 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; - } - - - /** - * @return the flatNodeArray - */ - public List getFlatNodeArray() { - return flatNodeArray; - } - - - /** - * @param flatNodeArray the flatNodeArray to set - */ - public void setFlatNodeArray(List flatNodeArray) { - this.flatNodeArray = flatNodeArray; - } - - - /** - * @return the enrichableUriPrefixes - */ - public Set getEnrichableUriPrefixes() { - return enrichableUriPrefixes; - } - - - /** - * @param enrichableUriPrefixes the enrichableUriPrefixes to set - */ - public void setEnrichableUriPrefixes(Set enrichableUriPrefixes) { - this.enrichableUriPrefixes = enrichableUriPrefixes; - } - - - /** - * @return the linkArrayOutput - */ - public List getLinkArrayOutput() { - return linkArrayOutput; - } - - - /** - * @param linkArrayOutput the linkArrayOutput to set - */ - public void setLinkArrayOutput(List linkArrayOutput) { - this.linkArrayOutput = linkArrayOutput; - } - - - /** - * @return the visualizationConfig - */ - public VisualizationConfig getVisualizationConfig() { - return visualizationConfig; - } - - - /** - * @param visualizationConfig the visualizationConfig to set - */ - public void setVisualizationConfig(VisualizationConfig visualizationConfig) { - this.visualizationConfig = visualizationConfig; - } - - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } } diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/servlet/SearchServlet.java b/src/main/java/org/onap/aai/sparky/viewandinspect/servlet/SearchServlet.java deleted file mode 100644 index 5a84346..0000000 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/servlet/SearchServlet.java +++ /dev/null @@ -1,224 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.dal.elasticsearch.SearchAdapter; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.sas.config.SearchServiceConfig; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.search.VnfSearchService; -import org.onap.aai.sparky.search.config.SuggestionConfig; -import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.sparky.viewandinspect.services.SearchServiceWrapper; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; -import org.onap.aai.cl.mdc.MdcContext; - -/** - * The Class SearchServlet. - */ - -public class SearchServlet extends HttpServlet { - - private static final long serialVersionUID = 1L; - - /** - * @return the searchWrapper - */ - public SearchServiceWrapper getSearchWrapper() { - return searchWrapper; - } - - /** - * @param searchWrapper the searchWrapper to set - */ - public void setSearchWrapper(SearchServiceWrapper searchWrapper) { - this.searchWrapper = searchWrapper; - } - - /** - * @return the serialversionuid - */ - public static long getSerialversionuid() { - return serialVersionUID; - } - - /** - * @return the log - */ - public static Logger getLog() { - return LOG; - } - - /** - * @return the keyPayload - */ - public static String getKeyPayload() { - return KEY_PAYLOAD; - } - - - 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/onap/aai/sparky/viewandinspect/servlet/VisualizationServlet.java b/src/main/java/org/onap/aai/sparky/viewandinspect/servlet/VisualizationServlet.java deleted file mode 100644 index 85ebe50..0000000 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/servlet/VisualizationServlet.java +++ /dev/null @@ -1,200 +0,0 @@ -/** - * ============LICENSE_START======================================================= - * org.onap.aai - * ================================================================================ - * Copyright © 2017 AT&T Intellectual Property. All rights reserved. - * Copyright © 2017 Amdocs - * ================================================================================ - * 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 is a trademark and service mark of AT&T Intellectual Property. - */ -package org.onap.aai.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.onap.aai.sparky.config.oxm.OxmModelLoader; -import org.onap.aai.sparky.dal.rest.OperationResult; -import org.onap.aai.sparky.dal.servlet.ResettableStreamHttpServletRequest; -import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.sparky.viewandinspect.entity.QueryRequest; -import org.onap.aai.sparky.viewandinspect.services.VisualizationService; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; -import org.onap.aai.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/onap/aai/sparky/viewandinspect/task/CollectNodeSelfLinkTask.java b/src/main/java/org/onap/aai/sparky/viewandinspect/task/CollectNodeSelfLinkTask.java index 3b750b3..8683299 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/task/CollectNodeSelfLinkTask.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/task/CollectNodeSelfLinkTask.java @@ -33,35 +33,6 @@ import org.onap.aai.sparky.dal.rest.OperationResult; public class CollectNodeSelfLinkTask implements Supplier { private String selfLink; - - /** - * @return the selfLink - */ - public String getSelfLink() { - return selfLink; - } - - /** - * @param selfLink the selfLink to set - */ - public void setSelfLink(String selfLink) { - this.selfLink = selfLink; - } - - /** - * @return the aaiProvider - */ - public ActiveInventoryDataProvider getAaiProvider() { - return aaiProvider; - } - - /** - * @param aaiProvider the aaiProvider to set - */ - public void setAaiProvider(ActiveInventoryDataProvider aaiProvider) { - this.aaiProvider = aaiProvider; - } - private ActiveInventoryDataProvider aaiProvider; /** diff --git a/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTask.java b/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTask.java index 518d569..7c59ffa 100644 --- a/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTask.java +++ b/src/main/java/org/onap/aai/sparky/viewandinspect/task/PerformNodeSelfLinkProcessingTask.java @@ -25,13 +25,13 @@ package org.onap.aai.sparky.viewandinspect.task; import java.util.Map; import java.util.function.Supplier; -import org.onap.aai.sparky.dal.aai.ActiveInventoryDataProvider; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.restclient.client.OperationResult; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; import org.onap.aai.sparky.viewandinspect.entity.NodeProcessingTransaction; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import org.slf4j.MDC; /** @@ -43,7 +43,7 @@ public class PerformNodeSelfLinkProcessingTask implements Supplier contextMap; private ActiveInventoryConfig aaiConfig; @@ -51,12 +51,19 @@ public class PerformNodeSelfLinkProcessingTask implements Supplier contextMap; @@ -53,9 +53,9 @@ public class PerformSelfLinkDeterminationTask * @param aaiProvider the aai provider */ public PerformSelfLinkDeterminationTask(SelfLinkDeterminationTransaction txn, - String requestParameters, ActiveInventoryDataProvider aaiProvider) { + String requestParameters, ActiveInventoryAdapter aaiAdapter) { - this.aaiProvider = aaiProvider; + this.aaiAdapter = aaiAdapter; this.txn = txn; this.contextMap = MDC.getCopyOfContextMap(); } @@ -78,7 +78,7 @@ public class PerformSelfLinkDeterminationTask OperationResult opResult = null; try { opResult = - aaiProvider.queryActiveInventoryWithRetries(txn.getQueryString(), "application/json", + aaiAdapter.queryActiveInventoryWithRetries(txn.getQueryString(), "application/json", ActiveInventoryConfig.getConfig().getAaiRestConfig().getNumRequestRetries()); } catch (Exception exc) { opResult = new OperationResult(); diff --git a/src/main/java/org/onap/aai/sparky/synchronizer/SearchableEntitySynchronizer.java b/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectEntitySynchronizer.java similarity index 85% rename from src/main/java/org/onap/aai/sparky/synchronizer/SearchableEntitySynchronizer.java rename to src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectEntitySynchronizer.java index e10163f..ccce3b0 100644 --- a/src/main/java/org/onap/aai/sparky/synchronizer/SearchableEntitySynchronizer.java +++ b/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectEntitySynchronizer.java @@ -20,22 +20,11 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ -package org.onap.aai.sparky.synchronizer; +package org.onap.aai.sparky.viewinspect.sync; import static java.util.concurrent.CompletableFuture.supplyAsync; -import org.onap.aai.cl.mdc.MdcContext; - -import org.onap.aai.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; @@ -47,32 +36,45 @@ import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutorService; import java.util.function.Supplier; +import org.onap.aai.cl.api.Logger; +import org.onap.aai.cl.eelf.LoggerFactory; +import org.onap.aai.cl.mdc.MdcContext; +import org.onap.aai.restclient.client.OperationResult; import org.onap.aai.sparky.config.oxm.OxmEntityDescriptor; +import org.onap.aai.sparky.config.oxm.OxmEntityLookup; +import org.onap.aai.sparky.config.oxm.SearchableEntityLookup; +import org.onap.aai.sparky.config.oxm.SearchableOxmEntityDescriptor; import org.onap.aai.sparky.dal.NetworkTransaction; import org.onap.aai.sparky.dal.aai.config.ActiveInventoryConfig; import org.onap.aai.sparky.dal.elasticsearch.config.ElasticSearchConfig; import org.onap.aai.sparky.dal.rest.HttpMethod; -import org.onap.aai.sparky.dal.rest.OperationResult; import org.onap.aai.sparky.logging.AaiUiMsgs; -import org.onap.aai.sparky.synchronizer.config.SynchronizerConfiguration; -import org.onap.aai.sparky.synchronizer.entity.MergableEntity; -import org.onap.aai.sparky.synchronizer.entity.SearchableEntity; -import org.onap.aai.sparky.synchronizer.entity.SelfLinkDescriptor; -import org.onap.aai.sparky.synchronizer.enumeration.OperationState; -import org.onap.aai.sparky.synchronizer.enumeration.SynchronizerState; -import org.onap.aai.sparky.synchronizer.task.PerformActiveInventoryRetrieval; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchPut; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchRetrieval; -import org.onap.aai.sparky.synchronizer.task.PerformElasticSearchUpdate; +import org.onap.aai.sparky.sync.AbstractEntitySynchronizer; +import org.onap.aai.sparky.sync.IndexSynchronizer; +import org.onap.aai.sparky.sync.SynchronizerConstants; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.entity.MergableEntity; +import org.onap.aai.sparky.sync.entity.SearchableEntity; +import org.onap.aai.sparky.sync.entity.SelfLinkDescriptor; +import org.onap.aai.sparky.sync.enumeration.OperationState; +import org.onap.aai.sparky.sync.enumeration.SynchronizerState; +import org.onap.aai.sparky.sync.task.PerformActiveInventoryRetrieval; +import org.onap.aai.sparky.sync.task.PerformElasticSearchPut; +import org.onap.aai.sparky.sync.task.PerformElasticSearchRetrieval; +import org.onap.aai.sparky.sync.task.PerformElasticSearchUpdate; import org.onap.aai.sparky.util.NodeUtils; -import org.onap.aai.cl.api.Logger; -import org.onap.aai.cl.eelf.LoggerFactory; import org.slf4j.MDC; +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 SearchableEntitySynchronizer. */ -public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer +public class ViewInspectEntitySynchronizer extends AbstractEntitySynchronizer implements IndexSynchronizer { /** @@ -103,7 +105,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer } private static final Logger LOG = - LoggerFactory.getInstance().getLogger(SearchableEntitySynchronizer.class); + LoggerFactory.getInstance().getLogger(ViewInspectEntitySynchronizer.class); private boolean allWorkEnumerated; private Deque selflinks; @@ -117,18 +119,21 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer * @param indexName the index name * @throws Exception the exception */ - public SearchableEntitySynchronizer(String indexName) throws Exception { - super(LOG, "SES", 2, 5, 5, indexName); + public ViewInspectEntitySynchronizer(ElasticSearchSchemaConfig schemaConfig, + int internalSyncWorkers, int aaiWorkers, int esWorkers, NetworkStatisticsConfig aaiStatConfig, + NetworkStatisticsConfig esStatConfig) throws Exception { + super(LOG, "SES", internalSyncWorkers, aaiWorkers, esWorkers, schemaConfig.getIndexName(), + aaiStatConfig, esStatConfig); 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()); + this.aaiEntityStats.intializeEntityCounters( + SearchableEntityLookup.getInstance().getSearchableEntityDescriptors().keySet()); + this.esEntityStats.intializeEntityCounters( + SearchableEntityLookup.getInstance().getSearchableEntityDescriptors().keySet()); this.syncDurationInMs = -1; } @@ -139,8 +144,8 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer */ private OperationState collectAllTheWork() { final Map contextMap = MDC.getCopyOfContextMap(); - Map descriptorMap = - oxmModelLoader.getSearchableEntityDescriptors(); + Map descriptorMap = + SearchableEntityLookup.getInstance().getSearchableEntityDescriptors(); if (descriptorMap.isEmpty()) { LOG.error(AaiUiMsgs.ERROR_LOADING_OXM_SEARCHABLE_ENTITIES); @@ -172,7 +177,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer MDC.setContextMap(contextMap); OperationResult typeLinksResult = null; try { - typeLinksResult = aaiDataProvider.getSelfLinksByEntityType(key); + typeLinksResult = aaiAdapter.getSelfLinksByEntityType(key); aaiWorkOnHand.decrementAndGet(); processEntityTypeSelfLinks(typeLinksResult); } catch (Exception exc) { @@ -226,7 +231,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#doSync() + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#doSync() */ @Override public OperationState doSync() { @@ -278,11 +283,12 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer final String resourceType = NodeUtils.getNodeFieldAsText(element, "resource-type"); final String resourceLink = NodeUtils.getNodeFieldAsText(element, "resource-link"); - OxmEntityDescriptor descriptor = null; + SearchableOxmEntityDescriptor descriptor = null; if (resourceType != null && resourceLink != null) { - descriptor = oxmModelLoader.getEntityDescriptor(resourceType); + descriptor = SearchableEntityLookup.getInstance().getSearchableEntityDescriptors() + .get(resourceType); if (descriptor == null) { LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, resourceType); @@ -292,7 +298,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer if (descriptor.hasSearchableAttributes()) { selflinks.add(new SelfLinkDescriptor(resourceLink, - SynchronizerConfiguration.NODES_ONLY_MODIFIER, resourceType)); + SynchronizerConstants.NODES_ONLY_MODIFIER, resourceType)); } } @@ -316,7 +322,8 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer if (linkDescriptor.getSelfLink() != null && linkDescriptor.getEntityType() != null) { - descriptor = oxmModelLoader.getEntityDescriptor(linkDescriptor.getEntityType()); + descriptor = OxmEntityLookup.getInstance().getEntityDescriptors() + .get(linkDescriptor.getEntityType()); if (descriptor == null) { LOG.error(AaiUiMsgs.MISSING_ENTITY_DESCRIPTOR, linkDescriptor.getEntityType()); @@ -332,7 +339,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer aaiWorkOnHand.incrementAndGet(); - supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiDataProvider), aaiExecutor) + supplyAsync(new PerformActiveInventoryRetrieval(txn, aaiAdapter), aaiExecutor) .whenComplete((result, error) -> { aaiWorkOnHand.decrementAndGet(); @@ -423,7 +430,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer 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()); + MergableEntity merged = updater.readValue(NodeUtils.convertObjectToJson(se, false)); jsonPayload = mapper.writeValueAsString(merged); } } catch (IOException exc) { @@ -434,14 +441,15 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer return; } } else { - jsonPayload = se.getIndexDocumentJson(); + jsonPayload = se.getAsJson(); } if (wasEntryDiscovered) { if (versionNumber != null && jsonPayload != null) { - String requestPayload = esDataProvider.buildBulkImportOperationRequest(getIndexName(), - ElasticSearchConfig.getConfig().getType(), se.getId(), versionNumber, jsonPayload); + String requestPayload = elasticSearchAdapter.buildBulkImportOperationRequest( + getIndexName(), ElasticSearchConfig.getConfig().getType(), se.getId(), versionNumber, + jsonPayload); NetworkTransaction transactionTracker = new NetworkTransaction(); transactionTracker.setEntityType(esGetTxn.getEntityType()); @@ -450,7 +458,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer esWorkOnHand.incrementAndGet(); supplyAsync(new PerformElasticSearchUpdate(ElasticSearchConfig.getConfig().getBulkUrl(), - requestPayload, esDataProvider, transactionTracker), esPutExecutor) + requestPayload, elasticSearchAdapter, transactionTracker), esPutExecutor) .whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -467,6 +475,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer } } else { + if (link != null && jsonPayload != null) { NetworkTransaction updateElasticTxn = new NetworkTransaction(); @@ -476,7 +485,8 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer updateElasticTxn.setOperationType(HttpMethod.PUT); esWorkOnHand.incrementAndGet(); - supplyAsync(new PerformElasticSearchPut(jsonPayload, updateElasticTxn, esDataProvider), + supplyAsync( + new PerformElasticSearchPut(jsonPayload, updateElasticTxn, elasticSearchAdapter), esPutExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -518,7 +528,10 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer List primaryKeyValues = new ArrayList(); String pkeyValue = null; - for (String keyName : resultDescriptor.getPrimaryKeyAttributeName()) { + SearchableOxmEntityDescriptor searchableDescriptor = SearchableEntityLookup.getInstance() + .getSearchableEntityDescriptors().get(resultDescriptor.getEntityName()); + + for (String keyName : searchableDescriptor.getPrimaryKeyAttributeNames()) { pkeyValue = NodeUtils.getNodeFieldAsText(entityNode, keyName); if (pkeyValue != null) { primaryKeyValues.add(pkeyValue); @@ -532,7 +545,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer final String primaryCompositeKeyValue = NodeUtils.concatArray(primaryKeyValues, "/"); doc.setEntityPrimaryKeyValue(primaryCompositeKeyValue); - final List searchTagFields = resultDescriptor.getSearchableAttributes(); + final List searchTagFields = searchableDescriptor.getSearchableAttributes(); /* * Based on configuration, use the configured field names for this entity-Type to build a @@ -558,13 +571,16 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer return; } + SearchableOxmEntityDescriptor searchableDescriptor = SearchableEntityLookup.getInstance() + .getSearchableEntityDescriptors().get(txn.getDescriptor().getEntityName()); + try { - if (txn.getDescriptor().hasSearchableAttributes()) { + if (searchableDescriptor.hasSearchableAttributes()) { final String jsonResult = txn.getOperationResult().getResult(); if (jsonResult != null && jsonResult.length() > 0) { - SearchableEntity se = new SearchableEntity(oxmModelLoader); + SearchableEntity se = new SearchableEntity(); se.setLink(ActiveInventoryConfig.extractResourcePath(txn.getLink())); populateSearchableEntityDocument(se, jsonResult, txn.getDescriptor()); se.deriveFields(); @@ -585,7 +601,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer esWorkOnHand.incrementAndGet(); - supplyAsync(new PerformElasticSearchRetrieval(n2, esDataProvider), esExecutor) + supplyAsync(new PerformElasticSearchRetrieval(n2, elasticSearchAdapter), esExecutor) .whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -677,7 +693,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer * called incrementAndGet when queuing the failed PUT! */ - supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, esDataProvider), + supplyAsync(new PerformElasticSearchRetrieval(retryTransaction, elasticSearchAdapter), esExecutor).whenComplete((result, error) -> { esWorkOnHand.decrementAndGet(); @@ -735,7 +751,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#getStatReport(boolean) */ @Override public String getStatReport(boolean showFinalReport) { @@ -746,7 +762,7 @@ public class SearchableEntitySynchronizer extends AbstractEntitySynchronizer /* * (non-Javadoc) * - * @see org.onap.aai.sparky.synchronizer.IndexSynchronizer#shutdown() + * @see org.openecomp.sparky.synchronizer.IndexSynchronizer#shutdown() */ @Override public void shutdown() { diff --git a/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectSyncController.java b/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectSyncController.java new file mode 100644 index 0000000..c2ecbb1 --- /dev/null +++ b/src/main/java/org/onap/aai/sparky/viewinspect/sync/ViewInspectSyncController.java @@ -0,0 +1,129 @@ +/** + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017 AT&T Intellectual Property. All rights reserved. + * Copyright © 2017 Amdocs + * ================================================================================ + * 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 is a trademark and service mark of AT&T Intellectual Property. + */ +package org.onap.aai.sparky.viewinspect.sync; + +import org.onap.aai.sparky.crossentityreference.sync.CrossEntityReferenceSynchronizer; +import org.onap.aai.sparky.dal.ActiveInventoryAdapter; +import org.onap.aai.sparky.dal.ElasticSearchAdapter; +import org.onap.aai.sparky.sync.ElasticSearchIndexCleaner; +import org.onap.aai.sparky.sync.ElasticSearchSchemaFactory; +import org.onap.aai.sparky.sync.IndexCleaner; +import org.onap.aai.sparky.sync.IndexIntegrityValidator; +import org.onap.aai.sparky.sync.SyncControllerImpl; +import org.onap.aai.sparky.sync.SyncControllerRegistrar; +import org.onap.aai.sparky.sync.SyncControllerRegistry; +import org.onap.aai.sparky.sync.config.ElasticSearchEndpointConfig; +import org.onap.aai.sparky.sync.config.ElasticSearchSchemaConfig; +import org.onap.aai.sparky.sync.config.NetworkStatisticsConfig; +import org.onap.aai.sparky.sync.config.SyncControllerConfig; + +public class ViewInspectSyncController extends SyncControllerImpl + implements SyncControllerRegistrar { + + private SyncControllerRegistry syncControllerRegistry; + private ActiveInventoryAdapter aaiAdapter; + private ElasticSearchAdapter esAdapter; + private ElasticSearchSchemaConfig schemaConfig; + private ElasticSearchEndpointConfig endpointConfig; + + public ViewInspectSyncController(SyncControllerConfig syncControllerConfig, + ActiveInventoryAdapter aaiAdapter, ElasticSearchAdapter esAdapter, + ElasticSearchSchemaConfig schemaConfig, ElasticSearchEndpointConfig endpointConfig, + NetworkStatisticsConfig aaiStatConfig, NetworkStatisticsConfig esStatConfig) + throws Exception { + super(syncControllerConfig); + + + // final String controllerName = "View and Inspect Entity Synchronizer"; + + this.aaiAdapter = aaiAdapter; + this.esAdapter = esAdapter; + this.schemaConfig = schemaConfig; + this.endpointConfig = endpointConfig; + IndexIntegrityValidator indexValidator = new IndexIntegrityValidator(esAdapter, schemaConfig, + endpointConfig, ElasticSearchSchemaFactory.getIndexSchema(schemaConfig)); + + registerIndexValidator(indexValidator); + + + ViewInspectEntitySynchronizer ses = new ViewInspectEntitySynchronizer(schemaConfig, + syncControllerConfig.getNumInternalSyncWorkers(), + syncControllerConfig.getNumSyncActiveInventoryWorkers(), + syncControllerConfig.getNumSyncElasticWorkers(), aaiStatConfig, esStatConfig); + ses.setAaiAdapter(aaiAdapter); + ses.setElasticSearchAdapter(esAdapter); + + registerEntitySynchronizer(ses); + + CrossEntityReferenceSynchronizer cers = new CrossEntityReferenceSynchronizer(schemaConfig, + syncControllerConfig.getNumInternalSyncWorkers(), + syncControllerConfig.getNumSyncActiveInventoryWorkers(), + syncControllerConfig.getNumSyncElasticWorkers(), aaiStatConfig, esStatConfig); + + cers.setAaiAdapter(aaiAdapter); + cers.setElasticSearchAdapter(esAdapter); + + registerEntitySynchronizer(cers); + + IndexCleaner indexCleaner = + new ElasticSearchIndexCleaner(esAdapter, endpointConfig, schemaConfig); + + registerIndexCleaner(indexCleaner); + + } + + public SyncControllerRegistry getSyncControllerRegistry() { + return syncControllerRegistry; + } + + public void setSyncControllerRegistry(SyncControllerRegistry syncControllerRegistry) { + this.syncControllerRegistry = syncControllerRegistry; + } + + public ActiveInventoryAdapter getAaiAdapter() { + return this.aaiAdapter; + } + + public ElasticSearchAdapter getElasticSearchAdapter() { + return this.esAdapter; + } + + public ElasticSearchEndpointConfig getendpointConfig() { + return this.endpointConfig; + } + + public ElasticSearchSchemaConfig getschemaConfig() { + return this.schemaConfig; + } + + + @Override + public void registerController() { + if (syncControllerRegistry != null) { + if (syncControllerConfig.isEnabled()) { + syncControllerRegistry.registerSyncController(this); + } + } + + } +} diff --git a/src/main/resources/extApps/aai.war b/src/main/resources/extApps/aai.war new file mode 100644 index 0000000000000000000000000000000000000000..f6d265acf7e822e8c71a6109e0ad00931097afff GIT binary patch literal 1372092 zcmZtMV{l|&*e?9owkEdiOl;e>&56;mZQHipNhY>!V*fPlcjfSl|4$?8yi1vP+xfIxwQfWUosYX>uPFGedjdpmVS7Nihn zge|&fWMzOuLTeQoxdh=Hbm#~>4QSCC@%=jhfb@LuxP5LXE2(N#hkY)zxuwm>18QxN zqX1L#S4PzRR4|qw(rQC@v(b`D99NOyueTX&@PX7X5I2yKOj%~?n+VdMp<1WheKyT< zq(Pi&BoNHxHIi?pHX&a0sRupu!~Tu$!rJ{Tj+pr^`E) zDifw&J#U?o7ckQ*NOE{SuW^_V3%no3{=eBV^!#KY8Jx*=V-P`LtXV){zq6aTJDAy- zGupUb_1dN%wlz|}d7o-d0H4KVRPmH=CE0)X^t9bZ9@ebAZ}n<+r(;uyF!)fbkW}Vb zPx1DD9?0{QYZ3F)%D-=^i&{zGuhJ8AE+ zI-}$aOu~A-DOMb3r+ZiyO=w-k?N@KgyJ zl zeVE-Ae;O!ttu|KdvLs*YQDEl4o}G5gcUb~P(+|~ZOV3Zha6ztMGbw$Q=PUiQ$ICN$ z!qmy5Kuwoj!1d@YVWS9mHGTWjLPuVHINNQ|@SE3qHTrIrTvEht^!LP%GZvECikkLQH^)L=E|%RT2T%9L@0xWRM5`&SzuBTdbAr8&uHT*7(7P< zV;$3Wo|M8DAtu4u%psR?xgShXNqBeJ_H~^M4H?0(+f@-n*!6Q(^pv9D=jZ2TyWq#u z?e6E@qG8{!&zsNPfVcC7Ymg7hoJl8L$!+!Q27ykdMl&V@x2ET7h6~BUbnokz?H|S? zC7Y8ykEPwfySqWKY}1A&U2#$+%|?x10Ws&_-`td2zM3e(r$5v%EU*GZ$hjqF*TJF~ zf{cci$|&_#GK8POX~XyTi{e8_z5hrMLps9ETL!jl?(@ls%nvkCFs8KtIt2s;2YNQ+JX% z=Kn0H$=azb@tipSA(Paw!~1m8{N!erm}qEby%imT7*(wtM*&2Yq|rL%pcWV^{W@a^ z(GOyE>=e#`aoZ`$m*PBGb4j-75d6h`+{A=LfJ+zk%VBxg=dY-mkpaQ4_cMQ@!!K+t z$^&*?EbWA&agg}61Cg^T`!U+FLs(gq!N#Ze!+l>?T^7Cme2Al28+1EC6f6po`lCPH z+c!--nBrMp$}aqv25}}eDZOBRv`*VW8n!7{%2=2(y`^h{Z1twhI&xfxn8NGu z^PDqatOIG%Sq0H3u=Afq6Gn~jTpFlF9IPto)Se28kgvTXQG(WC*2)D=6bqE&inV)c zB@BZWIwg`%khXn+SEb$haZ%=u5#6rX+xbpZguz!oWzs@q-8uwKfl)AY5e9+3sI>`; zm}BNk!|mHlHv#*d-sl98)L?<)VWz(ot(Tnwhu{U#7WW|Hax4YOn< z#U(T;Cs>tai~c-skPfE)NfeS0(_2~{GRUaUQX)&_O00NDZTIQl4PU-*V!qT?q=Kb< zVnVlbf&7Ry)Ycik%xUpkhMh*nYI{bfEw;%%CI%o6WoYn%YiV0A6|d%5`}Y>{rt@ha ziTfH{m@Y^i4-IKDUsiC|#~MvAFTS!MAcRB#Qpb6NnRbv)d%j7s4^k1Dj?}fA`?F)~ zkWqZn248aA8~`n3xi>GUFCABt7^&P4IiGaSh&*AuKw~`xPd}imfV-ap>RPl9xs&8)qlkQ#FcADG|i2t=Ntw)eG3Qm~MuJq@r`?=*aLaDz*`0?KYPY9Y}n%|ZG z*fR#D(QFY5%-=;?2s58@KQ3a;9=j%TZTv1PR@yttcBKUzXKd&51HGCdi{Dxx&i$$EsgbrvhFY}T7&e^F?( zA%;$HmpjCQ8VSgU@ruT>Nj>JK_F!uCCq0a^@{j-IrtrSUF|8#%qmc2GjsgE&qHTv( ziiPP#W929!uap^0wPiV1zG{<^@{?8dhWr>!qb+q~nfYnx06|Wbkz1R?xp@!-+_uF8 zDb(39-(sE#hdDymc;a-~gE!ymaWvVydpV6QHMnD!QO%N{36_k2 zcQMbE?ljs9^-r*&X^W}j(5jZCs`0>bdo_`N&NA@imc?RCx=~!RKL{7=GoPuBsnLSX zV+zr@2yv&l2a>wBC7sV`a3v+*q#2B2sUQ=6iOK*OF$!$hmr9h?yCS6Z*5 zMX61z(r|XyjGDg$#Uv$_zlO&Ojo~Ky?G4+`fI9{``DvaFn0Rl1?Ik;LAS8%;xgZZq z{C8)B5!V(NGMZLO{LqB7|BCF6J_b@uDXSl3pWp zw~P5S?fxLd4`)!Ig$yoEI2OiPY`$9kgkB5s=-zjLs6mQCg9>mq)oH;e@rXDkv-g&2 zw(8)fm@gC4-fE-0PBwYvls(K?dtlRcI3mDB{Uf)rvU8Xeq<*3*h!6BVuB9{*Pd=swRk-4kP8C?d%_DI+9<6NM9*u=G1_3e?z7vt!v#?q&PO zkzz>p%8_P>{>qVR$o9&SZV3N+j0~J@!phflg%Mx=TP=q_S>O1PHBn#rku_Q0`Jt1N z0ex(#9bLgI7*TbvnaP=CX!^>LXsG(ik!oY&Hrs~Bby_NjaTK^8aPzZ`T3t@d~nz_-u!^=w!3s!?36O@$%(*=14i2Zw4u|Np( z$apFL+pnF;cEB?%oymD(U7Q$cIpjLl7ikG}ov2H&%aCe5%Y4kK6rUBT0Gg_ZrGKqljYSuBw56G17?)61oqcCHLYJ# zEET6Mrp2J-u5&Cm<;vFK^90x`pCNzQc{KYDl4qaN8#YN>`u2^-q_0TK-f2V4q2%zG zWumM)-$Wc=$c>+yrXk%EoGNHBj=1FTnRlWm|HD-bKhJ5_i`eD%9CWdy%>8KzH)5YO zqYciIZ3cf*(PPoQo3T0O5|ejryRMPUy`*GV+psYvk${X(vOus1lL#1;h+xOy6e-y+LoK2Yj72erwF||GmLm&Q{UITB zH1J)#F$oiG-{piul6cK5Lb}N+LON_5D%Lg(6KOXEBTdL1lQ8XxLz3x;Pm%?ROOgnR zN0JDOPm*Z+-I2aK{Qn-WyRE@Y+Wl8kF#9>QWNH?gxP@p{3oxlhNMveYYIuccHUE!E z$z9U|42@H&gj5iCjMDMJsGv2Jt@bxh=RaQ9HxKihr}53x{f`It&C~eiwf)D#{N~mE z#|!)B(S7qYzIorpTl3BPE+?&TUc)!9=|5iBHxKTc2lvgZ`Q~YT^K`#?+TT2#Z=UWq z@4F*?cliH3{^r4b^S)~e?wd#V&C~vm2lvgR`{wEV$NS%t|9EiUJPoNJTZ^z1%=>@^ zygGA$6s;8iM!5l(R4mIPA^|>|MC7L(K+08)O$-kzn`A+04IvSbZ4uD~!YP_U(SU10 z&pLym;GRK}2%b%nc*!jK{fUWwlK|f&gl`f;BtkeY+By;&c@qO0x%VEkDC@24oZSGswFkS^zkRg;tGm&T($g6;E<1BF?FCOMZ64l(IC5ut^FX@LZ5X`&3+)E08I zX?k;4Qb~z;XOPL@1Fj_$Vn@f*^%;qpC-$4&*p}t#A?yU5j{oZ5-9p>zlk>SJ7trYAyW13|SlmkXg0{A_jb8r;AM7TAs)il9MoqGqb6({ z)ATg^ar9SpFp7 zl>P!gFc4tk2X232LjH00rUx=0)5^s%Q8FHOFUb6UaqS0mQPDS1|*W6FH}ZS7W^g$(ol5 zW#A}T&+4Gpbb(jiMwm*Nl)p$dAzQ+c*N2afQttG2;#HulSf%xyoY0^Za_Skhn;s`A z>g$@#K9Xf^-1Az8y)<2Pc=)}46xY*L+`UFZ*4HZUKcK@`aRA?=DxkUnlW<8@g&L}J z0txm8`i5j`b6P`#_W!8p-)ZS0ujsqcLsct<2W6AsBDBIVGy|P09s)VkJ)f*LvFC&I z9|A`!pFI)p*tG{-Pi`Z=;aP@{$kFdqOp>YccP z37rNpH_AnX{V6A5r)QRQbX+VNZ?)WW6z(UH(yOt^zQ#%0Fl$FD?73Ny&cn!^{{ju# zJ!N@4bBl>LZ*B>&p=kp*1vdN#tUdFRq>vQKJcH{^eQMNqeh>VS@c(cwS7u=wjHej>w$Kgv(?{V3+{+q-VqUZLW2X`OS5FvEI8CYl+$R(VSeUHXJYs}@5Acg`fR6{*{zc-YWG5mC9UF0-U{-|-adV&*SwzM#KS z6)C%~KI+gIfm%zj`hkBFouVmT6F`^vEm@Q9B63+vs5%tzNwNSX{>jodd@r|RF2zXV5AYDWD_QYhprjtM^wkr^^Tv!R3&@&_ z`_{*f-)9&Isu=7z>`04UmuFCW`SF5+IG8fu?$q_SXzba|OaB<3_RmQzwc|}(V)O)I zy#G-%ts6W8pCxYpLHM(k2q>*Djh-Vf4Z+-?ooE#-z|m4au~H|kj4!6eS@t-Zbb_D! z#npqK!g^IET%5`~irIV87lmTR>Gk8iRo_Wf{yJ8H&+U;U{t5zb2buey2zM39NUYk8^$23vz1TGNQ zpAS4#Wgd6>9QrP8qmeXyOT})(7p7|gMSZWn$s^4b^Z>LRb~W*gR{55gi*mk)L%l!V z(r6Y~Rp|$34p-bjRMk;HM^V%=DBy(tp48ISwf=^`rp{m8)JZVr(2{1%UbQjrp{mV@ z&zT(rREP4s#7IHhs1#cDWa}8)2e>rORiVfKDpr~_@YpMRN}5=L(=+uZ++UyrJ1Q;P zEPVJ)I3IMWnNYPx{M0U!SCt`oQe-3Gd9`lRdf*q8q<)0+J|1P;v%t8;U{53Mm#St8KKVW3D{3L=9hTzCvF>lBx~_qdnHZN_vn0f(6&zUP1vy!fa~j8{myG zXxD9H(fDt{!x!0PG7NOX#+;r6)cPXF1_oBG5t6Y1V+BW*c&1fN6OO-&;Jqmt+eN9# zW$#|LBxkwaBt6x~hX0MYP}@(txQtWMY|UtY2Isz`_~{^N&Yk?&woqx$nI5%$KCygp z8IPK+@Hqqh`u*zg3o_c#2sooWhjO*7>I81{DoqjAR!4JX7sKse>(uKnlp#@vdi+{Q zvWET_wrg0Cc0D2g_gF;84bapODLpiXlTdb7Wrc0PtdqFlNQaKw7Y{iI#Ct3|RD!Ux zFu&Fx2iFY@zBGv3=28^I?*eUA;}^F?z3{H^5~l_32ghb3X2)Qy;S2f9GJfAk_WP$> z+x_HDmqsx~rDZdN5_qnpR8B2T7}_2~{3xgRtjx+=>ZwL#9xT{VP!ZLm+Ac;m9fW;(xSW#hn9;{4~CS%$^}kI*G1vFRdV*P#v?32Lo9&ojV4#yMvWo!I#xXr z;mLo+T^(-5Qgb>rzwhOG2+WZUOp5LWV+#KMx!Z=F{-(2zXWCo%7?M7DRw<1g0}G;^ z-pObAChrQH8?S{V$hTN+T@j$XhQG(5sHJ9vqNuADv?S=QtSj6sDQ{i$fT^ql!aEBGYi?o88a9Yv=FZ^54(8#Yx-Q-i5u z+fH7i?+Ixj8hfP1K>n@knh&_e{c8E4rha$yES(-nu+^l6%ikp7%TY|x(mnh`(_hNP)Ss4Ob zcf>(&KIwiIOFvuZ_MAd%UBxQ?(2Sqk)v&o(E&WW$+KjXSJLc$*D4j)0r>2pmDE-jW z*^`C;;NnX#St#tLupAds4dh_8(=E*c92wBK%f}@Tl`cRCIsBMykFis<6 zd5;+X6+l#6uH1&3D2QSX0)ItfapGih%_)rFZlZg!W|PgBunNYA4=w_ZuBAYjESSH( z{wx5GWZw&Bzot{E^l45xmrIJAG{M?jRlopyAx+JUTck4CYD>pP!kfAUEQ(PH|Gt^z z{u=zk%Pb*{xYUH(WX@*xJKHpiS|o-R=C;%&h{($PFeH`FLQAZ9`~qyNo;4o+cl%}` zK>XS@ucX=-`%N2Vn&GK3(Vs_x#5PbyR%P`uBD#7$l8z%vx;b`&w`LVy5Obt!H$Kw zwLTR=8P?X24!sC{rO(5n_2u|Gult6kCKZR$cT*K>Rbsr}RD;BW)4~Y3J)}4u`HV~z zrGgo%b*%atHQ{@1$HQvf9HjstV%+&+U44WF`Sa(J`8W$pGDg%F9w_!Gb&uI)W4nVE+{FDaI zaV3y5s^k@$t%6gt@=TQ>tJ%oPb<*eIeXCh7TQXSbtA#F}GyUuIFyM18))WR zHnMtox-?xIVFJuiYaEY>RURF3z6pofoCr6;RH@U=Vu7o6>P(`fHbk|ulJFXf+gfT|>NtP8%lqe!EiX$W zs^#u1V<Ac(b&Fop7mW{)%H?8eS4Bo;Ru>nO^~}&&(mVI5yxr0D^*i z(cahssz_E>HgG+NCwF>`OY0YUh^3?Vo%jhr$=7E99yKip?%5*ta09~Fw0|5h*Nlvs z;X=V*s*F0I;L@G1ntj^Zh^jd&y6( zhXj47))9ZDq#reN=TjX3ep^)-|3y2I09{3Pu1NICR#h&U)Q7ExG$q~2jqCYn;SsYr zSuYW}QM(1xcep<0_9TYxR@m(QVbD){I?}5hx;Uq1W0Nr9*hua9gTn@6?yGp^xix`9_MHHo%L)8-Ey2sC z!p{6Q0-6NqB#YDV2jzI4GRk>Vt>SKE8}QF6sTx<@jo7)rTXo2!Y4Z3igZAn@RZop?n2f-2rWH*Rj3 zs-TSlJP9)pTt*>|PT|b{^}~V7c@kEEZkBb9+Tw+h4pfye=dK)Bo41sTMo>D7oBRj; zfST#=FBOD%!eNJ=#_hjqQ2jcD+;xK|SeTxV&8h?B1qSi`l&cTb%j5IT+B=$mCygw= zyrN83#x0%25zI;X)GG(u8ju1aCK#TF&XcJ$)8K*Zt`Tc?8HlHcuPZ_yv~dQIfN*M` zkdcfyag6ZKx6qjwiBlsAMV3a@HW5=Mtd7)D z1=iOyCFLV0ZjN*VL0Cdg;gw?dhhi0m$WT%1+jac*>ulWU-Io)Gb zl3KTlq`ng-+(~l}kAgKxuG1C{KP`G~oK0%J5e)@A|3Ai+lqhxzMX!rabTUl>U(MtT zdo;hpJGHdz_t4r^i#|Hb0ig-y6ZJE~x zB3b6#yBl1+aSd~E?}UYpt3gC>^JBR`ECQuIgCc(TJQ_3R;+#b6EL`QpSS`Y+)-+NH zX~qkO2_WbN!uc!$YK!6ox|r|soSCNmsBpH37uvk>n)=IOZN&Z-Os(|74Hy*eJM`Sk z>5JLUra?C8jnk=@N2S)we=sgkbo?Z4vTJHmwC!s(40qw3S{^n;2h=BUD&5fG*UHiA zvSgbhda=&`HUjVo30;#YbHs*MlV{~+uz$zuhdC{=THX6({YCWwY!-T(Kh^F}vB`^A zvTfKVUmPYYxIA4NR|RFQ&t%Ea%jztyG`TA{Ek=X5;Ip*j#V;}xI_9QK5eH^m{M6|+ zsh>M9Qu>L=)`>h!t# z=QB^*bL5Zb|DH$dhl==N8Z7JBX%ku+s{&UMfckJ<5&hHiEEB-Xgs8wj?7N%4Z{_4+cBehumH#cob#Jvkv-{{p z)d^Wz={nsS&4tw?d|!&(So5Rfw;NGwIkamHJqp&bsNE`D|D`hsW^puWKcl@>)ojDc zVG_iTX99_bu#2&TB}f-bV}&=eD*&sNsb?_Nb9loT)|iE58R`qY!E~wFU-K+j(HBzd z%q{3&MA&LnbFC`+H)tMs^Z=Xj7QKSMRpD^Bn{rFSBN~pEEWK0j6KAPdHR(2{w7huU zvDSp9(2ZC(ew))OnML~WlAyxcDyhGP5_M1q+IA91>BjR!e>(A`X9Lfr*IKE9awF~Y z_#7njBKrx}dpcU64qdCQ$W2iEb};9nikOV=VGO=TyUyHIO8+uanoP?GpJJk6X7k5| z77&Vj7j_%38t>n-PD;3 zfxKcq0eCkHm6&5SQ4aE5VfrxvwgCpJ;D;$m`1MC!>Cg0~c+isFSSXj+=3#n&en&Yy zGwtT$i9|bO!K!|m({0l-P$A>Rk`ohvw}gxY%6i z2hIDX`uAp+ba)+=p^4F>&bsD3IvQE4nA@7MFDny><3dqD zjExQ16{Z!14TOw3@A7Y3j9eRZ!);avhMcAe(JF1SIAsF--U3UbJl&m~tbmXyyg~=w zz(FE9ZS#cO^|LZ+EMx_gBXJ>zU+)q-d@bM`d)d$6EgPK@eoAq0bQ^na(+e*1^njT>-)Ef{Ss9ig0{xZqW_wZDm6U1 zW0Y(VPus|dgEiY9=X2`6A7(2!dGit~x0RA?&e~MEOaEBc>})1xJUfql@Qa)r@Bs@S z`%qu^2-0>w$PW8mF`6X+J(z7o%Y$1m28QD8UG>*2ZfsaZs`D%Q;5#%E?{Sw3(k=wQnS}D@@Wvu;-1>h@DSr2gcCC z2*df2`cc&+LF3wKVe=(9A7%w14qIw8L!jk#Nh~UoD5l1zmSzLbbs#NH8$m|l ziOxW?51ti!1yy-j6&@OF1eQh}8+g?by?bouTv?$JFo0OuuAvrY`5ECHLjFns&3!+3 zBVbfuj)gW(I`hPUY!QBO6mJ`}=I{@lYJx7F#4OYz=+&P_AR}s_W1N-!br{X2)*=O+ z;eEbT|8E9Q8zg}t)quZ8S>|NL!Y{Af4BgA|%=JDa^}$Hn$f?*lfR&d_+7imHY_!Iq zMCW9d-@in%1txsZI>&l)63b_F1v<^9nmL&AU*&+20@wDEstX+5_W~~cWp&Iqf-*22#6|(whTOU z;VYM;QBrZOMb!u}l)NV{#kv)gflAhRVRC;%Ykr57)YV)AD0lp>hn_juKxT872M+cn z1UBExIDgOj?k+_(y!oltuBq;Fxs$MbABI7lQDy?U#@@^7R4{L|ZN&4Q`YPjuf7(`3 z2tK}-8KoXH@^+hyzn3v%aU(zoy@mdf=b+c@an!NRtC@$b9~3RP(CBGx+LmHidvtWw zXkaZvsQUS1ZP9x1DHCqJS4ImJSEX0%EJsU2!BJO*`fK z^RjwYlR+P+rd19-5O2GBjQbjbVxSzVOgdd79Nalr4bd@pAC#y1NV@T2A^pu*)(sh~7$QEL$Pda5XN#BSJX?TSg%ZV{< z;-lK8m1)8jQ)-UU>?)YuuI329a+ zUcEBF0UlMQGG?j*TCe=3^43Fw9LRuZ7Lw(^8l4?^3|h4DY-cf8CTnFvI4wx zc{YbXT{7dHb7@=*0_JN=d@j$lm5pSc`sedkrceKesW=JgnwC_a@8-K_oxf6@H#R^)zhl%_e4e*`u9o#Qr9{8esH?1 zx-fDs2HuH3@@lNA5qiZ4Ym`96U;dKBo0ccgHg;wT__%*AsD+3{V~y3Il@hb6asGLR zvu81{1n2cW5^;>iwScsgZ3hITu3Z6eJI$ysVZ8>_c5T|5kd#F&jjL`XF}XGjJlX2} zNae}&_~S}P}eV{|e;irHTnf|J+ckE^Qd3RV*vAY6$LB2*y`hq|ngH@sS z=6vg#WO;$;e0upq%i(lGyCuw;mdE_?RD}BERE?L@CN$Y!rKlX+FqZ@P+x^I z-lYjxc#4a<4c=wAIV4xvZGAvCBR@d^7)tgQ7nA8f%B?yVGz@W4l{y~s+BYtC9rSeC z%~Ah_G8+6!w7(gJ;nV`hGN73y7$%K+1OrN4K1n+`TFW-LEoqdzXe#0Kc;dHg>$;yL zye)64534XgQto-PeJk}v*j)8RM5|hn%~y6~n_*o>4AvKT`vh|ez^Qy9k;V_^xp&3u zybkP}az&lpF43G2Viph5IjF*NMaRVaNifPwO|bPQ(x5!MXXg|Y{0rz;MG$}K>J{)= zpspatgXD0#lnx3nUuf6aK!<$aVT0uT$KUg4?hTGqr7`QG&X*6#XV{kZ(^7rh_)P*y zsW53Lru{(KYL%@5ia@PA?|3V>M3G(=)>yHyix6gMP>4sxRnNU?T%74+nSdLs&evYd z&n}eUGJAb&a|sH_ynqRgBMxmHTFq3Kv=RjMZy~MI9m3WtERK073Mg{W$P*+QjgQVW zyYE1nmX$g#&j&MWuqSoo?aveMRoZW?#_`!Q7u}Q<7HsUmU4b>n#&SzRJ*iw?Kvu<% zYa8}p)}L3#AMo=>Lw;haX5Kt?H7W|NU{ie90(m=cLPzgI^YZTd#cc1abX<}Eut<3Nw36b69n4PV9wC$jw$b~llXESTMV06d<(cB>;JQB5I;%YoAJ(O8BmSR}*S;WKR zo@}ZU&4wwjZ65|rhAa}5!M~4e;~RM2hnUt5F&TkX7@9i3FYvlY&J}pu?If(OL3{lV zTB4I;t^tjjsfO^GsuK@P*<`Fn0S^eNV)q4gL(`I@eHyDB6Ga3teGDIST1D&gnN}p* zM#~?+9#U)W$;JNUxuaPx*dtOI`b4c;A+{KzBS59%1y(lG>TRHVrTkGFIumxU<#_NT zNSr{A;f!`hC-0a{NlkET2OPvsVXYV|2XBuoFH=k#ROS+Bcw&d{8Mbf6jIGKk+v+%^ z{o5^De|A02N&|=a*KX~59n)KO?eQ9LT(-y)=1mEp5@n*lkB+eJ8~=Bn8169JUt*?W zZqVSREPSK~*ohf*U6`4*(#>e+tn~go3$$eU#UCaWOx<>4Vpgy;ehks8LWH^O*r!ZO z3F`yu12W#hFG(Y`v9})PEil00G`0a_kt;NsCjC2(ETV|lhbN>nNk$bCA##B9u;!rP zP|Vsn1^dK5)ZN>r6X*oxi~E>U+(@bHDNgI8vfc;Td~Y%2fvqqcCO%@>k7s<~ns4I! zCfz+yGir*AuSKvt+*oVEz$QEq3Z9R?6S34rORKpW>(&+vx;h5W!+N>f;!Xr(1Z_^h zuS)bhM3%?hjg*xv=dXvfJC58S!AzhZ98c%s3Rt1KZ&#d^E@sexF{#mq z1N*AZ@P>;75f7b*wq&NEIRzp)htOLmE!8lzS?r2@gHnh)h&$qIx(3ZQ|0vFGF9>JCx;+J!F%9gF2yfs^)MHrS7`36uO7(oIh)v=A;{JZI4=uPW4#ton`9QD#=oB>S9Obe+!)EyMcv07&AZi6xq6kD*`uR4sAY%J788$|#x2TH_ioSiZxW`X zwyg8Oi#B9bjm;P0F+eDDO)nG21*v!Gv=G(nZ%vxATUN21^f9Vcclke#*8 z<#}}E&4Yc|tS|YIAgL7bHruxA3)hp*p(W@vQ%oQThET!9`M%Fc*q$Hz@A8zAzg%+8 z7;pjOv;oYyy=^e@3U0|bY+tOZ5th3#7z=aCRI^YpLdtaz)Wf_}6OuzkN9Kh;LOPvG zVvGJ%#-_e#oaQB#ebTkLS3UHLVS96k?6SYOJ-=3<^;xau^wnqY)$7Ao?}OlH?iUXl z63)u5$UuH^7L>r_u1ah-$L!dr<>76!^BtR)TH(reQEeZ}G>p|kyLu@%(Q@b&ZUP!r zHOT@cuBP&OPD0i2l)T^O>5+%2>65lQJ9|z3{NU_TTo!Lt%<5yqM`p*HT+A<4M_LD{ zzle~pTq|<&*Qb_I_l^eQVW~b?9)ie{`Qg@z$Fi+?oF@|{v0P(zn?U6*MXGy=AorIJ zI5$B0@d#uiEr9ZOyr+F+Z`+kwj73`5R+BAQl(-8vsRRn;fqH2a^gwlu0<1#l0o)Z@ z53a{SfqsyFFjsSi;*mNTgkos{svtgz7cFG0*A5J*)uLF1dv6Ob2hYR})Et~+eUL+_ z?*0bHT&&!|V#kt_T)tW#EEPW7c`}e|-$N9&H8#yltVH6>jlz(wcx<{EOOK=Fc|zTa z%i;G6po8SUzEGwIIw*gIDAv){SB=Ua7EJ44p!+ZDE&J)D^|-#WUpd||*2wY3lIN6f z;hE)6^k^JI%kAWjzS@KCRrn=!OrvFGYUg}3@E4k$RHr0zJ7Q?)#sjWGtV zO5Y`7M-57jwkLeWW|RbTReHL*H7X5pOrx^)*A1YJDFE^Wz~2jHF59mtnPAc1Q}A0> zPLFw&^y^_HTUc^Jj75qVflwas@&n=sIE+ zBZ8hfo)i!R2bI`Y84oUD>jvF~6Btv^Js;X}cd+$F_dN|v%z@F%3nTBlrZl(~P1Y`~ z{7G|#6fy{Wkta2*%aX~*`vFWAsF;v>j-Z{rW47gt`XhJr6sT@U>$o{1=(=(jJu0Of zAhbj8yfDE>9jq`^{bHqclE|&k(!p}n>_3glKwAS_}TT-(7 z+yNN(Xfs$Eg2hpG&ax&kde|hWcU{y$+`c|-Rk%N&HYn@xxy;2AnOiCl%Wd~1~ z_U1Mi<-IdzfEn)K$-ux-6jz9F&A28a1ng6@oGZt7(h;?4!sJM{LL*>=5%*&MO?*aq zp=akeNKLcoQH2@fIF5OEsC});dmhAbp{c(ld;hOstV7UQq0X{&3K*i@ zX(jOH@a+pD2Q;ZZ=HO1M4Ry&)ZeGTzXedGL-d(Z*@{`vOu|#}i;$JiQp$pXoNgh~a z?jXoZO@ii))I40cJ3Tl7kkSSfGDA7x4=PVtJOsUIsh7OHu-YU$Z_XC1*dV2Hc6J7~v&tVY&_AU8K;Yb3x%xH>wUY zIqL0wF*nL_$VG79$Cd1=)U)}1Ul`g}g`3MmAKSajJ*Y8IEX8uZQStll;{0n;KiG*y z?cISUwrs?qJsJs4fecx#b~rDF|2acy5oIL3X9WvmNvNvyJLrzv^A~HgFlc9cvDD)C z#zdh%sc$x%%yk2WP`|JVivBuh5~8L2VRZ|L3YmV7`}fB8^?Xb1FLZQ~zhXEKn=075 zihCo)G!9jnUQEB?91yj;pZp_O%e8WBq*Mgbfvh(nvn5fqakG#O!`H}T{Iy8IJ0R+f zk14n&FqdLko(^5cRc7%?wunZYh-H^Y+$KtxOP(xU#&(9mk=w4|-h0-%${#d@NzIEM zHEZVwji$BY8DP=(s%ge!SuIbd0@r~FlGC4djUxU z=Xa>e4{_6#O1)Nn@C$2@mYinLF+Z(*c^<9oh7dqYmXTW!;iJm0?oimiWX4!dtnq^a zq=U)|<-b9#>PTXxq|sykJT_)>VHbAv+!}sw{5W-}7(~na>UwA$m5S9X>y<^Fvuf8Y z0t?pcS7RUwA1&-rOBmD_)|~1~$$Er;1z#Sxn$qtM1qM!{cp956Gd4DVD&`mi*W(SO zqB6__*U7byHaInB+h=O>9CbAqc5`&CnZ)CsHQ5Pba*Gf*x-7%?`L{?OA!G7(hrYfg2=&iY?tCJ8IL;@`YIbm|wlcP}=@T#DSdJ=i$%q1re=Q6h{} zuiw+U&}JGLPVBfOqa$+*BZ;&RP*zo|j_6tBMQoZoo?^J`Ah@Y$Y*{T@btoMGWHJYcN2-8rRx#L5w zGrp0hMIPP0QIPglwDFHvu=_eota{sM(o8k*cg5DmQU8IX!^ble3pdTe1oiIk?vd)~ zNj03=l_Xh6+gYCWjpx%WmYIo73Xc%L75R!aVYS7yeOodvn21EwK{7zn83byqrWGqc z`?hX{6qp^_~KOB*t1 z0!xxx6Rq&y?8KtXG#fgBe%}1u$~;Mq{-Rpsc&(t)BeQuQJfZ*GXs+!kOZPMHN3~;yDZW~taEyv}t;bj+ zpd6(#V(U>#p7Fwp2$+Q>TaQ_k6fQ7c1%q)Esg`jawnEGSK)0k*U{%SKEzv1;&4`LgA@nM?iYTdax?~RP3W1Pid3;V!tYG zZWyLyO_FbmyH#>Ky!#5_d0oEmw0R@iHzf6@eI_41{ayo2KR-z6t>g+bSTYoX=UIB1H7ho(r3G=0s z$xp^|Tp)86U=4|tLYUm{1;sQVk5w8G5o0w2h$0xpinnBaj>0AR@<>ybzDI$U(FOk} zxs-NL$)kc)mk=4raGhDntxRw$#oWpWNh$6^FT(%$cV3SF8Qys({Lkpli`VCEj;rz5 z`%k%-r>AU-z73Ac(*+NO;O;B(m&SzdvJjFVFuDUy?QMK_kwc*<=?+-$vY!2^)5R3u z-`;b2dp_;Pc99;j@U8KvNph4GuZwQ>Rn||?r{a68g7C2UD&bjg=z zWD>q5=cGCXNiYf|eR`6L0(8B5){LUNqWwsSU`tZ%FEl!eJvK9~1buA1&oa#Y_BY8x zKvwVZ-zU_^AfgIa&UZTmwDgZ+Ww|3kmsU76G>{Tz{g zMt!y@^Vc*)SiTSG17e`4yJG!+->2Ja-|a|J@oGJI@#4OZHEXmbZS}h)!ki#vg~D`u zI$*T@ZH9sZy^j(3)oIfOeN2|o!?GdWl7(?Xgbulx{uMVm0L>2-UW~!5ryOZJKz2_l4iZ zy245wuz`z}wUqW3FWQ2DQUm_S-~ZxD=k5HCzE@TSm!XIBLk--UINg95n<5Rq>LlwE zZXa&mQEdomnPTUn4o*q>jye#w%usllP-w|l?KW1ExGEwR z7#9(x?};dN)gJ`l2Q0bk2Rsl`KE9dU7?Mh$$Oyw`1Zzm#2r|S-0>@6llwfx`TfA`lu z^QWTSV<+q4|8Cl?KfY`;^@wE!D{L~fI>co-lOeS%)!C(s?INOMV#WSh1H+m~MF5W{ z+^eLT9WlWBa^MJ!C*XPjQ0sQuTipG)dD$;eUUYI4@A^sb5Zwnwn)OduJYV0F-Yn9;q#sfC7IV0Rd$Q~O z1fgun-Y0>Wzy2zJ-lKwR_597_55fIkvAyfG2~fL>-3yHGaZMG;WN<~k2QvO;WFwu@8!s5~r;>TLpawZPRnmA9TI!lPvQG2a)9dC)RG+0G{ zN`yxh36CmYC$|xCo_4o6^*>M4^X&D!fcX-q?uF6Ee~5`ntNkP%=>XWYe(CHG8O744UQ{Nl&#$E5YgR_p6DDO#^T zqC7pI*0?|F4_ceY)}O=R0o~(C%VVq8+84!f`SK;%P74@dKT2`>pOqAW?l0KK!h+^1 z^)lx99a8VI_5C-0X?^$gUs`_O_gZhi|9;u}_M0CBk}gB~^3Oc*Y0J;}@DM11ffK&U zv+eSf#bk!nnPYX=)lwGSn?HQ@`v?z^PcI(Uw2@unogM=0i6K0@!&C5(o?;;#g6C(X z;2&^7e}GGtpdZuSZm4GaR<@cu)N+gLgiQ1!4Dow~Lp+|VER;Xox4TwQmKC;j>F)w? z-GPE~pqBm*36go~w^S~cK|A~cd$vo8o$U}2iDkRN;;0aVKBOtu%Oo!;BJBKQEgG(R zhxC_Tntw83?z7+#8?G2R4;1YJvh=1z5A{de*a{6f4<%HSNL+;W8*D%v@vh(j8-3Q|`IIw*ZHUXL ztNxHYs5+dGzkSj%e8apynBycK(uEK*;<@@hK?lbYz!3zbawS8HOz87IRS~Le7x}#$ z`&z(5@;ryU1TUU|=TE|~ngCJMxN7WXx^eqTvAjUX9IGn;I|O$5vq1X+J?_Y~I&X=_ z488AE`R=)p@i_BrS%qrGe5pSUL%jf<-7m< zhP*regDBkpHri%sp6-hNn;?(2G`B-b<33pX-IbG&g`){9U8TaGe01kOZjv!V^m$GiBVHvO!6)BJgCNF~Rhl+>FHAjM560F*} z0UjC#d1#ws@r=8utLK06${)i|E*myj!S7Zp_DSG7t0c&0chov;KOjmlojFo&prOh4 zaggU$P~DF|emxir+7#k&!Xf}e_zWV)c3``QL52(j;o4*DYxYtYY{bCu1TrgNh;duA z&^{(g0VXAYtClGPA+ul@V8#Hj%p72VIRhy6J?ydGBN%8QfZ*)cB@XNb!7y(a%yy5q zA7F^L9D-hBz#yoYv2`6arHHVpf|0yaJFs_*PY$^E8r&gD9DqKTnIlAl*Nd*tYSp9>IHIr-kv{XQ3=J@)!xfQjsWm!SGn$#y)@957spvNQ-` z04_!3-LKdQ#HEPF+4i2NB z`CSQsCm{|X-syDrABOmvk236JX8{N@;+lp|^+5n%kP^cKY{W$-!oG*Yz>S1fq0!aI zDaNC*0>`X42*zcH+I2QN1Hq;$7&HGc;7kQ{kFwERh5PxG7tzjsOOZk@%CQCma1kPR z3nkiEPfvxCWR#NH#tp(n>mdqJTG{bj#3JI9ss!LcTclb-v@K4VOKXjU6ryAs!A58- zJfPs%Bhu{XhB244ASA4XLAb0HE{Pv=Nkd_zag|Zm%l~l-oGlp(*Jw{*szE1;Ur_@p zq>$JXSRIUK!sS9odVR{rAcM!+m`mV}T%B4CDfL(t(CgAR!2n#~j=?F1b>aA}pgo0k z?)aThFqxZ<_e!&>04D>5QRmJr!kf+2>o3m>wyLXAvDI-pxWOvZwpr<|L57>-L1 z_5UgXr$XGZ=UE9jQ{}M=P*}hd%E{cR5_Tc9%`UBk<5EQCx_InF87$?KH+EwjV`lQ! zZjK97Sjqr)3?}bYch1?#(}4=+?&7Jh!a3ZG=}-l7`Yh9tP>WG)2(u^k5L}WA8v1k$ z5_kYig@#HGN9+>9T{4}P0t*Ao6iJ04giXXlGK+H9Q<*LVz1uioiPzTHl$NI+XwJSt3HLKt!_K*6eL%2}FqdK_XFGC@ z0oEDwi6)2+AYuFVr^6<9&%oz11I{UE^SQFJwGU!gb<7va5}L#4%wLxaFT`PofrOHZ zk`LG&O)5L%tp}24>r4)>V8LZ)ABGOTz2=g$ImPil&0z6* zEfx+?3{MNc7LGMp_ur9#HJJIY#iBMgZFqdUowL_t4bbWAy`E@r4#o0%szEu`%4@ND zpe`7?>z)J96-|OxFI@yfE0=Dvxj@~=>Bhl#E>9mbJb8u<+v|5?r7Qmc3)5>cwPV{y z5Xc)LO<9C?+rTl=e4~2axpM*Y4c9~LC3|H6lFbU3#J2@6o_hkZu}4k7&~ZJ{FHMtPpPD3zx6_^2tDD8^GNc&xsih$;LLW%T+X5Nayu#SYe^ z?w;hq1KcHV#kz%CalV~P&qs3(NGK%+RFg8qqCP-0%6eMw1gn#`uN9co0rN(<09o__ zkrN1~dswpHz7_fw(?qY{1)-$;Zya-(x9_Dx?N}UrC(ZE5q1fMvDFVIe8adX+-VF_? zs(yLLrSCpX@5wPAM7yC0(o$^H;uK z3%G4pVNN%t|<8oP6@1iTljFe=a?fcxY<7gy}!XV@o#m*w9JVa1x-;=z%u zvcScp^25@N-Lj3VWpCt)#h$!a00WhK;-|2BclG@{6|}ML^Aca6m`c zI`|@t<^y`9M$d&xEbGUCLwounh=zjD{Uh?`BMi#>9V@*<UP|P zvDNiTqEUV*j2>T=?4iT4&}65~+MRUNWe&^@5v@ZG`_SD9cH1BbDlA>*82H0U$qBMu z1q_B zg5!Ank+IF>5GfJML$=n)LNs<{PEo#hG+IRTWGNnqFzZHg%jwfLg!^h_Y&q2|viYMK z2=6ulBC^LQ;4ru%y2pTaT3!+Cvo8+u1cnf1FV9CXfY^&l@GQu|>UUzrg2q(3?1+fh zA#1m}WBbz=5j)s-j*fZc*w^w!#Et|5_sDz^wUgylE}t)Aca%s;7*0fqMNXL-YY`Sz zbxv(Shv17SofzQ>txGgSfDQw4O2oL7pl#JIoi8JF)c7SBQqJs@y`jniVpT#3cY*=M z94|c_9YOBCh~QysEP{_J1K1=!y|469MSPEeMqkP0NE|&NB7Y1N>eyGYxOIXE5JIO+ zEr)-?@*(1c?l%s?xY&l|H9!sJDTg((h!ctu4LrjZ3+^cqFSJcG|KVqSwKzL$)Sr*= z_P0Ng;n$~)9?p=RHpWlKNEOjWAF_{XfsYQ8ECP-;cv_$8OYxfmTs-wBa`NgT;Y|rZ zC+9Gfxu59ui>oOne9k_ziYEW(f|aB@1JEZVv;4*_5| zY7wi;_R}4sOhh1YDCd9{VMveZsW@#a)r<&6;-D}Lm&s3#Xc}LHBN2eg$`(3Zt_Vn9 z#3GRw?HnQg{#5QNkW)kQJuwjUtr*f6_&y83NF9GFCo$N>!PZ^`CN<<+skV<4@F*Cs(RUIhji=ZS0)*+T6Hc5eWI*B4cNr816 z0}-S|?h->6lT$8<5ImME-C-;2(P00 zA}DE>p0bLLlHo^W%^J3RGVBN)0tY!F+Q$M6)vNBPOMKrxqF zQm&vAe=b9`xBCd*>-0sK(dGmz9uZvh0MBhjRMAHiZ5%{!*~n0mIV|#AMq}S2vLy)% z+5D}ljj9&<{ka;vR-Sco4}Z2RT&JRXWl%&RecU4ylO+gP zO^HaPhv<+7JQa(N!4{jx{i!G%BSZu% z;YmKcPwa~*C3#4+s%1hT(=2ob2>T*1i7X^K^pyxq+5~c0#}gJVDo)D!3t#_<$obi* zfXB0lVZwtxJn=x`KvM??a;+(O<--x)S=_#(cE zfmHGOwOryOW)Yo^mEq66h<8%JVK_0Nz{+47rSO)#FM^%I=pNQwBF^a;p%DLdbgblv zFsBmCxx^r1oI*TPaV)EfxTc@e^Z@UK`XZbu#_H=7I5e1uV%i0;trRg#V$Tf*5))i< zQ5y^)4mky|TPH%8@?+}gED^r6b-A`8aESoGJ^6;q#RlzN`+feKu?ABmm2c!hZ{%=8 z-%J&VN>pO1h_;U|seU6os8f+@8$cd65O66OyP-`vi{MiP!;7PtnPZCl=8fzr*Xcd~ zjc~BgLlh*1BQV>4BWHrABy@&?`ER)FkgNGNj@@cs#Guh>>M?Y!=ZnBI3P#_fbHEK1 zIP3x-$1bw}cBIxzPMaUo$Q3@}i}*7NO%D$0kzzZ(-B+Ygdet#P|E;=-&K)rT$uH5- zgOm>SLv{K^+4zl${=p0)Y zclkIXc1}W7$<}*$hW-FX_xmDPZg(m+X>4#l-EPTSYk@0{$ro{Rg~O>9v2mLyNw?&R zK)B)W%Sj9DCyB5)%01`lL|??hk;4*>K`%n#9#IT00p zZG)IJy>M)vy_e$i=yF*_s2h)g{fPU+v8pCw-L_k-<8#=%6|rs@=0ML*x{ho3BH9f@ zhEcvv9~>h<1iWFm;DBIvSRT39R<%?Hy=`frv}659guT%KJNM4BjCzw`j;<7OZ}jRJ z7((pQrpHJ38ifdX3mn;8V$dTZYr3xK&ew9Sg+4u!om@1D2}YC^`;cs01|J;!l8Y@U za3A;cMc^BrXStz($CWKZ5!)ufKtnYY zk!_pEIb|3QWWXe-u!bU{ElNM_Q#dMqC?eX(8d&VQj$A>X49 zdTcXJ`3LN`3`Jzy0R_o1fNwS#4&-$8F()Cyp`+n)M>a{&wg?ej?}0((>?7a1=AJ`e zUILSm=SmzvLY7Da;8SWt5z9u1&p~4}6me|%9*>wjN;an$T+JGaShhSm9OFkafrtA} zMzVc)3<@m8Fi?5ypl}{v82U2>s(MpmC@-Iif

    >RYPAMTZWI_3`J}ljf&3EHiIq7 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