From 38895141a081078ad8c1ca5851db0b4e9d56b2a5 Mon Sep 17 00:00:00 2001 From: Brian Long Date: Mon, 28 Oct 2024 15:36:37 -0400 Subject: [PATCH] initial checkin --- .gitignore | 12 + asie-api/.gitignore | 1 + asie-api/pom.xml | 70 ++++++ .../alfresco/asie/api/CoreAdminApi.java | 91 +++++++ .../alfresco/asie/model/ActionResponse.java | 22 ++ .../alfresco/asie/model/BaseResponse.java | 41 ++++ .../alfresco/asie/model/EmptyResponse.java | 8 + .../asie/model/core/CheckRequest.java | 43 ++++ .../model/core/DisableIndexingRequest.java | 42 ++++ .../model/core/EnableIndexingRequest.java | 42 ++++ .../alfresco/asie/model/core/FixRequest.java | 90 +++++++ .../asie/model/core/FixResponseAction.java | 34 +++ .../asie/model/core/IndexingStatusAction.java | 25 ++ .../model/core/IndexingStatusMetadata.java | 56 +++++ .../asie/model/core/NewCoreRequest.java | 161 +++++++++++++ .../model/core/NewDefaultIndexRequest.java | 78 ++++++ .../asie/model/core/PurgeRequest.java | 106 ++++++++ .../asie/model/core/ReindexRequest.java | 122 ++++++++++ .../alfresco/asie/model/core/Report.java | 24 ++ .../asie/model/core/ReportRequest.java | 106 ++++++++ .../asie/model/core/ReportResponse.java | 22 ++ .../asie/model/core/RetryRequest.java | 42 ++++ .../asie/model/core/RetryResponseAction.java | 22 ++ .../alfresco/asie/model/core/Summary.java | 24 ++ .../asie/model/core/SummaryRequest.java | 107 +++++++++ .../asie/model/core/SummaryResponse.java | 22 ++ .../asie/model/core/UpdateCoreRequest.java | 43 ++++ .../asie/model/core/UpdateLog4jRequest.java | 26 ++ .../asie/model/core/UpdateSharedRequest.java | 26 ++ module/.gitignore | 12 + module/README.md | 1 + module/pom.xml | 182 ++++++++++++++ module/rad.ps1 | 74 ++++++ module/rad.sh | 71 ++++++ .../acs-enterprise-shard-attributes.json | 114 +++++++++ .../inteligr8/alfresco/asie/Constants.java | 13 + .../compute/SolrShardEnumeratedHashTable.java | 57 +++++ .../asie/compute/SolrShardHashTable.java | 43 ++++ .../compute/SolrShardNumericHashTable.java | 43 ++++ .../bootstrap/ShardPurgeService.java | 45 ++++ .../model/NodeShardParameterSet.java | 24 ++ .../enterprise/model/ShardParameterSet.java | 23 ++ .../asie/enterprise/model/ShardSet.java | 115 +++++++++ .../rest/AbstractAsieEnterpriseWebScript.java | 129 ++++++++++ .../rest/AbstractAsieNodeShardWebScript.java | 35 +++ .../rest/AbstractAsieNodeWebScript.java | 52 ++++ .../rest/AbstractAsieShardWebScript.java | 46 ++++ .../rest/AbstractUnregisterNodeWebScript.java | 164 +++++++++++++ .../rest/ClearRegistryWebScript.java | 32 +++ .../rest/GetBackupNodeWebScript.java | 35 +++ .../enterprise/rest/GetLeadNodeWebScript.java | 39 +++ .../enterprise/rest/GetNodeWebScript.java | 53 ++++ .../enterprise/rest/GetNodesWebScript.java | 70 ++++++ .../rest/GetPropertyHashShardsWebScript.java | 150 ++++++++++++ .../rest/GetSampleHashesWebScript.java | 96 ++++++++ .../enterprise/rest/GetShardWebScript.java | 57 +++++ .../enterprise/rest/GetShardsWebScript.java | 90 +++++++ .../rest/ReloadNodeShardWebScript.java | 135 +++++++++++ .../enterprise/rest/ReloadNodeWebScript.java | 137 +++++++++++ .../rest/UnloadNodeShardWebScript.java | 31 +++ .../enterprise/rest/UnloadNodeWebScript.java | 16 ++ .../asie/enterprise/rest/model/NodeInfo.java | 48 ++++ .../enterprise/rest/model/NodeShardInfo.java | 26 ++ .../rest/model/PropertyHashShardSetInfo.java | 60 +++++ .../asie/enterprise/rest/model/ShardInfo.java | 97 ++++++++ .../enterprise/rest/model/ShardNodeInfo.java | 52 ++++ .../enterprise/rest/model/ShardSetInfo.java | 73 ++++++ .../service/ShardAnalysisService.java | 29 +++ .../service/ShardBackupService.java | 102 ++++++++ .../service/ShardDiscoveryService.java | 142 +++++++++++ .../enterprise/service/ShardStateService.java | 60 +++++ .../alfresco/asie/model/NodeParameterSet.java | 33 +++ .../asie/model/RequestParameterSet.java | 5 + .../provider/AttributeServiceProvider.java | 50 ++++ .../asie/provider/ObjectMapperProvider.java | 27 +++ .../asie/rest/AbstractAsieWebScript.java | 227 ++++++++++++++++++ .../asie/service/SolrShardHashService.java | 27 +++ .../asie/enterprise/backupNode.get.desc.xml | 49 ++++ .../asie/enterprise/leadNode.get.desc.xml | 45 ++++ .../asie/enterprise/node.delete.desc.xml | 40 +++ .../asie/enterprise/node.get.desc.xml | 69 ++++++ .../asie/enterprise/node.post.desc.xml | 42 ++++ .../asie/enterprise/nodeShard.delete.desc.xml | 45 ++++ .../asie/enterprise/nodeShard.post.desc.xml | 41 ++++ .../asie/enterprise/nodes.get.desc.xml | 65 +++++ .../propertyHashShards.get.desc.xml | 72 ++++++ .../asie/enterprise/registry.delete.desc.xml | 32 +++ .../asie/enterprise/sampleHashes.get.desc.xml | 58 +++++ .../asie/enterprise/shard.get.desc.xml | 73 ++++++ .../asie/enterprise/shards.get.desc.xml | 68 ++++++ .../alfresco-global.properties | 8 + .../log4j2.properties | 3 + .../module-context.xml | 24 ++ .../module.properties | 10 + pom.xml | 54 +++++ solr-api/.gitignore | 1 + solr-api/pom.xml | 79 ++++++ .../solr/api/CollectionAdminApi.java | 26 ++ .../com/inteligr8/solr/api/CoreAdminApi.java | 51 ++++ .../inteligr8/solr/model/ActionResponse.java | 21 ++ .../inteligr8/solr/model/BaseResponse.java | 21 ++ .../inteligr8/solr/model/CoreMetadata.java | 67 ++++++ .../inteligr8/solr/model/EmptyResponse.java | 8 + .../inteligr8/solr/model/ErrorMetadata.java | 39 +++ .../solr/model/ExceptionResponse.java | 16 ++ .../inteligr8/solr/model/IndexMetadata.java | 109 +++++++++ .../model/JsonFormattedResponseRequest.java | 35 +++ .../inteligr8/solr/model/ResponseAction.java | 41 ++++ .../inteligr8/solr/model/ResponseHeader.java | 32 +++ .../solr/model/TransactionResponseStatus.java | 45 ++++ .../model/collection/AliasesResponse.java | 24 ++ .../model/collection/GetAliasesRequest.java | 26 ++ .../solr/model/collection/ReloadRequest.java | 59 +++++ .../solr/model/core/CreateRequest.java | 171 +++++++++++++ .../solr/model/core/ReloadRequest.java | 43 ++++ .../solr/model/core/RenameRequest.java | 76 ++++++ .../solr/model/core/RequestStatusRequest.java | 43 ++++ .../model/core/RequestStatusResponse.java | 9 + .../com/inteligr8/solr/model/core/Status.java | 26 ++ .../solr/model/core/StatusRequest.java | 58 +++++ .../solr/model/core/StatusResponse.java | 21 ++ .../solr/model/core/SwapRequest.java | 76 ++++++ .../solr/model/core/UnloadRequest.java | 107 +++++++++ 123 files changed, 6878 insertions(+) create mode 100644 .gitignore create mode 100644 asie-api/.gitignore create mode 100644 asie-api/pom.xml create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/api/CoreAdminApi.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/ActionResponse.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/BaseResponse.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/EmptyResponse.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/CheckRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/DisableIndexingRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/EnableIndexingRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixResponseAction.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusAction.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusMetadata.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/NewCoreRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/NewDefaultIndexRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/PurgeRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReindexRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/Report.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReportRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReportResponse.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryResponseAction.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/Summary.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/SummaryRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/SummaryResponse.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateCoreRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateLog4jRequest.java create mode 100644 asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateSharedRequest.java create mode 100644 module/.gitignore create mode 100644 module/README.md create mode 100644 module/pom.xml create mode 100644 module/rad.ps1 create mode 100644 module/rad.sh create mode 100644 module/research/acs-enterprise-shard-attributes.json create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/Constants.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardEnumeratedHashTable.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardHashTable.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardNumericHashTable.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/bootstrap/ShardPurgeService.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/NodeShardParameterSet.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/ShardParameterSet.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/ShardSet.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieEnterpriseWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieNodeShardWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieNodeWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieShardWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractUnregisterNodeWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ClearRegistryWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetBackupNodeWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetLeadNodeWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetNodeWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetNodesWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetPropertyHashShardsWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetSampleHashesWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetShardWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetShardsWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ReloadNodeShardWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ReloadNodeWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/UnloadNodeShardWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/UnloadNodeWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/NodeInfo.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/NodeShardInfo.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/PropertyHashShardSetInfo.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardInfo.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardNodeInfo.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardSetInfo.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardAnalysisService.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardBackupService.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardDiscoveryService.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardStateService.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/model/NodeParameterSet.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/model/RequestParameterSet.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/provider/AttributeServiceProvider.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/provider/ObjectMapperProvider.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieWebScript.java create mode 100644 module/src/main/java/com/inteligr8/alfresco/asie/service/SolrShardHashService.java create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/backupNode.get.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/leadNode.get.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.delete.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.get.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.post.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodeShard.delete.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodeShard.post.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodes.get.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/propertyHashShards.get.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/registry.delete.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/sampleHashes.get.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/shard.get.desc.xml create mode 100644 module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/shards.get.desc.xml create mode 100644 module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/alfresco-global.properties create mode 100644 module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/log4j2.properties create mode 100644 module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/module-context.xml create mode 100644 module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/module.properties create mode 100644 pom.xml create mode 100644 solr-api/.gitignore create mode 100644 solr-api/pom.xml create mode 100644 solr-api/src/main/java/com/inteligr8/solr/api/CollectionAdminApi.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/api/CoreAdminApi.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/ActionResponse.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/BaseResponse.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/CoreMetadata.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/EmptyResponse.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/ErrorMetadata.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/ExceptionResponse.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/IndexMetadata.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/JsonFormattedResponseRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/ResponseAction.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/ResponseHeader.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/TransactionResponseStatus.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/collection/AliasesResponse.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/collection/GetAliasesRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/collection/ReloadRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/CreateRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/ReloadRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/RenameRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/RequestStatusRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/RequestStatusResponse.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/Status.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/StatusRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/StatusResponse.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/SwapRequest.java create mode 100644 solr-api/src/main/java/com/inteligr8/solr/model/core/UnloadRequest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e59065e --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Maven +target +pom.xml.versionsBackup + +# Eclipse +.project +.classpath +.settings +.vscode + +# IDEA +/.idea/ diff --git a/asie-api/.gitignore b/asie-api/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/asie-api/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/asie-api/pom.xml b/asie-api/pom.xml new file mode 100644 index 0000000..48e3fe7 --- /dev/null +++ b/asie-api/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + + com.inteligr8.alfresco + asie-api + 1.0-SNAPSHOT-asie2 + jar + + ASIE JAX-RS API + Alfresco Search & Insight Engine JAX-RS API + + + + com.inteligr8 + solr-api + 1.0-SNAPSHOT-solr6 + + + org.alfresco + alfresco-repository + 14.153 + + + + + org.junit.jupiter + junit-jupiter-api + 5.11.2 + test + + + + + UTF-8 + 11 + 11 + 11 + + + + + + + + maven-compiler-plugin + 3.13.0 + + + + maven-site-plugin + 3.12.1 + + + + maven-dependency-plugin + 3.7.1 + + + + + + + + alfresco-public + https://artifacts.alfresco.com/nexus/repository/public/ + + + diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/api/CoreAdminApi.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/api/CoreAdminApi.java new file mode 100644 index 0000000..0251fe9 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/api/CoreAdminApi.java @@ -0,0 +1,91 @@ +package com.inteligr8.alfresco.asie.api; + +import com.inteligr8.alfresco.asie.model.ActionResponse; +import com.inteligr8.alfresco.asie.model.EmptyResponse; +import com.inteligr8.alfresco.asie.model.core.CheckRequest; +import com.inteligr8.alfresco.asie.model.core.DisableIndexingRequest; +import com.inteligr8.alfresco.asie.model.core.EnableIndexingRequest; +import com.inteligr8.alfresco.asie.model.core.FixRequest; +import com.inteligr8.alfresco.asie.model.core.FixResponseAction; +import com.inteligr8.alfresco.asie.model.core.IndexingStatusAction; +import com.inteligr8.alfresco.asie.model.core.NewCoreRequest; +import com.inteligr8.alfresco.asie.model.core.NewDefaultIndexRequest; +import com.inteligr8.alfresco.asie.model.core.PurgeRequest; +import com.inteligr8.alfresco.asie.model.core.ReindexRequest; +import com.inteligr8.alfresco.asie.model.core.ReportRequest; +import com.inteligr8.alfresco.asie.model.core.ReportResponse; +import com.inteligr8.alfresco.asie.model.core.RetryRequest; +import com.inteligr8.alfresco.asie.model.core.RetryResponseAction; +import com.inteligr8.alfresco.asie.model.core.SummaryRequest; +import com.inteligr8.alfresco.asie.model.core.SummaryResponse; +import com.inteligr8.alfresco.asie.model.core.UpdateCoreRequest; +import com.inteligr8.alfresco.asie.model.core.UpdateLog4jRequest; +import com.inteligr8.alfresco.asie.model.core.UpdateSharedRequest; +import com.inteligr8.solr.model.ResponseAction; + +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("admin/cores") +public interface CoreAdminApi extends com.inteligr8.solr.api.CoreAdminApi { + + @GET + @Produces(MediaType.APPLICATION_JSON) + EmptyResponse newCore(@BeanParam NewCoreRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + EmptyResponse newDefaultIndex(@BeanParam NewDefaultIndexRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse updateCore(@BeanParam UpdateCoreRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse check(@BeanParam CheckRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse updateShared(@BeanParam UpdateSharedRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse updateLog4j(@BeanParam UpdateLog4jRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse purge(@BeanParam PurgeRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse reindex(@BeanParam ReindexRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse retry(@BeanParam RetryRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse fix(@BeanParam FixRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse enableIndexing(@BeanParam EnableIndexingRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse disableIndexing(@BeanParam DisableIndexingRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + ReportResponse getReport(@BeanParam ReportRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + SummaryResponse getSummary(@BeanParam SummaryRequest request); + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/ActionResponse.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/ActionResponse.java new file mode 100644 index 0000000..ca6b586 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/ActionResponse.java @@ -0,0 +1,22 @@ +package com.inteligr8.alfresco.asie.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.inteligr8.solr.model.ResponseAction; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ActionResponse extends BaseResponse { + + @JsonProperty(access = Access.READ_ONLY) + private T action; + + public T getAction() { + return action; + } + + protected void setAction(T action) { + this.action = action; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/BaseResponse.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/BaseResponse.java new file mode 100644 index 0000000..9ebcb05 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/BaseResponse.java @@ -0,0 +1,41 @@ +package com.inteligr8.alfresco.asie.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +public class BaseResponse extends com.inteligr8.solr.model.BaseResponse { + + @JsonProperty(value = "STATUS", access = Access.READ_ONLY) + private String reason; + + @JsonProperty(value = "exception", access = Access.READ_ONLY) + private String exception; + + @JsonProperty(value = "msg", access = Access.READ_ONLY) + private String message; + + public String getReason() { + return reason; + } + + protected void setReason(String reason) { + this.reason = reason; + } + + public String getException() { + return exception; + } + + protected void setException(String exception) { + this.exception = exception; + } + + public String getMessage() { + return message; + } + + protected void setMessage(String message) { + this.message = message; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/EmptyResponse.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/EmptyResponse.java new file mode 100644 index 0000000..75b7a77 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/EmptyResponse.java @@ -0,0 +1,8 @@ +package com.inteligr8.alfresco.asie.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class EmptyResponse extends BaseResponse { + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/CheckRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/CheckRequest.java new file mode 100644 index 0000000..c81acec --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/CheckRequest.java @@ -0,0 +1,43 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class CheckRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "check"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("coreName") + @Nonnull + private String core; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public CheckRequest withCore(String core) { + this.core = core; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/DisableIndexingRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/DisableIndexingRequest.java new file mode 100644 index 0000000..a0d12c1 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/DisableIndexingRequest.java @@ -0,0 +1,42 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class DisableIndexingRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "disable-indexing"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + private String core; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public DisableIndexingRequest withCore(String core) { + this.core = core; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/EnableIndexingRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/EnableIndexingRequest.java new file mode 100644 index 0000000..1834587 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/EnableIndexingRequest.java @@ -0,0 +1,42 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class EnableIndexingRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "enable-indexing"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + private String core; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public EnableIndexingRequest withCore(String core) { + this.core = core; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixRequest.java new file mode 100644 index 0000000..37ddce7 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixRequest.java @@ -0,0 +1,90 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class FixRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "fix"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + private String core; + + @QueryParam("dryRun") + private Boolean dryRun; + + @QueryParam("fromTxCommitTime") + private Long fromTransactionCommitTime; + + @QueryParam("toTxCommitTime") + private Long toTransactionCommitTime; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public FixRequest withCore(String core) { + this.core = core; + return this; + } + + public Boolean getDryRun() { + return dryRun; + } + + public void setDryRun(Boolean dryRun) { + this.dryRun = dryRun; + } + + public FixRequest dryRun(boolean dryRun) { + this.dryRun = dryRun; + return this; + } + + public Long getFromTransactionCommitTime() { + return fromTransactionCommitTime; + } + + public void setFromTransactionCommitTime(Long fromTransactionCommitTime) { + this.fromTransactionCommitTime = fromTransactionCommitTime; + } + + public FixRequest fromTransactionCommitTime(Long fromTransactionCommitTime) { + this.fromTransactionCommitTime = fromTransactionCommitTime; + return this; + } + + public Long getToTransactionCommitTime() { + return toTransactionCommitTime; + } + + public void setToTransactionCommitTime(Long toTransactionCommitTime) { + this.toTransactionCommitTime = toTransactionCommitTime; + } + + public FixRequest toTransactionCommitTime(Long toTransactionCommitTime) { + this.toTransactionCommitTime = toTransactionCommitTime; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixResponseAction.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixResponseAction.java new file mode 100644 index 0000000..b435cd8 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixResponseAction.java @@ -0,0 +1,34 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.inteligr8.solr.model.ResponseAction; +import com.inteligr8.solr.model.TransactionResponseStatus; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class FixResponseAction extends ResponseAction { + + @JsonProperty(value = "txToReindex", access = Access.READ_ONLY) + private TransactionResponseStatus transactionStatus; + + @JsonProperty(value = "aclChangeSetToReindex", access = Access.READ_ONLY) + private TransactionResponseStatus aclStatus; + + public TransactionResponseStatus getTransactionStatus() { + return transactionStatus; + } + + protected void setTransactionStatus(TransactionResponseStatus transactionStatus) { + this.transactionStatus = transactionStatus; + } + + public TransactionResponseStatus getAclStatus() { + return aclStatus; + } + + protected void setAclStatus(TransactionResponseStatus aclStatus) { + this.aclStatus = aclStatus; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusAction.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusAction.java new file mode 100644 index 0000000..270bbd8 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusAction.java @@ -0,0 +1,25 @@ +package com.inteligr8.alfresco.asie.model.core; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.inteligr8.solr.model.ResponseAction; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class IndexingStatusAction extends ResponseAction { + + private Map cores; + + @JsonAnyGetter + public Map getCores() { + return cores; + } + + @JsonAnySetter + public void setCores(Map cores) { + this.cores = cores; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusMetadata.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusMetadata.java new file mode 100644 index 0000000..e8fe4fd --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusMetadata.java @@ -0,0 +1,56 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.inteligr8.solr.model.ResponseAction; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class IndexingStatusMetadata extends ResponseAction { + + @JsonProperty(value = "ACL", access = Access.READ_ONLY) + private Boolean aclIndexed; + + @JsonProperty(value = "CONTENT", access = Access.READ_ONLY) + private Boolean contentIndexed; + + @JsonProperty(value = "METADATA", access = Access.READ_ONLY) + private Boolean metadataIndexed; + + public boolean isAclIndexed() { + return Boolean.TRUE.equals(this.aclIndexed); + } + + public Boolean getAclIndexed() { + return aclIndexed; + } + + protected void setAclIndexed(Boolean aclIndexed) { + this.aclIndexed = aclIndexed; + } + + public boolean isContentIndexed() { + return Boolean.TRUE.equals(this.contentIndexed); + } + + public Boolean getContentIndexed() { + return contentIndexed; + } + + protected void setContentIndexed(Boolean contentIndexed) { + this.contentIndexed = contentIndexed; + } + + public boolean isMetadataIndexed() { + return Boolean.TRUE.equals(this.metadataIndexed); + } + + public Boolean getMetadataIndexed() { + return metadataIndexed; + } + + protected void setMetadataIndexed(Boolean metadataIndexed) { + this.metadataIndexed = metadataIndexed; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/NewCoreRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/NewCoreRequest.java new file mode 100644 index 0000000..0b1b99b --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/NewCoreRequest.java @@ -0,0 +1,161 @@ +package com.inteligr8.alfresco.asie.model.core; + +import java.util.Collection; + +import org.alfresco.service.cmr.repository.StoreRef; +import org.apache.commons.lang3.StringUtils; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class NewCoreRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "newCore"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("coreName") + @Nonnull + private String core; + + @QueryParam("storeRef") + @Nonnull + private StoreRef storeRef; + + @QueryParam("shardIds") + private String shardIds; + + @QueryParam("numShards") + private Integer shardCount; + + @QueryParam("template") + private String template; + + @QueryParam("replicationFactor") + private Integer replicationFactor; + + @QueryParam("nodeInstance") + private Integer nodeId; + + @QueryParam("numNodes") + private Integer nodeCount; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public NewCoreRequest withCore(String core) { + this.core = core; + return this; + } + + public StoreRef getStoreRef() { + return storeRef; + } + + public void setStoreRef(StoreRef storeRef) { + this.storeRef = storeRef; + } + + public NewCoreRequest withStoreRef(StoreRef storeRef) { + this.storeRef = storeRef; + return this; + } + + public String getShardIds() { + return shardIds; + } + + public void setShardIds(String shardIds) { + this.shardIds = shardIds; + } + + public NewCoreRequest withShardIds(Collection shardIds) { + this.shardIds = StringUtils.join(shardIds, ","); + return this; + } + + public Integer getShardCount() { + return shardCount; + } + + public void setShardCount(Integer shardCount) { + this.shardCount = shardCount; + } + + public NewCoreRequest withShardCount(Integer shardCount) { + this.shardCount = shardCount; + return this; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + + public NewCoreRequest withTemplate(String template) { + this.template = template; + return this; + } + + public Integer getReplicationFactor() { + return replicationFactor; + } + + public void setReplicationFactor(Integer replicationFactor) { + this.replicationFactor = replicationFactor; + } + + public NewCoreRequest withReplicationFactor(Integer replicationFactor) { + this.replicationFactor = replicationFactor; + return this; + } + + public Integer getNodeId() { + return nodeId; + } + + public void setNodeId(Integer nodeId) { + this.nodeId = nodeId; + } + + public NewCoreRequest withNodeId(Integer nodeId) { + this.nodeId = nodeId; + return this; + } + + public Integer getNodeCount() { + return nodeCount; + } + + public void setNodeCount(Integer nodeCount) { + this.nodeCount = nodeCount; + } + + public NewCoreRequest withNodeCount(Integer nodeCount) { + this.nodeCount = nodeCount; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/NewDefaultIndexRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/NewDefaultIndexRequest.java new file mode 100644 index 0000000..e401c9d --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/NewDefaultIndexRequest.java @@ -0,0 +1,78 @@ +package com.inteligr8.alfresco.asie.model.core; + +import org.alfresco.service.cmr.repository.StoreRef; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class NewDefaultIndexRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "newDefaultIndex"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + public String action = ACTION; + + @QueryParam("coreName") + @Nonnull + public String core; + + @QueryParam("storeRef") + @Nonnull + public StoreRef storeRef; + + @QueryParam("template") + public String template; + + public String getAction() { + return action; + } + + protected void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public NewDefaultIndexRequest withCore(String core) { + this.core = core; + return this; + } + + public StoreRef getStoreRef() { + return storeRef; + } + + public void setStoreRef(StoreRef storeRef) { + this.storeRef = storeRef; + } + + public NewDefaultIndexRequest withStoreRef(StoreRef storeRef) { + this.storeRef = storeRef; + return this; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + + public NewDefaultIndexRequest withTemplate(String template) { + this.template = template; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/PurgeRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/PurgeRequest.java new file mode 100644 index 0000000..42662e5 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/PurgeRequest.java @@ -0,0 +1,106 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class PurgeRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "purge"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + private String core; + + @QueryParam("txid") + private Integer transactionId; + + @QueryParam("acltxid") + private Integer aclTransactionId; + + @QueryParam("nodeId") + private Integer nodeId; + + @QueryParam("aclid") + private Integer aclId; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public PurgeRequest withCore(String core) { + this.core = core; + return this; + } + + public Integer getTransactionId() { + return transactionId; + } + + public void setTransactionId(Integer transactionId) { + this.transactionId = transactionId; + } + + public PurgeRequest withTransactionId(Integer transactionId) { + this.transactionId = transactionId; + return this; + } + + public Integer getAclTransactionId() { + return aclTransactionId; + } + + public void setAclTransactionId(Integer aclTransactionId) { + this.aclTransactionId = aclTransactionId; + } + + public PurgeRequest withAclTransactionId(Integer aclTransactionId) { + this.aclTransactionId = aclTransactionId; + return this; + } + + public Integer getNodeId() { + return nodeId; + } + + public void setNodeId(Integer nodeId) { + this.nodeId = nodeId; + } + + public PurgeRequest withNodeId(Integer nodeId) { + this.nodeId = nodeId; + return this; + } + + public Integer getAclId() { + return aclId; + } + + public void setAclId(Integer aclId) { + this.aclId = aclId; + } + + public PurgeRequest withAclId(Integer aclId) { + this.aclId = aclId; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReindexRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReindexRequest.java new file mode 100644 index 0000000..15500c0 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReindexRequest.java @@ -0,0 +1,122 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class ReindexRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "reindex"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + private String core; + + @QueryParam("txid") + private Integer transactionId; + + @QueryParam("acltxid") + private Integer aclTransactionId; + + @QueryParam("nodeId") + private Integer nodeId; + + @QueryParam("aclid") + private Integer aclId; + + @QueryParam("query") + private String query; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public ReindexRequest withCore(String core) { + this.core = core; + return this; + } + + public Integer getTransactionId() { + return transactionId; + } + + public void setTransactionId(Integer transactionId) { + this.transactionId = transactionId; + } + + public ReindexRequest withTransactionId(Integer transactionId) { + this.transactionId = transactionId; + return this; + } + + public Integer getAclTransactionId() { + return aclTransactionId; + } + + public void setAclTransactionId(Integer aclTransactionId) { + this.aclTransactionId = aclTransactionId; + } + + public ReindexRequest withAclTransactionId(Integer aclTransactionId) { + this.aclTransactionId = aclTransactionId; + return this; + } + + public Integer getNodeId() { + return nodeId; + } + + public void setNodeId(Integer nodeId) { + this.nodeId = nodeId; + } + + public ReindexRequest withNodeId(Integer nodeId) { + this.nodeId = nodeId; + return this; + } + + public Integer getAclId() { + return aclId; + } + + public void setAclId(Integer aclId) { + this.aclId = aclId; + } + + public ReindexRequest withAclId(Integer aclId) { + this.aclId = aclId; + return this; + } + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public ReindexRequest withQuery(String query) { + this.query = query; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/Report.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/Report.java new file mode 100644 index 0000000..78646ec --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/Report.java @@ -0,0 +1,24 @@ +package com.inteligr8.alfresco.asie.model.core; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Report { + + private Map> report; + + @JsonAnyGetter + public Map> getReport() { + return report; + } + + @JsonAnySetter + protected void setReport(Map> report) { + this.report = report; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReportRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReportRequest.java new file mode 100644 index 0000000..c325299 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReportRequest.java @@ -0,0 +1,106 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class ReportRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "report"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + private String core; + + @QueryParam("fromTime") + private Long fromTime; + + @QueryParam("toTime") + private Long toTime; + + @QueryParam("fromTx") + private Integer fromTransactionId; + + @QueryParam("toTx") + private Integer toTransactionId; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public ReportRequest withCore(String core) { + this.core = core; + return this; + } + + public Long getFromTime() { + return fromTime; + } + + public void setFromTime(Long fromTime) { + this.fromTime = fromTime; + } + + public ReportRequest fromTime(Long fromTime) { + this.fromTime = fromTime; + return this; + } + + public Long getToTime() { + return toTime; + } + + public void setToTime(Long toTime) { + this.toTime = toTime; + } + + public ReportRequest toTime(Long toTime) { + this.toTime = toTime; + return this; + } + + public Integer getFromTransactionId() { + return fromTransactionId; + } + + public void setFromTransactionId(Integer fromTransactionId) { + this.fromTransactionId = fromTransactionId; + } + + public ReportRequest fromTransactionId(Integer fromTransactionId) { + this.fromTransactionId = fromTransactionId; + return this; + } + + public Integer getToTransactionId() { + return toTransactionId; + } + + public void setToTransactionId(Integer toTransactionId) { + this.toTransactionId = toTransactionId; + } + + public ReportRequest toTransactionId(Integer toTransactionId) { + this.toTransactionId = toTransactionId; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReportResponse.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReportResponse.java new file mode 100644 index 0000000..4556f0a --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/ReportResponse.java @@ -0,0 +1,22 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.inteligr8.alfresco.asie.model.BaseResponse; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ReportResponse extends BaseResponse { + + @JsonProperty(access = Access.READ_ONLY) + private Report report; + + public Report getReport() { + return report; + } + + protected void setReport(Report report) { + this.report = report; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryRequest.java new file mode 100644 index 0000000..6233bbb --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryRequest.java @@ -0,0 +1,42 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class RetryRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "retry"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + private String core; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public RetryRequest withCore(String core) { + this.core = core; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryResponseAction.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryResponseAction.java new file mode 100644 index 0000000..cdf4eed --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryResponseAction.java @@ -0,0 +1,22 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.inteligr8.solr.model.ResponseAction; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class RetryResponseAction extends ResponseAction { + + @JsonProperty(value = "alfresco", access = Access.READ_ONLY) + private int[] nodeIds; + + public int[] getNodeIds() { + return nodeIds; + } + + public void setNodeIds(int[] nodeIds) { + this.nodeIds = nodeIds; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/Summary.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/Summary.java new file mode 100644 index 0000000..a6b4e2f --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/Summary.java @@ -0,0 +1,24 @@ +package com.inteligr8.alfresco.asie.model.core; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Summary { + + private Map summary; + + @JsonAnyGetter + public Map getSummary() { + return summary; + } + + @JsonAnySetter + public void setSummary(Map summary) { + this.summary = summary; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/SummaryRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/SummaryRequest.java new file mode 100644 index 0000000..b49193c --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/SummaryRequest.java @@ -0,0 +1,107 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class SummaryRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "summary"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + @Nonnull + private String core; + + @QueryParam("detail") + private Boolean includeDetails; + + @QueryParam("hist") + private Boolean includeHistogram; + + @QueryParam("values") + private Boolean includeSomeValues; + + @QueryParam("reset") + private Boolean resetStats; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public SummaryRequest withCore(String core) { + this.core = core; + return this; + } + + public Boolean getIncludeDetails() { + return includeDetails; + } + + public void setIncludeDetails(Boolean includeDetails) { + this.includeDetails = includeDetails; + } + + public SummaryRequest includeDetails(boolean includeDetails) { + this.includeDetails = includeDetails; + return this; + } + + public Boolean getIncludeHistogram() { + return includeHistogram; + } + + public void setIncludeHistogram(Boolean includeHistogram) { + this.includeHistogram = includeHistogram; + } + + public SummaryRequest includeHistogram(boolean includeHistogram) { + this.includeHistogram = includeHistogram; + return this; + } + + public Boolean getIncludeSomeValues() { + return includeSomeValues; + } + + public void setIncludeSomeValues(Boolean includeSomeValues) { + this.includeSomeValues = includeSomeValues; + } + + public SummaryRequest includeSomeValues(boolean includeSomeValues) { + this.includeSomeValues = includeSomeValues; + return this; + } + + public Boolean getResetStats() { + return resetStats; + } + + public void setResetStats(Boolean resetStats) { + this.resetStats = resetStats; + } + + public SummaryRequest resetStats(boolean resetStats) { + this.resetStats = resetStats; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/SummaryResponse.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/SummaryResponse.java new file mode 100644 index 0000000..8aa7675 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/SummaryResponse.java @@ -0,0 +1,22 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.inteligr8.alfresco.asie.model.BaseResponse; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class SummaryResponse extends BaseResponse { + + @JsonProperty(value = "Summary", access = Access.READ_ONLY) + private Summary summary; + + public Summary getSummary() { + return summary; + } + + public void setSummary(Summary summary) { + this.summary = summary; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateCoreRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateCoreRequest.java new file mode 100644 index 0000000..ef16e02 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateCoreRequest.java @@ -0,0 +1,43 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class UpdateCoreRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "updateCore"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("coreName") + @Nonnull + private String core; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public UpdateCoreRequest withCore(String core) { + this.core = core; + return this; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateLog4jRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateLog4jRequest.java new file mode 100644 index 0000000..334de99 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateLog4jRequest.java @@ -0,0 +1,26 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class UpdateLog4jRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "log4j"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + +} diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateSharedRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateSharedRequest.java new file mode 100644 index 0000000..2018204 --- /dev/null +++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/UpdateSharedRequest.java @@ -0,0 +1,26 @@ +package com.inteligr8.alfresco.asie.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class UpdateSharedRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "updateShared"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + +} diff --git a/module/.gitignore b/module/.gitignore new file mode 100644 index 0000000..e59065e --- /dev/null +++ b/module/.gitignore @@ -0,0 +1,12 @@ +# Maven +target +pom.xml.versionsBackup + +# Eclipse +.project +.classpath +.settings +.vscode + +# IDEA +/.idea/ diff --git a/module/README.md b/module/README.md new file mode 100644 index 0000000..1aa10d1 --- /dev/null +++ b/module/README.md @@ -0,0 +1 @@ +# ASIE Platform Module Library diff --git a/module/pom.xml b/module/pom.xml new file mode 100644 index 0000000..e4b9b0a --- /dev/null +++ b/module/pom.xml @@ -0,0 +1,182 @@ + + 4.0.0 + + + com.inteligr8.alfresco + asie-platform-module-parent + 1.0-SNAPSHOT + ../ + + + asie-platform-module + jar + + ASIE Platform Module + + + 5.2.0 + 23.3.2 + + + + + + org.alfresco + acs-packaging + ${alfresco.platform.version} + pom + import + + + + + org.springframework + spring-context + provided + + + jakarta.annotation + jakarta.annotation-api + provided + + + jakarta.ws.rs + jakarta.ws.rs-api + provided + + + com.fasterxml.jackson.core + jackson-databind + provided + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + provided + + + com.fasterxml.jackson.jakarta.rs + jackson-jakarta-rs-json-provider + provided + + + org.apache.commons + commons-lang3 + provided + + + org.apache.cxf + cxf-rt-rs-client + provided + + + org.slf4j + slf4j-api + provided + + + + + + + com.inteligr8.alfresco + asie-api + 1.0-SNAPSHOT-asie2 + + + com.inteligr8 + common-rest-client + 3.0.1-cxf + + + + + org.alfresco + alfresco-enterprise-repository + provided + + + org.alfresco + alfresco-elasticsearch-shared + + + + + org.alfresco + alfresco-repository + provided + + + + xpp3 + xpp3 + + + + + + + com.inteligr8.alfresco + cxf-jaxrs-platform-module + 1.2.0-acs-v23.2 + provided + + + + + junit + junit + test + + + org.mockito + mockito-core + test + + + + + + + + + maven-compiler-plugin + 3.13.0 + + + + maven-site-plugin + 3.12.1 + + + + maven-dependency-plugin + 3.7.1 + + + + + + io.repaint.maven + tiles-maven-plugin + 2.40 + true + + + + com.inteligr8.ootbee:beedk-acs-platform-module-tile:[1.1.6,2.0.0) + + + + + + + + + alfresco-private + https://artifacts.alfresco.com/nexus/content/groups/private + + + diff --git a/module/rad.ps1 b/module/rad.ps1 new file mode 100644 index 0000000..61bcb2f --- /dev/null +++ b/module/rad.ps1 @@ -0,0 +1,74 @@ + +function discoverArtifactId { + $script:ARTIFACT_ID=(mvn -q -Dexpression=project"."artifactId -DforceStdout help:evaluate) +} + +function rebuild { + echo "Rebuilding project ..." + mvn process-classes +} + +function start_ { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad process-classes +} + +function start_log { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad "-Ddocker.showLogs" process-classes +} + +function stop_ { + discoverArtifactId + echo "Stopping Docker containers that supported rapid application development ..." + docker container ls --filter name=${ARTIFACT_ID}-* + echo "Stopping containers ..." + docker container stop (docker container ls -q --filter name=${ARTIFACT_ID}-*) + echo "Removing containers ..." + docker container rm (docker container ls -aq --filter name=${ARTIFACT_ID}-*) +} + +function tail_logs { + param ( + $container + ) + + discoverArtifactId + docker container logs -f (docker container ls -q --filter name=${ARTIFACT_ID}-${container}) +} + +function list { + discoverArtifactId + docker container ls --filter name=${ARTIFACT_ID}-* +} + +switch ($args[0]) { + "start" { + start_ + } + "start_log" { + start_log + } + "stop" { + stop_ + } + "restart" { + stop_ + start_ + } + "rebuild" { + rebuild + } + "tail" { + tail_logs $args[1] + } + "containers" { + list + } + default { + echo "Usage: .\rad.ps1 [ start | start_log | stop | restart | rebuild | tail {container} | containers ]" + } +} + +echo "Completed!" + diff --git a/module/rad.sh b/module/rad.sh new file mode 100644 index 0000000..7cb0a80 --- /dev/null +++ b/module/rad.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +discoverArtifactId() { + ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate` +} + +rebuild() { + echo "Rebuilding project ..." + mvn process-classes +} + +start() { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad process-classes +} + +start_log() { + echo "Rebuilding project and starting Docker containers to support rapid application development ..." + mvn -Drad -Ddocker.showLogs process-classes +} + +stop() { + discoverArtifactId + echo "Stopping Docker containers that supported rapid application development ..." + docker container ls --filter name=${ARTIFACT_ID}-* + echo "Stopping containers ..." + docker container stop `docker container ls -q --filter name=${ARTIFACT_ID}-*` + echo "Removing containers ..." + docker container rm `docker container ls -aq --filter name=${ARTIFACT_ID}-*` +} + +tail_logs() { + discoverArtifactId + docker container logs -f `docker container ls -q --filter name=${ARTIFACT_ID}-$1` +} + +list() { + discoverArtifactId + docker container ls --filter name=${ARTIFACT_ID}-* +} + +case "$1" in + start) + start + ;; + start_log) + start_log + ;; + stop) + stop + ;; + restart) + stop + start + ;; + rebuild) + rebuild + ;; + tail) + tail_logs $2 + ;; + containers) + list + ;; + *) + echo "Usage: ./rad.sh [ start | start_log | stop | restart | rebuild | tail {container} | containers ]" + exit 1 +esac + +echo "Completed!" + diff --git a/module/research/acs-enterprise-shard-attributes.json b/module/research/acs-enterprise-shard-attributes.json new file mode 100644 index 0000000..ad7db13 --- /dev/null +++ b/module/research/acs-enterprise-shard-attributes.json @@ -0,0 +1,114 @@ +{ + ".SHARD_STATE": { + "d2fda078-887c-46a3-bda0-78887c06a305": { + "type": "ShardState", + "shardInstance": { + "type": "ShardInstance", + "shard": { + "type": "Shard", + "floc": { + "type": "Floc", + "storeRefs": [ + "workspace://SpacesStore" + ], + "numberOfShards": 32, + "shardMethod": "PROPERTY", + "template": "rerank", + "hasContent": true, + "propertyBag": {} + }, + "instance": 29 + }, + "baseUrl": "/solr/alfresco-year-29", + "port": 8983, + "hostName": "localhost" + }, + "isMaster": true, + "lastUpdated": 1729099258001, + "lastIndexedChangeSetId": 1189, + "lastIndexedTxCommitTime": 1729098871726, + "lastIndexedTxId": 524708, + "lastIndexedChangeSetCommitTime": 1729010974050, + "propertyBag": { + "shard.key": "cm:created", + "shard.regex": "^(\\d{4})", + "coreName": "alfresco-year-29" + } + }, + "8cd7436d-787a-478a-9743-6d787a778a1f": { + "type": "ShardState", + "shardInstance": { + "type": "ShardInstance", + "shard": { + "type": "Shard", + "floc": { + "type": "Floc", + "storeRefs": [ + "workspace://SpacesStore" + ], + "numberOfShards": 32, + "shardMethod": "PROPERTY", + "template": "rerank", + "hasContent": true, + "propertyBag": {} + }, + "instance": 0 + }, + "baseUrl": "/solr/alfresco-year-0", + "port": 8983, + "hostName": "localhost" + }, + "isMaster": true, + "lastUpdated": 1729099258001, + "lastIndexedChangeSetId": 1189, + "lastIndexedTxCommitTime": 1729098871726, + "lastIndexedTxId": 524708, + "lastIndexedChangeSetCommitTime": 1729010974050, + "propertyBag": { + "shard.key": "cm:created", + "shard.regex": "^(\\d{4})", + "coreName": "alfresco-year-29" + } + }, + "ae5e918d-c602-4bb0-9e91-8dc6028bb088": { + "type": "ShardState", + "shardInstance": { + "type": "ShardInstance", + "shard": { + "type": "Shard", + "floc": { + "type": "Floc", + "storeRefs": [ + "workspace://SpacesStore" + ], + "numberOfShards": 32, + "shardMethod": "PROPERTY", + "template": "rerank", + "hasContent": true, + "propertyBag": {} + }, + "instance": 1 + }, + "baseUrl": "/solr/alfresco-year-1", + "port": 8983, + "hostName": "localhost" + }, + "isMaster": true, + "lastUpdated": 1729099258001, + "lastIndexedChangeSetId": 1189, + "lastIndexedTxCommitTime": 1729098871726, + "lastIndexedTxId": 524708, + "lastIndexedChangeSetCommitTime": 1729010974050, + "propertyBag": { + "shard.key": "cm:created", + "shard.regex": "^(\\d{4})", + "coreName": "alfresco-year-29" + } + } + }, + ".SHARD_SUBSCRIPTION": { + "8cd7436d-787a-478a-9743-6d787a778a1f": "Shard #0 subscribed on Wed Oct 16 17:20:07 UTC 2024 (timestamp = 1729099207319)", + "ae5e918d-c602-4bb0-9e91-8dc6028bb088": "Shard #1 subscribed on Wed Oct 16 17:20:08 UTC 2024 (timestamp = 1729099208026)" + } +} + \ No newline at end of file diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/Constants.java b/module/src/main/java/com/inteligr8/alfresco/asie/Constants.java new file mode 100644 index 0000000..f98112e --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/Constants.java @@ -0,0 +1,13 @@ +package com.inteligr8.alfresco.asie; + +public interface Constants { + + static final String QUALIFIER_ASIE = "asie"; + static final String BEAN_SOLR_ADMIN_CLIENT = "search.SolrAdminClient"; + static final String BEAN_SHARD_REGISTRY = "search.ShardRegistry"; + static final String ATTR_SHARD_STATE = ".SHARD_STATE"; + static final String ATTR_SHARD_SUBSCRIPTION = ".SHARD_SUBSCRIPTION"; + static final String ATTR_ASIE = "inteligr8.asie"; + static final String ATTR_UNLOADED_NODE_CORES = "unloadedNode.cores"; + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardEnumeratedHashTable.java b/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardEnumeratedHashTable.java new file mode 100644 index 0000000..b559da1 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardEnumeratedHashTable.java @@ -0,0 +1,57 @@ +package com.inteligr8.alfresco.asie.compute; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.service.SolrShardHashService; + +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class SolrShardEnumeratedHashTable extends SolrShardHashTable { + + /** + * For Spring use only + */ + public SolrShardEnumeratedHashTable(int shards) { + super(shards); + } + + /** + * For non-Spring use + */ + public SolrShardEnumeratedHashTable(int shards, SolrShardHashService sshs) { + super(shards, sshs); + } + + public SolrShardEnumeratedHashTable build(Collection objs) { + for (Object obj : objs) { + int hashed = this.hashForward(obj); + + Set ns = this.reverseHash.get(hashed); + if (ns == null) + this.reverseHash.put(hashed, ns = new HashSet<>()); + ns.add(obj); + } + + return this; + } + + public SolrShardEnumeratedHashTable build(Object... objs) { + for (Object obj : objs) { + int hashed = this.hashForward(obj); + + Set ns = this.reverseHash.get(hashed); + if (ns == null) + this.reverseHash.put(hashed, ns = new HashSet<>()); + ns.add(obj); + } + + return this; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardHashTable.java b/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardHashTable.java new file mode 100644 index 0000000..743ea0f --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardHashTable.java @@ -0,0 +1,43 @@ +package com.inteligr8.alfresco.asie.compute; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.inteligr8.alfresco.asie.service.SolrShardHashService; + +public class SolrShardHashTable { + + private final int shards; + protected Map> reverseHash; + + @Autowired + private SolrShardHashService sshs; + + /** + * For Spring use only + */ + public SolrShardHashTable(int shards) { + this.shards = shards; + this.reverseHash = new HashMap<>(); + } + + /** + * For non-Spring use + */ + public SolrShardHashTable(int shards, SolrShardHashService sshs) { + this(shards); + this.sshs = sshs; + } + + public int hashForward(Object obj) { + return this.sshs.hash(obj, this.shards); + } + + public Set hashReverse(int hash) { + return this.reverseHash.get(hash); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardNumericHashTable.java b/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardNumericHashTable.java new file mode 100644 index 0000000..bfaeeba --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/compute/SolrShardNumericHashTable.java @@ -0,0 +1,43 @@ +package com.inteligr8.alfresco.asie.compute; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.service.SolrShardHashService; + +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +public class SolrShardNumericHashTable extends SolrShardHashTable { + + /** + * For Spring use only + */ + public SolrShardNumericHashTable(int shards) { + super(shards); + } + + /** + * For non-Spring use + */ + public SolrShardNumericHashTable(int shards, SolrShardHashService sshs) { + super(shards, sshs); + } + + public SolrShardNumericHashTable build(long start, long end) { + for (long n = start; n <= end; n++) { + int hashed = this.hashForward(n); + + Set ns = this.reverseHash.get(hashed); + if (ns == null) + this.reverseHash.put(hashed, ns = new HashSet<>()); + ns.add(n); + } + + return this; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/bootstrap/ShardPurgeService.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/bootstrap/ShardPurgeService.java new file mode 100644 index 0000000..9948a51 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/bootstrap/ShardPurgeService.java @@ -0,0 +1,45 @@ +package com.inteligr8.alfresco.asie.enterprise.bootstrap; + +import org.alfresco.service.cmr.attributes.AttributeService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.Constants; + +/** + * This is a workaround to a bug with the Alfresco Enterprise Solr purge + * functionality, letting it fail due to a concurrency exception. This skips + * all the fancy stuff and just hard purges the shards. + */ +@Component +public class ShardPurgeService extends AbstractLifecycleBean { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + @Qualifier(Constants.QUALIFIER_ASIE) + private AttributeService attributeService; + + @Value("${search.solrShardRegistry.purgeOnInit}") + private boolean enabled; + + @Override + protected void onBootstrap(ApplicationEvent event) { + this.logger.info("Using workaround for Alfresco Enterprise Solr ShardRegistry purge"); + this.attributeService.removeAttributes(Constants.ATTR_SHARD_STATE); + this.attributeService.removeAttributes(Constants.ATTR_SHARD_SUBSCRIPTION); + this.attributeService.removeAttributes(Constants.ATTR_ASIE); + } + + @Override + protected void onShutdown(ApplicationEvent event) { + // nothing to do + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/NodeShardParameterSet.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/NodeShardParameterSet.java new file mode 100644 index 0000000..33a7739 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/NodeShardParameterSet.java @@ -0,0 +1,24 @@ +package com.inteligr8.alfresco.asie.enterprise.model; + +import com.inteligr8.alfresco.asie.model.NodeParameterSet; + +public class NodeShardParameterSet extends NodeParameterSet { + + private ShardSet shardSet; + private int shardId; + + public NodeShardParameterSet(String nodeHostname, int nodePort, ShardSet shardSet, int shardId) { + super(nodeHostname, nodePort); + this.shardSet = shardSet; + this.shardId = shardId; + } + + public ShardSet getShardSet() { + return shardSet; + } + + public int getShardId() { + return shardId; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/ShardParameterSet.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/ShardParameterSet.java new file mode 100644 index 0000000..1134fb8 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/ShardParameterSet.java @@ -0,0 +1,23 @@ +package com.inteligr8.alfresco.asie.enterprise.model; + +import com.inteligr8.alfresco.asie.model.RequestParameterSet; + +public class ShardParameterSet implements RequestParameterSet { + + private ShardSet set; + private int id; + + public ShardParameterSet(ShardSet set, int id) { + this.set = set; + this.id = id; + } + + public ShardSet getSet() { + return set; + } + + public int getId() { + return id; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/ShardSet.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/ShardSet.java new file mode 100644 index 0000000..25fefce --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/model/ShardSet.java @@ -0,0 +1,115 @@ +package com.inteligr8.alfresco.asie.enterprise.model; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.repo.index.shard.Floc; +import org.alfresco.repo.index.shard.ShardMethodEnum; +import org.alfresco.repo.index.shard.ShardState; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +public class ShardSet { + + /** + * Examples: + * + * MOD_ACL_ID + * ACL_ID + * DB_ID + * DB_ID_RANGE;range:0-20000 + * DATE;key:cm:created + * DATE;key:cm:created;date.grouping:3 + * PROPERTY;key:cm:created;regex:^d{4} + */ + private final Pattern shardSetPattern = Pattern.compile("([A-Z]+)(;fulltext)?(;([a-z]+):([^;]+))?(;([a-z]+):([^;]+))?"); + + private final ShardMethodEnum method; + private final boolean hasContent; + private final Map config; + private Integer hash; + + public ShardSet(Floc floc, ShardState anyShardNode) { + this.method = floc.getShardMethod(); + this.hasContent = floc.hasContent(); + this.config = (floc.getPropertyBag().isEmpty() && anyShardNode != null) ? anyShardNode.getPropertyBag() : floc.getPropertyBag(); + } + + public ShardSet(String shardSetSpec) { + Matcher matcher = this.shardSetPattern.matcher(shardSetSpec); + if (!matcher.find()) + throw new IllegalArgumentException("The shard set '" + shardSetSpec + "' is not properly formatted"); + + this.method = ShardMethodEnum.valueOf(matcher.group(1)); + this.hasContent = ";fulltext".equals(matcher.group(2)); + this.config = new HashMap<>(); + for (int g = 3; g < matcher.groupCount(); g += 3) + if (matcher.group(g) != null) + this.config.put("shard." + matcher.group(g+1), matcher.group(g+2)); + } + + public ShardMethodEnum getMethod() { + return method; + } + + public boolean hasContent() { + return hasContent; + } + + public String toSpec() { + StringBuilder spec = new StringBuilder(this.method.toString()); + if (this.hasContent) + spec.append(";fulltext"); + for (Entry c : this.config.entrySet()) { + if (!c.getKey().startsWith("shard.")) + continue; + spec.append(';').append(c.getKey().substring(6)).append(':').append(c.getValue()); + } + return spec.toString(); + } + + public Map getConfig() { + return config; + } + + public boolean isFor(ShardState shardState) { + return this.method.equals(shardState.getShardInstance().getShard().getFloc().getShardMethod()) && + this.hasContent == shardState.getShardInstance().getShard().getFloc().hasContent() && + this.isConfigurationFor(shardState.getPropertyBag()); + } + + public boolean isConfigurationFor(Map propertyBag) { + for (Entry config : this.config.entrySet()) { + if (config.getValue() == null || !config.getValue().equals(propertyBag.get(config.getKey()))) + return false; + } + + return true; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ShardSet)) + return false; + + ShardSet shardSet = (ShardSet) obj; + return this.method.equals(shardSet.method) && this.config.equals(shardSet.config); + } + + @Override + public int hashCode() { + if (this.hash == null) { + this.hash = new HashCodeBuilder().append(this.method).append(this.hasContent).append(this.config).build(); + } + + return this.hash; + } + + @Override + public String toString() { + return this.toSpec(); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieEnterpriseWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieEnterpriseWebScript.java new file mode 100644 index 0000000..298514b --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieEnterpriseWebScript.java @@ -0,0 +1,129 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.text.NumberFormat; +import java.time.LocalDate; +import java.time.Month; +import java.time.Year; +import java.time.temporal.WeekFields; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.repo.index.shard.ShardInstance; +import org.alfresco.repo.index.shard.ShardRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.http.HttpStatus; + +import com.inteligr8.alfresco.asie.Constants; +import com.inteligr8.alfresco.asie.api.CoreAdminApi; +import com.inteligr8.alfresco.asie.compute.SolrShardEnumeratedHashTable; +import com.inteligr8.alfresco.asie.compute.SolrShardHashTable; +import com.inteligr8.alfresco.asie.compute.SolrShardNumericHashTable; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardSetInfo; +import com.inteligr8.alfresco.asie.rest.AbstractAsieWebScript; +import com.inteligr8.alfresco.asie.service.SolrShardHashService; + +public abstract class AbstractAsieEnterpriseWebScript extends AbstractAsieWebScript { + + private final Pattern sampleTypePattern = Pattern.compile("([A-Za-z]+)([0-9]+)"); + + public enum SolrShardHashSampleType { + PropertyYear, + PropertyQuarter, + PropertyMonth, + PropertyWeek + } + + @Autowired + @Qualifier(Constants.BEAN_SHARD_REGISTRY) + private ShardRegistry shardRegistry; + + @Autowired + private SolrShardHashService sshs; + + protected ShardRegistry getShardRegistry() { + return shardRegistry; + } + + protected SolrShardHashTable createSampleHashTable(String sampleType) { + Matcher matcher = this.sampleTypePattern.matcher(sampleType); + if (!matcher.find()) + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'sampleType` is not properly formatted"); + + try { + SolrShardHashSampleType type = SolrShardHashSampleType.valueOf(matcher.group(1)); + int shards = Integer.parseInt(matcher.group(2)); + return this.createSampleHashTable(type, shards); + } catch (NumberFormatException nfe) { + // this should never happen, because of the regex + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'sampleType` number '" + matcher.group(2) + "' is not properly formatted"); + } catch (IllegalArgumentException iae) { + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'sampleType` '" + matcher.group(1) + "' is not supported"); + } + } + + protected SolrShardHashTable createSampleHashTable(SolrShardHashSampleType sampleType, int shards) { + int thisYear = Year.now().getValue(); + + switch (sampleType) { + case PropertyYear: + return new SolrShardNumericHashTable(shards, this.sshs).build(thisYear-40, thisYear+10); + case PropertyQuarter: + List quarters = new LinkedList<>(); + for (int year = thisYear - 15; year <= thisYear + 5; year++) + for (int quarter = 1; quarter <= 4; quarter++) + quarters.add(year + "-Q" + quarter); + return new SolrShardEnumeratedHashTable(shards, this.sshs).build(quarters); + case PropertyMonth: + NumberFormat monthFormat = NumberFormat.getInstance(); + monthFormat.setMinimumIntegerDigits(2); + + List months = new LinkedList<>(); + for (int year = thisYear - 15; year <= thisYear + 5; year++) + for (Month month : Month.values()) + months.add(year + "-" + monthFormat.format(month.getValue())); + return new SolrShardEnumeratedHashTable(shards, this.sshs).build(months); + case PropertyWeek: + List weeks = new LinkedList<>(); + for (int year = thisYear - 15; year <= thisYear + 5; year++) { + int maxWeek = LocalDate.of(year, Month.DECEMBER.getValue(), 31).get(WeekFields.SUNDAY_START.weekOfYear()); + for (int week = 1; week <= maxWeek; week++) + weeks.add(year + "-W" + week); + } + return new SolrShardEnumeratedHashTable(shards, this.sshs).build(weeks); + default: + throw new IllegalArgumentException(); + } + } + + protected void addShardHashSamples(ShardSetInfo shardSet, ShardInfo shard, SolrShardHashTable sampleHashTable) { + Set existingValues = shardSet.getShardHashSamples().get(shard.getId()); + if (existingValues == null) { + existingValues = new HashSet<>(); + shardSet.getShardHashSamples().put(shard.getId(), existingValues); + } + + Set values = sampleHashTable.hashReverse(shard.getId()); + if (values != null) + existingValues.addAll(values); + } + + protected void addShardHashSamples(ShardInfo shard, SolrShardHashTable sampleHashTable) { + this.addShardHashSamples(shard.getShardSet(), shard, sampleHashTable); + } + + protected int getLatestTxId() { + return 0; + } + + protected CoreAdminApi getApi(ShardInstance shard) { + return this.createApi(shard.getHostName(), shard.getPort()); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieNodeShardWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieNodeShardWebScript.java new file mode 100644 index 0000000..c71df64 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieNodeShardWebScript.java @@ -0,0 +1,35 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; + +import com.inteligr8.alfresco.asie.enterprise.model.ShardSet; + +public abstract class AbstractAsieNodeShardWebScript extends AbstractAsieEnterpriseWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public final void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException { + this.logger.trace("execute()"); + + String nodeEndpoint = this.getRequiredPathParameter(req, "nodeEndpoint"); + int colon = nodeEndpoint.lastIndexOf(':'); + String nodeHostname = colon < 0 ? nodeEndpoint : nodeEndpoint.substring(0, colon); + int nodePort = colon < 0 ? this.getDefaultSolrPort() : Integer.parseInt(nodeEndpoint.substring(colon+1)); + + ShardSet shardSet = this.getRequiredPathParameter(req, "shardSet", ShardSet.class); + int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class); + + this.execute(req, res, nodeHostname, nodePort, shardSet, shardId); + } + + protected abstract void execute(WebScriptRequest req, WebScriptResponse res, + String nodeHostname, int nodePort, ShardSet shardSet, int shardId) + throws IOException; + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieNodeWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieNodeWebScript.java new file mode 100644 index 0000000..a31a38c --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieNodeWebScript.java @@ -0,0 +1,52 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.util.Set; + +import org.alfresco.repo.index.shard.ShardState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; + +import com.inteligr8.alfresco.asie.enterprise.service.ShardDiscoveryService; + +public abstract class AbstractAsieNodeWebScript extends AbstractAsieEnterpriseWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private ShardDiscoveryService sds; + + @Override + public final void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException { + this.logger.trace("execute()"); + + String nodeEndpoint = this.getRequiredPathParameter(req, "nodeEndpoint"); + int colon = nodeEndpoint.lastIndexOf(':'); + String nodeHostname = colon < 0 ? nodeEndpoint : nodeEndpoint.substring(0, colon); + nodeHostname = nodeHostname.replace('_', '.'); + int nodePort = colon < 0 ? this.getDefaultSolrPort() : Integer.parseInt(nodeEndpoint.substring(colon+1)); + + this.execute(req, res, nodeHostname, nodePort); + } + + protected void execute(WebScriptRequest req, WebScriptResponse res, String nodeHostname, int nodePort) throws IOException { + this.logger.trace("execute({}, {})", nodeHostname, nodePort); + + Set shardsOnNode = this.sds.findByNode(nodeHostname, nodePort); + if (shardsOnNode == null || shardsOnNode.isEmpty()) + throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE node could not be found"); + + this.execute(req, res, shardsOnNode); + } + + protected void execute(WebScriptRequest req, WebScriptResponse res, Set registeredNodeShards) throws IOException { + this.logger.trace("execute({})", registeredNodeShards.size()); + // made to be optionally overridden + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieShardWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieShardWebScript.java new file mode 100644 index 0000000..5a04c1b --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractAsieShardWebScript.java @@ -0,0 +1,46 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.util.Set; + +import org.alfresco.repo.index.shard.ShardState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; + +import com.inteligr8.alfresco.asie.enterprise.model.ShardSet; +import com.inteligr8.alfresco.asie.enterprise.service.ShardDiscoveryService; + +public abstract class AbstractAsieShardWebScript extends AbstractAsieEnterpriseWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private ShardDiscoveryService sds; + + @Override + public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException { + this.logger.trace("execute()"); + + ShardSet shardSet = this.getRequiredPathParameter(req, "shardSet", ShardSet.class); + this.logger.debug("Parsed shard set: {}", shardSet); + int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class); + + try { + Set registeredShardNodes = this.sds.findByShard(shardSet, shardId); + if (registeredShardNodes == null || registeredShardNodes.isEmpty()) + throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE shard set or shard could not be found"); + + this.execute(req, res, registeredShardNodes); + } catch (IllegalArgumentException iae) { + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), iae.getMessage()); + } + } + + protected abstract void execute(WebScriptRequest req, WebScriptResponse res, Set registeredShardNodes) throws IOException; + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractUnregisterNodeWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractUnregisterNodeWebScript.java new file mode 100644 index 0000000..b3f37ad --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractUnregisterNodeWebScript.java @@ -0,0 +1,164 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.io.Serializable; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.alfresco.repo.index.shard.ShardState; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; + +import com.inteligr8.alfresco.asie.Constants; +import com.inteligr8.alfresco.asie.api.CoreAdminApi; +import com.inteligr8.alfresco.asie.enterprise.service.ShardBackupService; +import com.inteligr8.alfresco.asie.enterprise.service.ShardStateService; +import com.inteligr8.alfresco.asie.model.NodeParameterSet; +import com.inteligr8.alfresco.asie.model.core.DisableIndexingRequest; +import com.inteligr8.solr.model.CoreMetadata; +import com.inteligr8.solr.model.core.StatusRequest; +import com.inteligr8.solr.model.core.StatusResponse; +import com.inteligr8.solr.model.core.UnloadRequest; + +public abstract class AbstractUnregisterNodeWebScript extends AbstractAsieNodeWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + @Qualifier(Constants.QUALIFIER_ASIE) + private AttributeService attrService; + + @Autowired + private ShardBackupService sbs; + + @Autowired + private ShardStateService sss; + + protected abstract T createParameters(WebScriptRequest req, String nodeHostname, int nodePort); + + @Override + protected void execute(WebScriptRequest req, WebScriptResponse res, String nodeHostname, int nodePort) + throws IOException { + T params = this.createParameters(req, nodeHostname, nodePort); + + final Map guidCores = new HashMap<>(); + + AttributeQueryCallback callback = new AttributeQueryCallback() { + @Override + public boolean handleAttribute(Long id, Serializable value, Serializable[] keys) { + ShardState shardState = (ShardState) value; + if (!matches(params, shardState)) + return true; + + String guid = (String) keys[1]; + guidCores.put(guid, shardState); + return true; + } + }; + + this.attrService.getAttributes(callback, Constants.ATTR_SHARD_STATE); + + Serializable[] keys = new String[] { + Constants.ATTR_ASIE, + Constants.ATTR_UNLOADED_NODE_CORES, + nodeHostname + ":" + nodePort + }; + + @SuppressWarnings("unchecked") + Map cores = (Map) this.attrService.getAttribute(keys); + if (cores == null) + cores = new HashMap<>(); + try { + for (Entry guidCore : guidCores.entrySet()) { + ShardState shardNode = guidCore.getValue(); + String core = shardNode.getPropertyBag().get("coreName"); + + StatusResponse status = this.getCoreStatus(nodeHostname, nodePort, core); + if (status == null) + throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "This should never happen"); + CoreMetadata coreMetadata = status.getStatus().getCores().get(core); + if (coreMetadata == null || coreMetadata.getName() == null) { + this.logger.warn("Registered core does not actually exist on the node host; could be a DNS issue: {}:{}/solr/{}", nodeHostname, nodePort, core); + continue; + } + + this.unloadCore(nodeHostname, nodePort, core); + cores.put(core, coreMetadata.getInstancePath()); + + String guid = guidCore.getKey(); + this.removeShard(guid, shardNode); + } + } finally { + // FIXME maybe a separate tx? + this.attrService.setAttribute((Serializable) cores, keys); + } + + res.setStatus(HttpStatus.OK.value()); + } + + protected boolean matches(T params, ShardState shardState) { + if (!params.getHostname().equalsIgnoreCase(shardState.getShardInstance().getHostName())) { + InetAddress nodeAddress = params.getAddress(); + if (nodeAddress == null) + return false; + InetAddress shardNodeAddress = resolve(shardState.getShardInstance().getHostName()); + if (!nodeAddress.equals(shardNodeAddress)) + return false; + } + + if (params.getPort() != shardState.getShardInstance().getPort()) + return false; + + return true; + } + + private InetAddress resolve(String hostname) { + try { + return InetAddress.getByName(hostname); + } catch (UnknownHostException uhe) { + // suppress + return null; + } + } + + protected StatusResponse getCoreStatus(String nodeHostname, int nodePort, String core) { + this.logger.debug("Retrieving status for core {} on ASIE node: {}", core, nodeHostname); + CoreAdminApi api = this.createApi(nodeHostname, nodePort); + return api.getStatus(new StatusRequest().withCore(core)); + } + + protected void unloadCore(String nodeHostname, int nodePort, String core) { + this.logger.info("Unloading core {} on ASIE node: {}", core, nodeHostname); + CoreAdminApi api = this.createApi(nodeHostname, nodePort); + api.unload(new UnloadRequest().withCore(core)); + } + + protected void disableIndexing(String nodeHostname, int nodePort) { + this.logger.info("Disabling indexing on ASIE node: {}", nodeHostname); + CoreAdminApi api = this.createApi(nodeHostname, nodePort); + api.disableIndexing(new DisableIndexingRequest()); + } + + protected void disableCoreIndexing(String nodeHostname, int nodePort, String core) { + this.logger.info("Disabling indexing on ASIE node/core: {}/{}", nodeHostname, core); + CoreAdminApi api = this.createApi(nodeHostname, nodePort); + api.disableIndexing(new DisableIndexingRequest().withCore(core)); + } + + protected void removeShard(String guid, ShardState shardNode) { + this.sss.clear(guid); + this.sbs.forget(shardNode); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ClearRegistryWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ClearRegistryWebScript.java new file mode 100644 index 0000000..4bcd655 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ClearRegistryWebScript.java @@ -0,0 +1,32 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.extensions.webscripts.AbstractWebScript; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.enterprise.service.ShardBackupService; +import com.inteligr8.alfresco.asie.enterprise.service.ShardStateService; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.registry.delete") +public class ClearRegistryWebScript extends AbstractWebScript { + + @Autowired + private ShardBackupService sbs; + + @Autowired + private ShardStateService sss; + + @Override + public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException { + this.sss.clear(); + this.sbs.forget(); + + res.setStatus(HttpStatus.OK.value()); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetBackupNodeWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetBackupNodeWebScript.java new file mode 100644 index 0000000..6405e8f --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetBackupNodeWebScript.java @@ -0,0 +1,35 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.util.Set; + +import org.alfresco.repo.index.shard.ShardState; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.enterprise.service.ShardBackupService; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.backupNode.get") +public class GetBackupNodeWebScript extends AbstractAsieShardWebScript { + + @Autowired + private ShardBackupService sbs; + + @Override + public void execute(WebScriptRequest req, WebScriptResponse res, Set shardNodes) throws IOException { + if (shardNodes.isEmpty()) + throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE shard state could not be found"); + + String nodeId = this.sbs.fetchNodeId(shardNodes); + + res.setContentType(MediaType.APPLICATION_JSON_VALUE); + res.setContentEncoding("utf-8"); + this.getObjectMapper().writeValue(res.getWriter(), nodeId); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetLeadNodeWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetLeadNodeWebScript.java new file mode 100644 index 0000000..48733ed --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetLeadNodeWebScript.java @@ -0,0 +1,39 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.util.Set; + +import org.alfresco.repo.index.shard.ShardInstance; +import org.alfresco.repo.index.shard.ShardState; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeInfo; +import com.inteligr8.alfresco.asie.enterprise.service.ShardAnalysisService; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.leadNode.get") +public class GetLeadNodeWebScript extends AbstractAsieShardWebScript { + + @Autowired + private ShardAnalysisService sas; + + @Override + public void execute(WebScriptRequest req, WebScriptResponse res, Set shardNodesCache) throws IOException { + if (shardNodesCache.isEmpty()) + throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE shard state could not be found"); + + ShardInstance latestNode = this.sas.computeLeadShard(shardNodesCache); + if (latestNode == null) + throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE shard state could not be found"); + + res.setContentType(MediaType.APPLICATION_JSON_VALUE); + res.setContentEncoding("utf-8"); + this.getObjectMapper().writeValue(res.getWriter(), NodeInfo.determineId(latestNode)); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetNodeWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetNodeWebScript.java new file mode 100644 index 0000000..70a7c00 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetNodeWebScript.java @@ -0,0 +1,53 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Set; + +import org.alfresco.repo.index.shard.ShardInstance; +import org.alfresco.repo.index.shard.ShardState; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.compute.SolrShardHashTable; +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeShardInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardSetInfo; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.node.get") +public class GetNodeWebScript extends AbstractAsieNodeWebScript { + + @Override + protected void execute(WebScriptRequest req, WebScriptResponse res, Set registeredNodeShards) throws IOException { + ShardState anyRegisteredNodeShard = registeredNodeShards.iterator().next(); + ShardInstance registeredNode = anyRegisteredNodeShard.getShardInstance(); + int maxShards = registeredNode.getShard().getFloc().getNumberOfShards(); + + SolrShardHashSampleType sampleHashType = this.getOptionalQueryParameter(req, "sampleHashType", SolrShardHashSampleType.class); + SolrShardHashTable sampleHashTable = sampleHashType == null ? null : this.createSampleHashTable(sampleHashType, maxShards); + + NodeInfo node = new NodeShardInfo(registeredNode); + + for (ShardState registeredNodeShard : registeredNodeShards) { + ShardInfo shard = new ShardInfo(); + shard.setId(registeredNodeShard.getShardInstance().getShard().getInstance()); + shard.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(registeredNodeShard.getLastIndexedTxCommitTime()), ZoneOffset.UTC)); + shard.setTxsCompleted(registeredNodeShard.getLastIndexedTxId()); + + shard.setShardSet(new ShardSetInfo(registeredNodeShard.getShardInstance().getShard().getFloc(), registeredNodeShard)); + if (sampleHashTable != null) + this.addShardHashSamples(shard, sampleHashTable); + + node.getShards().put(shard.getId(), shard); + } + + res.setContentType("application/json"); + res.setContentEncoding("utf-8"); + this.getObjectMapper().writeValue(res.getWriter(), node); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetNodesWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetNodesWebScript.java new file mode 100644 index 0000000..72a501f --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetNodesWebScript.java @@ -0,0 +1,70 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.repo.index.shard.Floc; +import org.alfresco.repo.index.shard.Shard; +import org.alfresco.repo.index.shard.ShardState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.compute.SolrShardHashTable; +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeShardInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardSetInfo; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.nodes.get") +public class GetNodesWebScript extends AbstractAsieEnterpriseWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException { + this.logger.trace("execute()"); + + Map>> flocs = this.getShardRegistry().getFlocs(); + if (flocs.isEmpty()) + throw new WebScriptException(HttpStatus.NO_CONTENT.value(), "There are no ASIE shards registred with ACS"); + + SolrShardHashSampleType sampleHashType = this.getOptionalQueryParameter(req, "sampleHashType", SolrShardHashSampleType.class); + + Map nodes = new TreeMap<>(); + + for (Entry>> floc : flocs.entrySet()) { + int maxShards = floc.getKey().getNumberOfShards(); + + SolrShardHashTable sampleHashTable = sampleHashType == null ? null : this.createSampleHashTable(sampleHashType, maxShards); + + for (Entry> registeredShards : floc.getValue().entrySet()) { + for (ShardState registeredShardNode : registeredShards.getValue()) { + NodeInfo node = nodes.get(NodeInfo.determineId(registeredShardNode.getShardInstance())); + if (node == null) { + node = new NodeShardInfo(registeredShardNode.getShardInstance()); + nodes.put(node.getId(), node); + } + + ShardInfo shard = new ShardInfo(registeredShardNode); + shard.setShardSet(new ShardSetInfo(floc.getKey(), registeredShardNode)); + if (sampleHashTable != null) + this.addShardHashSamples(shard, sampleHashTable); + node.getShards().put(shard.getId(), shard); + } + } + } + + res.setContentType("application/json"); + res.setContentEncoding("utf-8"); + this.getObjectMapper().writeValue(res.getWriter(), nodes); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetPropertyHashShardsWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetPropertyHashShardsWebScript.java new file mode 100644 index 0000000..b69aafd --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetPropertyHashShardsWebScript.java @@ -0,0 +1,150 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.repo.index.shard.Floc; +import org.alfresco.repo.index.shard.Shard; +import org.alfresco.repo.index.shard.ShardMethodEnum; +import org.alfresco.repo.index.shard.ShardState; +import org.alfresco.util.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.compute.SolrShardHashTable; +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.PropertyHashShardSetInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardNodeInfo; +import com.inteligr8.alfresco.asie.enterprise.service.ShardDiscoveryService; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.propertyHashShards.get") +public class GetPropertyHashShardsWebScript extends AbstractAsieEnterpriseWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private ShardDiscoveryService sds; + + @Override + public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException { + this.logger.trace("execute()"); + + SolrShardHashSampleType sampleHashType = this.getRequiredPathParameter(req, "sampleHashType", SolrShardHashSampleType.class); + + Integer max = this.getOptionalQueryParameter(req, "max", Integer.class); + Integer min = this.getOptionalQueryParameter(req, "min", Integer.class); + List values = this.getOptionalQueryParameterAsList(req); + this.validateParameters(min, max, values); + + List shardSets = new LinkedList<>(); + + Collection>>> flocs = this.sds.findByShardMethod(ShardMethodEnum.PROPERTY); + if (flocs.isEmpty()) + throw new WebScriptException(HttpStatus.NO_CONTENT.value(), "There are no property-based shards"); + + for (Pair>> floc : flocs) { + ShardState anyShardNode = this.getAnyShardNode(floc.getSecond()); + PropertyHashShardSetInfo shardSet = new PropertyHashShardSetInfo(floc.getFirst(), anyShardNode); + shardSet.setShards(new TreeMap<>()); + + int maxShards = floc.getFirst().getNumberOfShards(); + SolrShardHashTable sampleHashTable = this.createSampleHashTable(sampleHashType, maxShards); + + Map> shardToHashMap = new HashMap<>(); + + if (max != null && min != null) { + for (int i = min; i <= max; i++) { + int shardId = sampleHashTable.hashForward(i); + this.getAdd(shardToHashMap, shardId, i); + } + } else if (values != null) { + for (String value : values) { + int shardId = sampleHashTable.hashForward(value); + this.getAdd(shardToHashMap, shardId, value); + } + } + + for (Entry> shardCache : floc.getSecond().entrySet()) { + ShardInfo shard = new ShardInfo(); + shard.setId(shardCache.getKey().getInstance()); + shard.setNodes(new HashMap<>()); + + for (ShardState shardNodeCache : shardCache.getValue()) { + if (shard.getTxsCompleted() == null || shard.getTxsCompleted().longValue() < shardNodeCache.getLastIndexedTxId()) { + shard.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(shardNodeCache.getLastIndexedTxCommitTime()), ZoneOffset.UTC)); + shard.setTxsCompleted(shardNodeCache.getLastIndexedTxId()); + } + + NodeInfo node = new ShardNodeInfo(shardNodeCache); + shard.getNodes().put(node.getId(), node); + } + + List hashedValues = shardToHashMap.get(shard.getId()); + if (hashedValues != null) for (Object hashedValue : hashedValues) + shardSet.getShards().put(hashedValue, shard); + } + + shardSets.add(shardSet); + } + + res.setContentType(MediaType.APPLICATION_JSON_VALUE); + res.setContentEncoding("utf-8"); + this.getObjectMapper().writeValue(res.getWriter(), shardSets); + } + + private ShardState getAnyShardNode(Map> shards) { + for (Set shardNodes : shards.values()) + for (ShardState shardNode : shardNodes) + return shardNode; + return null; + } + + private List getOptionalQueryParameterAsList(WebScriptRequest req) { + String valuesStr = this.getOptionalQueryParameter(req, "values"); + if (valuesStr == null) + return null; + + String[] values = valuesStr.trim().split("[ ]*[,|][ ]*"); + return Arrays.asList(values); + } + + private void validateParameters(Integer min, Integer max, List values) { + if (max != null && min != null) { + if (min > max) + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The `min` parameter value may not be greater than the `max` parameter value"); + } else if (values != null) { + } else { + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "Either `min`/`max` or `values` query parameter is required"); + } + } + + private void getAdd(Map> map, K key, V value) { + List values = map.get(key); + if (values == null) { + values = new LinkedList<>(); + map.put(key, values); + } + + values.add(value); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetSampleHashesWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetSampleHashesWebScript.java new file mode 100644 index 0000000..5d08cf4 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetSampleHashesWebScript.java @@ -0,0 +1,96 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.compute.SolrShardEnumeratedHashTable; +import com.inteligr8.alfresco.asie.compute.SolrShardHashTable; +import com.inteligr8.alfresco.asie.compute.SolrShardNumericHashTable; +import com.inteligr8.alfresco.asie.service.SolrShardHashService; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.sampleHashes.get") +public class GetSampleHashesWebScript extends AbstractAsieEnterpriseWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private SolrShardHashService sshs; + + @Override + public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException { + this.logger.trace("execute()"); + + int shards = this.getRequiredPathParameter(req, "shards", Integer.class); + Integer max = this.getOptionalQueryParameter(req, "max", Integer.class); + Integer min = this.getOptionalQueryParameter(req, "min", Integer.class); + List values = this.getOptionalQueryParameterAsList(req); + this.validateParameters(min, max, values); + + Map forward = new HashMap<>(); + Map> reverse = new HashMap<>(); + + SolrShardHashTable table = null; + if (max != null && min != null) { + table = new SolrShardNumericHashTable(shards, this.sshs).build(min, max); + for (int i = min; i <= max; i++) + forward.put(String.valueOf(i), table.hashForward(i)); + } else if (values != null) { + table = new SolrShardEnumeratedHashTable(shards, this.sshs).build(values); + for (String value : values) + forward.put(value, table.hashForward(value)); + } + + for (int s = 0; s < shards; s++) { + Set vs = table.hashReverse(s); + if (vs != null && !vs.isEmpty()) { + reverse.put(s, vs); + } else { + reverse.put(s, Collections.emptyList()); + } + } + + Map> response = new HashMap<>(); + response.put("forward", forward); + response.put("reverse", reverse); + + res.setContentType(MediaType.APPLICATION_JSON_VALUE); + res.setContentEncoding("utf-8"); + this.getObjectMapper().writeValue(res.getWriter(), response); + } + + private List getOptionalQueryParameterAsList(WebScriptRequest req) { + String valuesStr = this.getOptionalQueryParameter(req, "values"); + if (valuesStr == null) + return null; + + String[] values = valuesStr.trim().split("[ ]*[,|][ ]*"); + return Arrays.asList(values); + } + + private void validateParameters(Integer min, Integer max, List values) { + if (max != null && min != null) { + if (min > max) + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The `min` parameter value may not be greater than the `max` parameter value"); + } else if (values != null) { + } else { + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "Either `min`/`max` or `values` query parameter is required"); + } + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetShardWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetShardWebScript.java new file mode 100644 index 0000000..2feafb7 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetShardWebScript.java @@ -0,0 +1,57 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.repo.index.shard.Shard; +import org.alfresco.repo.index.shard.ShardState; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.compute.SolrShardHashTable; +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardNodeInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardSetInfo; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.shard.get") +public class GetShardWebScript extends AbstractAsieShardWebScript { + + @Override + public void execute(WebScriptRequest req, WebScriptResponse res, Set registeredShardNodes) throws IOException { + ShardState aRegisteredShardNode = registeredShardNodes.iterator().next(); + Shard registeredShard = aRegisteredShardNode.getShardInstance().getShard(); + int maxShards = registeredShard.getFloc().getNumberOfShards(); + + SolrShardHashSampleType sampleHashType = this.getOptionalQueryParameter(req, "sampleHashType", SolrShardHashSampleType.class); + SolrShardHashTable sampleHashTable = sampleHashType == null ? null : this.createSampleHashTable(sampleHashType, maxShards); + + ShardInfo shard = new ShardInfo(); + shard.setId(registeredShard.getInstance()); + shard.setShardSet(new ShardSetInfo(registeredShard.getFloc(), aRegisteredShardNode)); + shard.setNodes(new TreeMap<>()); + if (sampleHashTable != null) + this.addShardHashSamples(shard, sampleHashTable); + + for (ShardState registeredShardNode : registeredShardNodes) { + if (shard.getTxsCompleted() == null || shard.getTxsCompleted().longValue() < registeredShardNode.getLastIndexedTxId()) { + shard.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(registeredShardNode.getLastIndexedTxCommitTime()), ZoneOffset.UTC)); + shard.setTxsCompleted(registeredShardNode.getLastIndexedTxId()); + } + + NodeInfo node = new ShardNodeInfo(registeredShardNode); + shard.getNodes().put(node.getId(), node); + } + + res.setContentType(MediaType.APPLICATION_JSON_VALUE); + res.setContentEncoding("utf-8"); + this.getObjectMapper().writeValue(res.getWriter(), shard); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetShardsWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetShardsWebScript.java new file mode 100644 index 0000000..41f3f91 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/GetShardsWebScript.java @@ -0,0 +1,90 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.repo.index.shard.Floc; +import org.alfresco.repo.index.shard.Shard; +import org.alfresco.repo.index.shard.ShardState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.compute.SolrShardHashTable; +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardNodeInfo; +import com.inteligr8.alfresco.asie.enterprise.rest.model.ShardSetInfo; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.shards.get") +public class GetShardsWebScript extends AbstractAsieEnterpriseWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException { + this.logger.trace("execute()"); + + Map>> flocs = this.getShardRegistry().getFlocs(); + if (flocs.isEmpty()) + throw new WebScriptException(HttpStatus.NO_CONTENT.value(), "There are no ASIE shards registred with ACS"); + + SolrShardHashSampleType sampleHashType = this.getOptionalQueryParameter(req, "sampleHashType", SolrShardHashSampleType.class); + + Map shardSets = new TreeMap<>(); + + for (Entry>> floc : flocs.entrySet()) { + int maxShards = floc.getKey().getNumberOfShards(); + ShardState anyShardNode = this.getAnyShardNode(floc.getValue()); + ShardSetInfo shardSet = new ShardSetInfo(floc.getKey(), anyShardNode); + shardSet.setShards(new TreeMap<>()); + + SolrShardHashTable sampleHashTable = sampleHashType == null ? null : this.createSampleHashTable(sampleHashType, maxShards); + + for (Entry> registeredShard : floc.getValue().entrySet()) { + ShardInfo shard = new ShardInfo(); + shard.setId(registeredShard.getKey().getInstance()); + shard.setNodes(new TreeMap<>()); + + for (ShardState registeredShardNode : registeredShard.getValue()) { + if (shard.getTxsCompleted() == null || shard.getTxsCompleted().longValue() < registeredShardNode.getLastIndexedTxId()) { + shard.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(registeredShardNode.getLastIndexedTxCommitTime()), ZoneOffset.UTC)); + shard.setTxsCompleted(registeredShardNode.getLastIndexedTxId()); + } + + NodeInfo node = new ShardNodeInfo(registeredShardNode); + shard.getNodes().put(node.getId(), node); + } + + if (sampleHashTable != null) + this.addShardHashSamples(shardSet, shard, sampleHashTable); + shardSet.getShards().put(shard.getId(), shard); + } + + shardSets.put(shardSet.getMethodSpec(), shardSet); + } + + res.setContentType(MediaType.APPLICATION_JSON_VALUE); + res.setContentEncoding("utf-8"); + this.getObjectMapper().writeValue(res.getWriter(), shardSets); + } + + private ShardState getAnyShardNode(Map> shards) { + for (Set shardNodes : shards.values()) + for (ShardState shardNode : shardNodes) + return shardNode; + return null; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ReloadNodeShardWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ReloadNodeShardWebScript.java new file mode 100644 index 0000000..da920d8 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ReloadNodeShardWebScript.java @@ -0,0 +1,135 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.service.cmr.attributes.AttributeService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.Constants; +import com.inteligr8.alfresco.asie.api.CoreAdminApi; +import com.inteligr8.solr.model.ExceptionResponse; +import com.inteligr8.solr.model.core.CreateRequest; +import com.inteligr8.solr.model.core.ReloadRequest; + +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.InternalServerErrorException; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.nodeShard.post") +public class ReloadNodeShardWebScript extends AbstractAsieNodeWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Pattern shardRangePattern = Pattern.compile("([0-9]+)-([0-9]+)"); + + @Autowired + @Qualifier(Constants.QUALIFIER_ASIE) + private AttributeService attrService; + + @Override + protected void execute(WebScriptRequest req, WebScriptResponse res, final String nodeHostname, final int nodePort) + throws IOException { + String shardCore = this.getRequiredPathParameter(req, "shardCore"); + int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class); + String coreName = shardCore + "-" + shardId; + + Serializable[] keys = new String[] { + Constants.ATTR_ASIE, + Constants.ATTR_UNLOADED_NODE_CORES, + nodeHostname + ":" + nodePort + }; + + Map cores = this.fetchUnloadedCores(keys); + if (!cores.containsKey(coreName)) { + cores.putAll(this.fetchOtherCores(req)); + } + + boolean changed = false; + String coreInstancePath = cores.get(coreName); + if (coreInstancePath == null) + throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The specified node/shard could not be found or formulated"); + + this.logger.info("Reloading core {} on ASIE node: {}", coreName, nodeHostname); + CoreAdminApi api = this.createApi(nodeHostname, nodePort); + try { + api.create(new CreateRequest() + .withCore(coreName) + .withConfigDirectory(coreInstancePath)); + } catch (BadRequestException bre) { + this.logger.warn("Core {} does not exist on ASIE node: {}; forgetting it", coreName, nodeHostname); + cores.remove(coreName); + changed = true; + } catch (InternalServerErrorException isee) { + ExceptionResponse response = isee.getResponse().readEntity(ExceptionResponse.class); + if (response.getError() == null || response.getError().getMessage() == null || !response.getError().getMessage().endsWith(" already exists.")) + throw isee; + + this.logger.warn("Core {} was already loaded on ASIE node: {}; reloading ...", coreName, nodeHostname); + api.reload(new ReloadRequest() + .withCore(coreName)); + } + + if (changed) + this.attrService.setAttribute((Serializable) cores, keys); + + res.setStatus(HttpStatus.OK.value()); + } + + private Map fetchUnloadedCores(Serializable[] keys) { + @SuppressWarnings("unchecked") + Map cores = (Map) this.attrService.getAttribute(keys); + if (cores == null) + cores = new HashMap(); + return cores; + } + + private Map fetchOtherCores(WebScriptRequest req) { + String coreName = this.getOptionalQueryParameter(req, "coreName"); + if (coreName == null) + coreName = this.getOptionalQueryParameter(req, "core"); + String shardRange = this.getOptionalQueryParameter(req, "shardRange"); + Short shardCount = this.getOptionalQueryParameter(req, "shardCount", Short.class); + if (coreName == null || shardRange == null || shardCount == null) + return Collections.emptyMap(); + + String template = this.getOptionalQueryParameter(req, "template"); + if (template == null) + template = "rerank"; + Short nodeId = this.getOptionalQueryParameter(req, "nodeId", Short.class); + if (nodeId == null) + nodeId = 1; + Short nodeCount = this.getOptionalQueryParameter(req, "nodeCount", Short.class); + if (nodeCount == null) + nodeCount = 2; + + String baseConfigDirectory = template + "--" + coreName + "--shards--" + shardCount + "-x-1--node--" + nodeId + "-of-" + nodeCount; + + Matcher matcher = this.shardRangePattern.matcher(shardRange); + if (!matcher.find()) + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'shardRange' query parameter value is not formatted as expected"); + + Map cores = new HashMap<>(); + + short startShardId = Short.parseShort(matcher.group(1)); + short endShardId = Short.parseShort(matcher.group(2)); + for (short shardId = startShardId; shardId <= endShardId; shardId++) { + String shardConfigDirectory = baseConfigDirectory + "/" + coreName + "-" + shardId; + cores.put(coreName, shardConfigDirectory); + } + + return cores; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ReloadNodeWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ReloadNodeWebScript.java new file mode 100644 index 0000000..1f846cf --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/ReloadNodeWebScript.java @@ -0,0 +1,137 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.service.cmr.attributes.AttributeService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.Constants; +import com.inteligr8.alfresco.asie.api.CoreAdminApi; +import com.inteligr8.solr.model.ExceptionResponse; +import com.inteligr8.solr.model.core.CreateRequest; +import com.inteligr8.solr.model.core.ReloadRequest; + +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.InternalServerErrorException; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.node.post") +public class ReloadNodeWebScript extends AbstractAsieNodeWebScript { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Pattern shardRangePattern = Pattern.compile("([0-9]+)-([0-9]+)"); + + @Autowired + @Qualifier(Constants.QUALIFIER_ASIE) + private AttributeService attrService; + + @Override + protected void execute(WebScriptRequest req, WebScriptResponse res, final String nodeHostname, final int nodePort) + throws IOException { + Serializable[] keys = new String[] { + Constants.ATTR_ASIE, + Constants.ATTR_UNLOADED_NODE_CORES, + nodeHostname + ":" + nodePort + }; + + Map cores = this.fetchUnloadedCores(keys); + cores.putAll(this.fetchOtherCores(req)); + if (cores.isEmpty()) + throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The specified node was not found or formulated"); + + boolean changed = false; + + Iterator> i = cores.entrySet().iterator(); + while (i.hasNext()) { + Entry core = i.next(); + String coreName = core.getKey(); + String coreInstancePath = core.getValue(); + + this.logger.info("Reloading core {} on ASIE node: {}", coreName, nodeHostname); + CoreAdminApi api = this.createApi(nodeHostname, nodePort); + try { + api.create(new CreateRequest() + .withCore(coreName) + .withConfigDirectory(coreInstancePath)); + } catch (BadRequestException bre) { + this.logger.warn("Core {} does not exist on ASIE node: {}; forgetting it", coreName, nodeHostname); + i.remove(); + changed = true; + } catch (InternalServerErrorException isee) { + ExceptionResponse response = isee.getResponse().readEntity(ExceptionResponse.class); + if (response.getError() == null || response.getError().getMessage() == null || !response.getError().getMessage().endsWith(" already exists.")) + throw isee; + + this.logger.info("Core {} was already loaded on ASIE node: {}; reloading ...", coreName, nodeHostname); + api.reload(new ReloadRequest() + .withCore(coreName)); + } + } + + if (changed) + this.attrService.setAttribute((Serializable) cores, keys); + + res.setStatus(HttpStatus.OK.value()); + } + + private Map fetchUnloadedCores(Serializable[] keys) { + @SuppressWarnings("unchecked") + Map cores = (Map) this.attrService.getAttribute(keys); + if (cores == null) + cores = new HashMap(); + return cores; + } + + private Map fetchOtherCores(WebScriptRequest req) { + String coreName = this.getOptionalQueryParameter(req, "coreName"); + if (coreName == null) + coreName = this.getOptionalQueryParameter(req, "core"); + String shardRange = this.getOptionalQueryParameter(req, "shardRange"); + Short shardCount = this.getOptionalQueryParameter(req, "shardCount", Short.class); + if (coreName == null || shardRange == null || shardCount == null) + return Collections.emptyMap(); + + String template = this.getOptionalQueryParameter(req, "template"); + if (template == null) + template = "rerank"; + Short nodeId = this.getOptionalQueryParameter(req, "nodeId", Short.class); + if (nodeId == null) + nodeId = 1; + Short nodeCount = this.getOptionalQueryParameter(req, "nodeCount", Short.class); + if (nodeCount == null) + nodeCount = 2; + + String baseConfigDirectory = template + "--" + coreName + "--shards--" + shardCount + "-x-1--node--" + nodeId + "-of-" + nodeCount; + + Matcher matcher = this.shardRangePattern.matcher(shardRange); + if (!matcher.find()) + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'shardRange' query parameter value is not formatted as expected"); + + Map cores = new HashMap<>(); + + short startShardId = Short.parseShort(matcher.group(1)); + short endShardId = Short.parseShort(matcher.group(2)); + for (short shardId = startShardId; shardId <= endShardId; shardId++) { + String shardConfigDirectory = baseConfigDirectory + "/" + coreName + "-" + shardId; + cores.put(coreName, shardConfigDirectory); + } + + return cores; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/UnloadNodeShardWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/UnloadNodeShardWebScript.java new file mode 100644 index 0000000..2bcd870 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/UnloadNodeShardWebScript.java @@ -0,0 +1,31 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import org.alfresco.repo.index.shard.ShardState; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.enterprise.model.NodeShardParameterSet; +import com.inteligr8.alfresco.asie.enterprise.model.ShardSet; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.nodeShard.delete") +public class UnloadNodeShardWebScript extends AbstractUnregisterNodeWebScript { + + @Override + protected NodeShardParameterSet createParameters(WebScriptRequest req, String nodeHostname, int nodePort) { + ShardSet shardSet = this.getRequiredPathParameter(req, "shardSet", ShardSet.class); + int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class); + + return new NodeShardParameterSet(nodeHostname, nodePort, shardSet, shardId); + } + + @Override + protected boolean matches(NodeShardParameterSet params, ShardState shardState) { + if (!params.getShardSet().isFor(shardState)) + return false; + if (params.getShardId() != shardState.getShardInstance().getShard().getInstance()) + return false; + + return true; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/UnloadNodeWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/UnloadNodeWebScript.java new file mode 100644 index 0000000..f624437 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/UnloadNodeWebScript.java @@ -0,0 +1,16 @@ +package com.inteligr8.alfresco.asie.enterprise.rest; + +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.model.NodeParameterSet; + +@Component(value = "webscript.com.inteligr8.alfresco.asie.enterprise.node.delete") +public class UnloadNodeWebScript extends AbstractUnregisterNodeWebScript { + + @Override + protected NodeParameterSet createParameters(WebScriptRequest req, String nodeHostname, int nodePort) { + return new NodeParameterSet(nodeHostname, nodePort); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/NodeInfo.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/NodeInfo.java new file mode 100644 index 0000000..d2b6ac8 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/NodeInfo.java @@ -0,0 +1,48 @@ +package com.inteligr8.alfresco.asie.enterprise.rest.model; + +import java.util.Map; +import java.util.TreeMap; + +import org.alfresco.repo.index.shard.ShardInstance; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public abstract class NodeInfo { + + public static String determineId(ShardInstance nodeCache) { + int lastSlash = nodeCache.getBaseUrl().lastIndexOf('/'); + return nodeCache.getHostName() + ":" + nodeCache.getPort() + nodeCache.getBaseUrl().substring(0, lastSlash); + } + + @JsonProperty + private String id; + + @JsonProperty + private Map shards; + + public NodeInfo() { + } + + public NodeInfo(ShardInstance nodeCache) { + this.setId(determineId(nodeCache)); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Map getShards() { + if (shards == null) + shards = new TreeMap<>(); + return shards; + } + + public void setShards(Map shards) { + this.shards = shards; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/NodeShardInfo.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/NodeShardInfo.java new file mode 100644 index 0000000..5d10536 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/NodeShardInfo.java @@ -0,0 +1,26 @@ +package com.inteligr8.alfresco.asie.enterprise.rest.model; + +import java.util.Map; + +import org.alfresco.repo.index.shard.ShardInstance; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class NodeShardInfo extends NodeInfo { + + @JsonProperty + private Map shards; + + public NodeShardInfo() { + } + + public NodeShardInfo(ShardInstance nodeCache) { + super(nodeCache); + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/PropertyHashShardSetInfo.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/PropertyHashShardSetInfo.java new file mode 100644 index 0000000..4a389ef --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/PropertyHashShardSetInfo.java @@ -0,0 +1,60 @@ +package com.inteligr8.alfresco.asie.enterprise.rest.model; + +import java.util.Map; + +import org.alfresco.repo.index.shard.Floc; +import org.alfresco.repo.index.shard.ShardState; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.inteligr8.alfresco.asie.enterprise.model.ShardSet; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class PropertyHashShardSetInfo { + + @JsonProperty + private String methodSpec; + + @JsonProperty + private int shardCount; + + @JsonProperty + private Map shards; + + public PropertyHashShardSetInfo() { + } + + public PropertyHashShardSetInfo(Floc floc, ShardState anyShardNode) { + ShardSet shardSet = new ShardSet(floc, anyShardNode); + this.setMethodSpec(shardSet.toSpec()); + this.setShardCount(floc.getNumberOfShards()); + } + + public String getMethodSpec() { + return this.methodSpec; + } + + public void setMethodSpec(String methodSpec) { + this.methodSpec = methodSpec; + } + + public int getShardCount() { + return shardCount; + } + + public void setShardCount(int shardCount) { + this.shardCount = shardCount; + } + + public Map getShards() { + return shards; + } + + public void setShards(Map shards) { + this.shards = shards; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardInfo.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardInfo.java new file mode 100644 index 0000000..09f2399 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardInfo.java @@ -0,0 +1,97 @@ +package com.inteligr8.alfresco.asie.enterprise.rest.model; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.util.Map; + +import org.alfresco.repo.index.shard.ShardState; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +@JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ShardInfo { + + @JsonProperty + private int id; + + @JsonProperty + private Long txsCompleted; + + @JsonProperty + private Long txsRemaining; + + @JsonProperty + @JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX") + private OffsetDateTime latestTx; + + @JsonProperty + private Map nodes; + + @JsonProperty + private ShardSetInfo shardSet; + + public ShardInfo() { + } + + public ShardInfo(ShardState shard) { + this.setId(shard.getShardInstance().getShard().getInstance()); + this.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(shard.getLastIndexedTxCommitTime()), ZoneOffset.UTC)); + this.setTxsCompleted(shard.getLastIndexedTxId()); + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Long getTxsCompleted() { + return txsCompleted; + } + + public void setTxsCompleted(Long txsCompleted) { + this.txsCompleted = txsCompleted; + } + + public Long getTxsRemaining() { + return txsRemaining; + } + + public void setTxsRemaining(Long txsRemaining) { + this.txsRemaining = txsRemaining; + } + + public OffsetDateTime getLatestTx() { + return latestTx; + } + + public void setLatestTx(OffsetDateTime latestTx) { + this.latestTx = latestTx; + } + + public Map getNodes() { + return nodes; + } + + public void setNodes(Map nodes) { + this.nodes = nodes; + } + + public ShardSetInfo getShardSet() { + return shardSet; + } + + public void setShardSet(ShardSetInfo shardSet) { + this.shardSet = shardSet; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardNodeInfo.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardNodeInfo.java new file mode 100644 index 0000000..6ed16b6 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardNodeInfo.java @@ -0,0 +1,52 @@ +package com.inteligr8.alfresco.asie.enterprise.rest.model; + +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +import org.alfresco.repo.index.shard.ShardState; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonFormat.Shape; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ShardNodeInfo extends NodeInfo { + + @JsonProperty + private Long txsCompleted; + + @JsonProperty + @JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX") + private OffsetDateTime latestTx; + + public ShardNodeInfo() { + } + + public ShardNodeInfo(ShardState shard) { + super(shard.getShardInstance()); + this.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(shard.getLastIndexedTxCommitTime()), ZoneOffset.UTC)); + this.setTxsCompleted(shard.getLastIndexedTxId()); + } + + public Long getTxsCompleted() { + return txsCompleted; + } + + public void setTxsCompleted(Long txsCompleted) { + this.txsCompleted = txsCompleted; + } + + public OffsetDateTime getLatestTx() { + return latestTx; + } + + public void setLatestTx(OffsetDateTime latestTx) { + this.latestTx = latestTx; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardSetInfo.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardSetInfo.java new file mode 100644 index 0000000..f44722a --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/model/ShardSetInfo.java @@ -0,0 +1,73 @@ +package com.inteligr8.alfresco.asie.enterprise.rest.model; + +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.alfresco.repo.index.shard.Floc; +import org.alfresco.repo.index.shard.ShardState; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.inteligr8.alfresco.asie.enterprise.model.ShardSet; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties(ignoreUnknown = true) +public class ShardSetInfo { + + @JsonProperty + private String methodSpec; + + @JsonProperty + private int shardCount; + + @JsonProperty + private Map shards; + + @JsonProperty + private Map> shardHashSamples = new TreeMap<>(); + + public ShardSetInfo() { + } + + public ShardSetInfo(Floc floc, ShardState anyShardNode) { + ShardSet shardSet = new ShardSet(floc, anyShardNode); + this.methodSpec = shardSet.toSpec(); + this.setShardCount(floc.getNumberOfShards()); + } + + public String getMethodSpec() { + return this.methodSpec; + } + + public void setMethodSpec(String methodSpec) { + this.methodSpec = methodSpec; + } + + public int getShardCount() { + return shardCount; + } + + public void setShardCount(int shardCount) { + this.shardCount = shardCount; + } + + public Map getShards() { + return shards; + } + + public void setShards(Map shards) { + this.shards = shards; + } + + public Map> getShardHashSamples() { + return shardHashSamples; + } + + public void setShardHashSamples(Map> shardHashSamples) { + this.shardHashSamples = shardHashSamples; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardAnalysisService.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardAnalysisService.java new file mode 100644 index 0000000..0bfbdd0 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardAnalysisService.java @@ -0,0 +1,29 @@ +package com.inteligr8.alfresco.asie.enterprise.service; + +import java.util.Collection; + +import org.alfresco.repo.index.shard.ShardInstance; +import org.alfresco.repo.index.shard.ShardState; +import org.springframework.stereotype.Component; + +@Component +public class ShardAnalysisService { + + public ShardInstance computeLeadShard(Collection shardNodesCache) { + if (shardNodesCache.isEmpty()) + return null; + + long latestTime = 0L; + ShardInstance latestNode = null; + + for (ShardState shardNodeCache : shardNodesCache) { + if (latestTime < shardNodeCache.getLastIndexedTxCommitTime()) { + latestNode = shardNodeCache.getShardInstance(); + latestTime = shardNodeCache.getLastIndexedTxCommitTime(); + } + } + + return latestNode; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardBackupService.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardBackupService.java new file mode 100644 index 0000000..2a3da36 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardBackupService.java @@ -0,0 +1,102 @@ +package com.inteligr8.alfresco.asie.enterprise.service; + +import java.io.Serializable; +import java.util.Collection; + +import org.alfresco.repo.index.shard.Shard; +import org.alfresco.repo.index.shard.ShardInstance; +import org.alfresco.repo.index.shard.ShardState; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.Constants; +import com.inteligr8.alfresco.asie.enterprise.rest.model.NodeInfo; + +@Component +public class ShardBackupService { + + private static final String ATTR_BACKUP_NODE = "backupNode"; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + private ShardAnalysisService sas; + + @Autowired + @Qualifier(Constants.QUALIFIER_ASIE) + private AttributeService attributeService; + + @Value("${inteligr8.asie.backup.persistTimeMinutes}") + private int persistTimeMinutes; + + public String fetchNodeId(Collection shardNodes) { + if (shardNodes.isEmpty()) + return null; + + ShardState shardNode0 = shardNodes.iterator().next(); + ShardInstance node0Shard = shardNode0.getShardInstance(); + Shard shard = node0Shard.getShard(); + String shardKey = shard.getFloc().getShardMethod().name() + "~" + shard.getFloc().getNumberOfShards() + "~" + shard.getInstance(); + + PersistedNode backupNode = (PersistedNode) this.attributeService.getAttribute(Constants.ATTR_ASIE, ATTR_BACKUP_NODE, shardKey); + this.logger.debug("Found backup node: {}", backupNode); + + if (backupNode == null || backupNode.isExpired()) { + ShardInstance backupShardInstance = this.sas.computeLeadShard(shardNodes); + backupNode = new PersistedNode(NodeInfo.determineId(backupShardInstance)); + this.attributeService.setAttribute(backupNode, Constants.ATTR_ASIE, ATTR_BACKUP_NODE, shardKey); + } + + return backupNode.getNode(); + } + + public void forget() { + this.attributeService.removeAttribute(Constants.ATTR_ASIE, ATTR_BACKUP_NODE); + } + + public void forget(ShardState shardNode) { + ShardInstance nodeShard = shardNode.getShardInstance(); + Shard shard = nodeShard.getShard(); + String shardKey = shard.getFloc().getShardMethod().name() + "~" + shard.getFloc().getNumberOfShards() + "~" + shard.getInstance(); + + this.attributeService.removeAttribute(Constants.ATTR_ASIE, ATTR_BACKUP_NODE, shardKey); + } + + + + private class PersistedNode implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String node; + private long expireTimeMillis; + + PersistedNode(String node) { + this.node = node; + this.reset(); + } + + void reset() { + this.expireTimeMillis = System.currentTimeMillis() + persistTimeMinutes * 60L * 1000L; + } + + boolean isExpired() { + return this.expireTimeMillis < System.currentTimeMillis(); + } + + String getNode() { + return this.node; + } + + @Override + public String toString() { + return "node: " + this.node + "; expires in: " + (System.currentTimeMillis() - this.expireTimeMillis) + " ms"; + } + + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardDiscoveryService.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardDiscoveryService.java new file mode 100644 index 0000000..8ed5b1e --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardDiscoveryService.java @@ -0,0 +1,142 @@ +package com.inteligr8.alfresco.asie.enterprise.service; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.alfresco.repo.index.shard.Floc; +import org.alfresco.repo.index.shard.Shard; +import org.alfresco.repo.index.shard.ShardInstance; +import org.alfresco.repo.index.shard.ShardMethodEnum; +import org.alfresco.repo.index.shard.ShardRegistry; +import org.alfresco.repo.index.shard.ShardState; +import org.alfresco.util.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.Constants; +import com.inteligr8.alfresco.asie.enterprise.model.ShardSet; + +@Component +public class ShardDiscoveryService { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + @Qualifier(Constants.BEAN_SHARD_REGISTRY) + private ShardRegistry shardRegistry; + + public Set findByNode(String nodeHostname, int nodePort) { + Map>> flocs = this.shardRegistry.getFlocs(); + if (flocs.isEmpty()) + return Collections.emptySet(); + + Set shards = new HashSet<>(); + + for (Entry>> floc : flocs.entrySet()) { + for (Entry> flocShard : floc.getValue().entrySet()) { + for (ShardState shardState : flocShard.getValue()) { + ShardInstance shardInstance = shardState.getShardInstance(); + if (!nodeHostname.equalsIgnoreCase(shardInstance.getHostName())) { + InetAddress nodeAddress = this.resolve(nodeHostname); + if (nodeAddress == null) + continue; + + InetAddress shardInstanceAddress = this.resolve(shardInstance.getHostName()); + if (!nodeAddress.equals(shardInstanceAddress)) + continue; + } + + if (nodePort == shardInstance.getPort()) + shards.add(shardState); + } + } + } + + return shards; + } + + public Map> findByShardSet(ShardSet shardSet) { + Map>> flocs = this.shardRegistry.getFlocs(); + if (flocs.isEmpty()) + return Collections.emptyMap(); + this.logger.trace("Found {} shard sets", flocs.size()); + + for (Entry>> floc : flocs.entrySet()) { + if (!floc.getKey().getShardMethod().equals(shardSet.getMethod())) + continue; + + if (!shardSet.getConfig().isEmpty()) { + if (floc.getValue().isEmpty()) + continue; + Shard firstShard = floc.getValue().keySet().iterator().next(); + + Set firstShardStates = floc.getValue().get(firstShard); + if (firstShardStates == null || firstShardStates.isEmpty()) + continue; + ShardState firstShardState = firstShardStates.iterator().next(); + + Map firstShardProps = firstShardState.getPropertyBag(); + if (!shardSet.isConfigurationFor(firstShardProps)) + continue; + } + + return floc.getValue(); + } + + return Collections.emptyMap(); + } + + public Collection>>> findByShardMethod(ShardMethodEnum shardMethod) { + Map>> flocs = this.shardRegistry.getFlocs(); + if (flocs.isEmpty()) + return Collections.emptyList(); + this.logger.trace("Found {} shard sets", flocs.size()); + + List>>> filteredFlocs = new LinkedList<>(); + + for (Entry>> floc : flocs.entrySet()) { + if (!floc.getKey().getShardMethod().equals(shardMethod)) + continue; + filteredFlocs.add(new Pair<>(floc.getKey(), floc.getValue())); + } + + return filteredFlocs; + } + + public Set findByShard(Map> shards, int shardId) { + if (shards == null) + return null; + + for (Entry> shard : shards.entrySet()) { + if (shard.getKey().getInstance() == shardId) + return shard.getValue(); + } + + return Collections.emptySet(); + } + + public Set findByShard(ShardSet shardSet, int shardId) { + Map> shards = this.findByShardSet(shardSet); + return this.findByShard(shards, shardId); + } + + private InetAddress resolve(String hostname) { + try { + return InetAddress.getByName(hostname); + } catch (UnknownHostException uhe) { + return null; + } + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardStateService.java b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardStateService.java new file mode 100644 index 0000000..202e7ce --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/enterprise/service/ShardStateService.java @@ -0,0 +1,60 @@ +package com.inteligr8.alfresco.asie.enterprise.service; + +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.index.shard.ShardInstance; +import org.alfresco.repo.index.shard.ShardState; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import com.inteligr8.alfresco.asie.Constants; + +@Component +public class ShardStateService { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Autowired + @Qualifier(Constants.QUALIFIER_ASIE) + private AttributeService attrService; + + @Autowired + @Qualifier("shardStateCache") + private SimpleCache shardStateCache; + + @Autowired + @Qualifier("shardToGuidCache") + private SimpleCache shardToGuidCache; + + public void clear() { + this.logger.info("Removing all nodes/shards from the shard registry"); + + // this clears the state from the backend database + this.attrService.removeAttribute(Constants.ATTR_SHARD_STATE); + this.attrService.removeAttribute(Constants.ATTR_SHARD_SUBSCRIPTION); + + // this clears the state from Hazelcast + this.shardStateCache.clear(); + this.shardToGuidCache.clear(); + } + + public void clear(String guid) { + this.logger.info("Removing node from the shard registry: {}", guid); + + ShardState shardState = (ShardState) this.attrService.getAttribute(Constants.ATTR_SHARD_STATE, guid); + + // this clears the state from the backend database + this.attrService.removeAttribute(Constants.ATTR_SHARD_STATE, guid); + this.attrService.removeAttribute(Constants.ATTR_SHARD_SUBSCRIPTION, guid); + + // this clears the state from Hazelcast + if (shardState != null) { + this.shardStateCache.remove(shardState.getShardInstance()); + this.shardToGuidCache.remove(shardState.getShardInstance()); + } + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/model/NodeParameterSet.java b/module/src/main/java/com/inteligr8/alfresco/asie/model/NodeParameterSet.java new file mode 100644 index 0000000..53453df --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/model/NodeParameterSet.java @@ -0,0 +1,33 @@ +package com.inteligr8.alfresco.asie.model; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +public class NodeParameterSet implements RequestParameterSet { + + private String hostname; + private int port; + + public NodeParameterSet(String hostname, int port) { + this.hostname = hostname; + this.port = port; + } + + public String getHostname() { + return hostname; + } + + public int getPort() { + return port; + } + + public InetAddress getAddress() { + try { + return InetAddress.getByName(this.hostname); + } catch (UnknownHostException uhe) { + // suppress + return null; + } + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/model/RequestParameterSet.java b/module/src/main/java/com/inteligr8/alfresco/asie/model/RequestParameterSet.java new file mode 100644 index 0000000..4c96557 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/model/RequestParameterSet.java @@ -0,0 +1,5 @@ +package com.inteligr8.alfresco.asie.model; + +public interface RequestParameterSet { + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/provider/AttributeServiceProvider.java b/module/src/main/java/com/inteligr8/alfresco/asie/provider/AttributeServiceProvider.java new file mode 100644 index 0000000..826d9fb --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/provider/AttributeServiceProvider.java @@ -0,0 +1,50 @@ +package com.inteligr8.alfresco.asie.provider; + +import org.alfresco.service.cmr.attributes.AttributeService; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +import com.inteligr8.alfresco.asie.Constants; + +@Configuration +public class AttributeServiceProvider { + + @Autowired + private ApplicationContext context; + + /** + * This allows for the selection of the primary or first AttributeService + * registered in the Spring BeanFactory. OOTB, there are multiple + * AttributeService options and you would typically want + * 'attributeService`. But an extension could have a @Primary that we + * would want to use instead. + * + * @return An AttributeService. + */ + @Bean("asieAttributeService") + @Qualifier(Constants.QUALIFIER_ASIE) + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + public AttributeService selectAttributeService() { + return this.getPrimaryOrNamed(AttributeService.class, "attributeService"); + } + + private T getPrimaryOrNamed(Class type, String beanName) { + ObjectProvider provider = this.context.getBeanProvider(type); + + // this will select the primary or if there is just one impl, that one impl + T t = provider.getIfUnique(); + if (t == null) { + // this will select the named bean; throwing exception if there is none + t = this.context.getBean(beanName, type); + } + + return t; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/provider/ObjectMapperProvider.java b/module/src/main/java/com/inteligr8/alfresco/asie/provider/ObjectMapperProvider.java new file mode 100644 index 0000000..4763e73 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/provider/ObjectMapperProvider.java @@ -0,0 +1,27 @@ +package com.inteligr8.alfresco.asie.provider; + +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.inteligr8.alfresco.asie.Constants; + +@Configuration +public class ObjectMapperProvider { + + @Bean("asieObjectMapper") + @Qualifier(Constants.QUALIFIER_ASIE) + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + public ObjectMapper createObjectMapper() { + ObjectMapper om = new ObjectMapper(); + om.registerModule(new JavaTimeModule()); + om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return om; + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieWebScript.java b/module/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieWebScript.java new file mode 100644 index 0000000..f83a7d3 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieWebScript.java @@ -0,0 +1,227 @@ +package com.inteligr8.alfresco.asie.rest; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.extensions.webscripts.AbstractWebScript; +import org.springframework.extensions.webscripts.Description.RequiredAuthentication; +import org.springframework.extensions.webscripts.WebScriptException; +import org.springframework.extensions.webscripts.WebScriptRequest; +import org.springframework.extensions.webscripts.WebScriptResponse; +import org.springframework.http.HttpStatus; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.inteligr8.alfresco.asie.Constants; +import com.inteligr8.alfresco.asie.api.CoreAdminApi; +import com.inteligr8.rs.AuthorizationFilter; +import com.inteligr8.rs.Client; +import com.inteligr8.rs.ClientCxfConfiguration; +import com.inteligr8.rs.ClientCxfImpl; + +import jakarta.ws.rs.client.ClientRequestContext; +import net.sf.acegisecurity.GrantedAuthority; + +public abstract class AbstractAsieWebScript extends AbstractWebScript implements InitializingBean { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Value("${solr.secureComms}") + private String solrSecureComms; + + @Value("${solr.port}") + private int solrPort; + + @Value("${solr.port.ssl}") + private int solrSslPort; + + @Value("${solr.sharedSecret.header}") + private String solrSharedSecretHeader; + + @Value("${solr.sharedSecret}") + private String solrSharedSecret; + + @Value("${inteligr8.asie.allowedAuthorities}") + private String authorizedAuthoritiesStr; + + @Value("${inteligr8.asie.basePath}") + private String solrBaseUrl; + + @Autowired + @Qualifier(Constants.QUALIFIER_ASIE) + private ObjectMapper objectMapper; + + private Set authorizedAuthorities; + + @Override + public void afterPropertiesSet() throws Exception { + this.authorizedAuthorities = new HashSet<>(); + String[] authorities = this.authorizedAuthoritiesStr.split(","); + for (String authority : authorities) { + authority = StringUtils.trimToNull(authority); + if (authority != null) + this.authorizedAuthorities.add(authority); + } + + if (this.authorizedAuthorities.isEmpty()) + this.logger.warn("All authenticated users will be authorized to access ASIE web scripts"); + + this.solrSharedSecret = StringUtils.trimToNull(this.solrSharedSecret); + } + + @Override + public final void execute(WebScriptRequest request, WebScriptResponse response) throws IOException { + if (RequiredAuthentication.user.equals(this.getDescription().getRequiredAuthentication())) { + if (!this.isAuthorized()) + throw new WebScriptException(HttpStatus.FORBIDDEN.value(), "You are not authorized for access this resource."); + } + + this.executeAuthorized(request, response); + } + + private boolean isAuthorized() { + if (this.authorizedAuthorities.contains(AuthenticationUtil.getFullyAuthenticatedUser())) + return true; + for (GrantedAuthority auth : AuthenticationUtil.getFullAuthentication().getAuthorities()) { + if (this.authorizedAuthorities.contains(auth.getAuthority())) + return true; + } + + return false; + } + + public abstract void executeAuthorized(WebScriptRequest request, WebScriptResponse response) throws IOException; + + public ObjectMapper getObjectMapper() { + return objectMapper; + } + + protected CoreAdminApi createApi(String hostname, int port) { + String solrBaseUrl = this.formulateSolrBaseUrl(hostname, port); + this.logger.trace("Using Solr base URL: {}", solrBaseUrl); + Client solrClient = this.createClient(solrBaseUrl); + return this.getApi(solrClient); + } + + protected CoreAdminApi getApi(Client solrClient) { + return solrClient.getApi(CoreAdminApi.class); + } + + protected int getDefaultSolrPort() { + boolean isSsl = "https".equals(this.solrSecureComms); + return isSsl ? this.solrSslPort : this.solrPort; + } + + protected String formulateSolrBaseUrl(WebScriptRequest req) { + String hostname = this.getRequiredPathParameter(req, "hostname"); + Integer port = this.getOptionalPathParameter(req, "port", Integer.class); + return this.formulateSolrBaseUrl(hostname, port); + } + + protected String formulateSolrBaseUrl(String hostname, Integer port) { + boolean isSsl = "https".equals(this.solrSecureComms); + StringBuilder baseUrl = new StringBuilder(isSsl ? "https" : "http").append("://").append(hostname); + baseUrl.append(':').append(port == null ? (isSsl ? this.solrSslPort : this.solrPort) : port); + baseUrl.append(this.solrBaseUrl); + return baseUrl.toString(); + } + + protected Client createClient(final String baseUrl) { + ClientCxfImpl client = new ClientCxfImpl(new ClientCxfConfiguration() { + @Override + public String getBaseUrl() { + return baseUrl.toString(); + } + + @Override + public AuthorizationFilter createAuthorizationFilter() { + return solrSharedSecret == null ? null : new AuthorizationFilter() { + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + logger.debug("Adding authorization headers for ASIE shared auth: {}", solrSharedSecretHeader); + requestContext.getHeaders().putSingle(solrSharedSecretHeader, solrSharedSecret); + } + }; + } + + @Override + public boolean isDefaultBusEnabled() { + return false; + } + }); + + client.register(); + return client; + } + + protected String getRequiredPathParameter(WebScriptRequest req, String pathParamName) { + String pathParamValue = this.getOptionalPathParameter(req, pathParamName); + if (pathParamValue == null) + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The '" + pathParamName + "' path parameter is required"); + return pathParamValue; + } + + protected T getRequiredPathParameter(WebScriptRequest req, String pathParamName, Class type) { + T pathParamValue = this.getOptionalPathParameter(req, pathParamName, type); + if (pathParamValue == null) + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The '" + pathParamName + "' path parameter is required"); + return pathParamValue; + } + + protected String getOptionalPathParameter(WebScriptRequest req, String pathParamName) { + return StringUtils.trimToNull(req.getServiceMatch().getTemplateVars().get(pathParamName)); + } + + protected T getOptionalPathParameter(WebScriptRequest req, String pathParamName, Class type) { + String str = StringUtils.trimToNull(req.getServiceMatch().getTemplateVars().get(pathParamName)); + return this.getOptionalParameter(req, pathParamName, str, "path element", type); + } + + protected String getOptionalQueryParameter(WebScriptRequest req, String queryParamName) { + return StringUtils.trimToNull(req.getParameter(queryParamName)); + } + + protected T getOptionalQueryParameter(WebScriptRequest req, String queryParamName, Class type) { + String str = StringUtils.trimToNull(req.getParameter(queryParamName)); + return this.getOptionalParameter(req, queryParamName, str, "query parameter", type); + } + + protected T getOptionalParameter(WebScriptRequest req, String paramName, String paramValue, String paramTypeDisplay, Class type) { + if (paramValue == null) + return null; + if (type.equals(String.class)) + return type.cast(paramValue); + + try { + try { + Constructor constructor = type.getConstructor(String.class); + return constructor.newInstance(paramValue); + } catch (NoSuchMethodException nsme) { + Method method = type.getDeclaredMethod("valueOf", String.class); + @SuppressWarnings("unchecked") + T t = (T) method.invoke(null, paramValue); + return t; + } + } catch (InvocationTargetException ite) { + if (ite.getTargetException() instanceof NumberFormatException) { + throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The `" + paramName + "` " + paramTypeDisplay + " '" + paramValue + "' must be a number"); + } else { + throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected issue occurred", ite); + } + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException e) { + throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected issue occurred", e); + } + } + +} diff --git a/module/src/main/java/com/inteligr8/alfresco/asie/service/SolrShardHashService.java b/module/src/main/java/com/inteligr8/alfresco/asie/service/SolrShardHashService.java new file mode 100644 index 0000000..cda8353 --- /dev/null +++ b/module/src/main/java/com/inteligr8/alfresco/asie/service/SolrShardHashService.java @@ -0,0 +1,27 @@ +package com.inteligr8.alfresco.asie.service; + +import java.nio.charset.Charset; + +import org.apache.commons.codec.digest.MurmurHash3; +import org.springframework.stereotype.Component; + +@Component +public class SolrShardHashService { + + private final Charset charset = Charset.forName("utf-8"); + + public int hash(Object obj, int shardCount) { + String str = obj.toString(); + byte[] bytes = str.getBytes(this.charset); + + // From ASIE source: + // Math.abs(Hash.murmurhash3_x86_32(shardBy, 0, shardBy.length(), 66)) % shardCount + + // Using Apache Commons Codec: + MurmurHash3.IncrementalHash32x86 hash = new MurmurHash3.IncrementalHash32x86(); + hash.start(66); + hash.add(bytes, 0, bytes.length); + return Math.abs(hash.end()) % shardCount; + } + +} diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/backupNode.get.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/backupNode.get.desc.xml new file mode 100644 index 0000000..b7145c6 --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/backupNode.get.desc.xml @@ -0,0 +1,49 @@ + + + + Retrieve Backup ASIE Node for ASIE Shard + Inteligr8 ASIE + Retrieve a reference to the ASIE node that should be used for the backup of the specified ASIE shard registered with ACS.

+

The following path parameters are expected:

+
+
shardSet
+
A shard method combined with its distinguishing properties; + methods: MOD_ACL_ID, ACL_ID, DB_ID, DB_ID_RANGE, DATE, PROPERTY, EXPLICIT_ID; + e.g. PROPERTY;key:cm:created;regex:^d{4} or DB_ID
+
shardId
+
A number starting at 1
+
+

The response will have the following format:

+
"hostname:port/baseUrl"
+

This is meant to help determine which shard should be backed up. + The node is computed based on the lead shard on the first request. + That value is cached until a reques isn't made for at least a configurable amount of time.

+

The following status codes should be expected:

+
+
200
+
OK
+
204
+
No ASIE node/shard information available
+
400
+
The path parameters are invalid
+
404
+
The specified shard set or shard ID could not be found
+
+ ]]>
+ + + /inteligr8/asie/shard/{shardSet}/{shardId}/backup + any + + + none + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/leadNode.get.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/leadNode.get.desc.xml new file mode 100644 index 0000000..1008ff7 --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/leadNode.get.desc.xml @@ -0,0 +1,45 @@ + + + + Retrieve Lead ASIE Node for ASIE Shard + Inteligr8 ASIE + Retrieve a reference to the most current/up-to-date ASIE node for the specified ASIE shard registered with ACS.

+

The following path parameters are expected:

+
+
shardSet
+
A shard method combined with its distinguishing properties; + methods: MOD_ACL_ID, ACL_ID, DB_ID, DB_ID_RANGE, DATE, PROPERTY, EXPLICIT_ID; + e.g. PROPERTY;key:cm:created;regex:^d{4} or DB_ID
+
shardId
+
A number starting at 1
+
+

The response will have the following format:

+
"hostname:port/baseUrl"
+
+
200
+
OK
+
204
+
No ASIE node/shard information available
+
400
+
The path parameters are invalid
+
404
+
The specified shard set or shard ID could not be found
+
+ ]]>
+ + + /inteligr8/asie/shard/{shardSet}/{shardId}/lead + any + + + none + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.delete.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.delete.desc.xml new file mode 100644 index 0000000..f1ece9d --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.delete.desc.xml @@ -0,0 +1,40 @@ + + + + Clears ASIE Node from Registry + Inteligr8 ASIE + Clears meta-data about all ASIE shards on a single ASIE node registered with ACS. + This call will attempt to unload all shards on the ASIE node to prevent automatic registration on its next index attempt. + If that disablement fails, this call will continue to unregister all shards on the node from ACS; however, they will automatically register again on its next indexing attempt.

+

The following path parameters are expected:

+
+
nodeEndpoint
+
A hostname or hostname:port for the ASIE node
+
+

The following status codes should be expected:

+
+
200
+
OK
+
400
+
The path parameters are invalid
+
404
+
The specified ASIE node could not be found
+
+ ]]>
+ + + /inteligr8/asie/node/{nodeEndpoint}/shards + /inteligr8/asie/node/{nodeEndpoint} + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.get.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.get.desc.xml new file mode 100644 index 0000000..8cc7b8a --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.get.desc.xml @@ -0,0 +1,69 @@ + + + + Retrieves ASIE Node Information/Status + Inteligr8 ASIE + Retrieves meta-data about all shards on a single ASIE node as registred with ACS.

+

The following query parameter is supported:

+
+
nodeEndpoint
+
A hostname or hostname:port for the ASIE node; dots are not allowed, you may use _ (underscore) instead
+
sampleType
+
A sample hash type; Sample hash types: PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek
+
+

The response will have the following format:

+
{
+			"url": string,
+			"hostname": string,
+			"shards": {
+				"string": {  // shard ID as string 
+					"id": number,
+					"txsCompleted": number,
+					"txsRemaining": number,
+					"latestTx": "datetime",
+					"shardSet": {
+						"id": "string",  // generated shard set ID
+						"method": "string",
+						"methodDetail": "string",
+						"hasContent": boolean,
+						"shardCount": number,
+						"shardHashSamples": {
+							"string": [   // shard ID as string
+								"string": // year as string
+							],
+							...
+						}
+					}
+				},
+				...
+			]
+		}
+

The following status codes should be expected:

+
+
200
+
OK
+
204
+
No ASIE shard information available
+
400
+
The path or query parameters are invalid
+
404
+
The specified ASIE node instance could not be found
+
+ ]]>
+ + + /inteligr8/asie/node/{nodeEndpoint}?sampleHashType={sampleHashType?} + any + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.post.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.post.desc.xml new file mode 100644 index 0000000..5e3463e --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/node.post.desc.xml @@ -0,0 +1,42 @@ + + + + Adds ASIE Node to Registry + Inteligr8 ASIE + Loads all previously registered ASIE shards on a single ASIE node, which will eventually register with ACS.

+

The following path parameters are expected:

+
+
nodeEndpoint
+
A hostname or hostname:port for the ASIE node
+
coreName
+
A core name to restore in addition to known cores
+
shardRange
+
A shard range restore in addition to known shards (e.g. 0-7)
+
+

The following status codes should be expected:

+
+
200
+
OK
+
400
+
The path parameters are invalid
+
404
+
The specified ASIE node was not previously registered
+
+ ]]>
+ + + /inteligr8/asie/node/{nodeEndpoint}/shards?coreName={coreName?}&shardRange={shardRange?}&template={template?}&shardCount={shardCount?}&nodeId={nodeId?}&nodeCount={nodeCount?} + /inteligr8/asie/node/{nodeEndpoint}?coreName={coreName?}&shardRange={shardRange?}&template={template?}&shardCount={shardCount?}&nodeId={nodeId?}&nodeCount={nodeCount?} + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodeShard.delete.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodeShard.delete.desc.xml new file mode 100644 index 0000000..37a7673 --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodeShard.delete.desc.xml @@ -0,0 +1,45 @@ + + + + Clears ASIE Node/Shard from Registry + Inteligr8 ASIE + Clears meta-data about a single ASIE shard on a single ASIE node registred with ACS. + This call will attempt to disable further indexing by the ASIE node of only that shard to prevent automatic registration on its next index attempt. + If that disablement fails, this call will continue to unregister the node/shard from ACS; however, it will automatically register again on its next indexing attempt.

+

The following path parameters are expected:

+
+
nodeEndpoint
+
A hostname or hostname:port for the ASIE node
+
shardSet
+
A shard method combined with its distinguishing properties; + methods: MOD_ACL_ID, ACL_ID, DB_ID, DB_ID_RANGE, DATE, PROPERTY, EXPLICIT_ID; + e.g. PROPERTY;key:cm:created;regex:^d{4} or DB_ID
+
shardId
+
A number starting at 1
+
+

The following status codes should be expected:

+
+
200
+
OK
+
400
+
The path parameters are invalid
+
404
+
The specified ASIE node, shard set, or shard ID could not be found
+
+ ]]>
+ + + /inteligr8/asie/node/{nodeEndpoint}/shard/{shardSet}/{shardId} + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodeShard.post.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodeShard.post.desc.xml new file mode 100644 index 0000000..a3b3348 --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodeShard.post.desc.xml @@ -0,0 +1,41 @@ + + + + Adds ASIE Node to Registry + Inteligr8 ASIE + Loads an ASIE shard on a single ASIE node, which will eventually register with ACS.

+

The following path parameters are expected:

+
+
nodeEndpoint
+
A hostname or hostname:port for the ASIE node
+
shardCore
+
A core name (prefix) for the ASIE shard (e.g. alfresco)
+
shardId
+
A numeric shard ID for the ASIE shard (e.g. 0)
+
+

The following status codes should be expected:

+
+
200
+
OK
+
400
+
The path parameters are invalid
+
404
+
The specified ASIE node could not be found
+
+ ]]>
+ + + /inteligr8/asie/node/{nodeEndpoint}/shard/{shardCore}/{shardId} + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodes.get.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodes.get.desc.xml new file mode 100644 index 0000000..c3501e5 --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/nodes.get.desc.xml @@ -0,0 +1,65 @@ + + + + Retrieve ASIE Node Information/Status + Inteligr8 ASIE + Retrieve meta-data about the ASIE nodes and their shards registered with ACS.

+

The following query parameter is supported:

+
+
sampleHashType
+
A sample hash type; Sample hash types: PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek
+
+

The response will have the following format:

+
{
+			"nodes": {
+				"string": {          // node URL
+					"id": "string",
+					"shards": {
+						"string": {  // shard ID as string 
+							"id": number,
+							"txsCompleted": number,
+							"txsRemaining": number,
+							"latestTx": "datetime",
+							"shardSet": {
+								"methodSpec": "string",  // generated shard set ID
+								"shardCount": number,
+								"shardHashSamples": {
+									"string": [   // shard ID as string
+										"string": // year as string
+									],
+									...
+								}
+							}
+						},
+						...
+					}
+				}
+			}
+		}
+

The following status codes should be expected:

+
+
200
+
OK
+
204
+
No ASIE shard information available
+
400
+
The query parameter is invalid
+
+ ]]>
+ + + /inteligr8/asie/nodes?sampleHashType={sampleHashType?} + any + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/propertyHashShards.get.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/propertyHashShards.get.desc.xml new file mode 100644 index 0000000..3274f7a --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/propertyHashShards.get.desc.xml @@ -0,0 +1,72 @@ + + + + Retrieve ASIE Shard Hash Status + Inteligr8 ASIE + Retrieve meta-data about the ASIE shard hashes of the `PROPERTY` method registered with ACS. + Sample hashes are performed given the query parameters, which allows for reverse hashes of those samples in the response. + A min/max or an enumerated list of values must also be provided.

+

The following path and query parameters is expected or supported:

+
+
sampleHashType
+
PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek
+
shards
+
The total number of shards expected
+
min
+
For numerical hashes, the minimum of an integer range to compute sample hashes
+
max
+
For numerical hashes, the maximum of an integer range to compute sample hashes
+
values
+
For any hashes, a comma-delimited enumeration of values to compute sample hashes
+
+

The response will have the following format:

+
[
+			{
+				"methodSpec": "string",  // generated shard set ID
+				"shardCount": number
+				"shards": {
+					"string": {  // hash
+						"id": number,
+						"txsCompleted": number,
+						"txsRemaining": number,
+						"latestTx": "datetime",
+						"nodes": {
+							"string": {    // ASIE node URL
+								"url": "string",
+								"hostname": "string"
+							},
+							...
+						}
+					},
+					...
+				}
+			},
+			...
+		]
+

The following status codes should be expected:

+
+
200
+
OK
+
204
+
No ASIE PROPERTY shard information available
+
400
+
The path or query parameters are invalid
+
+ ]]>
+ + + /inteligr8/asie/shards/byHash/{sampleHashType}/{shards}?min={min?}&max={max?}&values={values?} + any + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/registry.delete.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/registry.delete.desc.xml new file mode 100644 index 0000000..721c459 --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/registry.delete.desc.xml @@ -0,0 +1,32 @@ + + + + Clear ASIE Node/Shard Registry + Inteligr8 ASIE + Clears all meta-data about the ASIE shard nodes and instances registred with ACS. + This is more extreme than the OOTB purge capability, which sometimes errors rather than purging. + This will avoid those errors, clearing all shard registration information regardless of how mess up they may be.

+

If there are no shards, this will effectively do nothing.

+

The following status codes should be expected:

+
+
200
+
OK
+
+ ]]>
+ + + /inteligr8/asie/shards + /inteligr8/asie/nodes + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/sampleHashes.get.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/sampleHashes.get.desc.xml new file mode 100644 index 0000000..87121d0 --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/sampleHashes.get.desc.xml @@ -0,0 +1,58 @@ + + + + Compute ASIE Sample Hash Table + Inteligr8 ASIE + Compute sample hashes. + Sample hashes are performed given the query parameters, which allows for reverse hashes of those samples in the response. + A min/max or an enumerated list of values must also be provided.

+

The following path and query parameters is expected or supported:

+
+
shards
+
The total number of shards expected
+
min
+
For numerical hashes, the minimum of an integer range to compute sample hashes
+
max
+
For numerical hashes, the maximum of an integer range to compute sample hashes
+
values
+
For any hashes, a comma-delimited enumeration of values to compute sample hashes
+
+

The response will have the following format:

+
{
+			"forward": {
+				"string": number,   // sample value to shard ID
+				...
+			},
+			"reverse": {
+				"number": [   // shard ID, starting at 0
+					"string", // sample value
+					...
+				],
+				...
+			}
+		}
+

The following status codes should be expected:

+
+
200
+
OK
+
400
+
The path or query parameters are invalid
+
+ ]]>
+ + + /inteligr8/asie/hash/{shards}?min={min?}&max={max?}&values={values?} + any + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/shard.get.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/shard.get.desc.xml new file mode 100644 index 0000000..b595269 --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/shard.get.desc.xml @@ -0,0 +1,73 @@ + + + + Retrieve ASIE Shard Information/Status + Inteligr8 ASIE + Retrieve meta-data about the specified ASIE shard registered with ACS.

+

The following path and query parameters are expected or supported:

+
+
shardSet
+
A shard method combined with its distinguishing properties; + methods: MOD_ACL_ID, ACL_ID, DB_ID, DB_ID_RANGE, DATE, PROPERTY, EXPLICIT_ID; + e.g. PROPERTY;key:cm:created;regex:^d{4} or DB_ID
+
shardId
+
A number starting at 1
+
sampleHashType
+
A sample hash type; Sample hash types: PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek
+
+

The response will have the following format:

+
{
+			"id": number,
+			"txsCompleted": number,
+			"txsRemaining": number,
+			"latestTx": "datetime",
+			"nodes": {
+				"string": {    // ASIE node URL
+					"url": "string",
+					"hostname": "string"
+				},
+				...
+			},
+			"shardSet": {
+				"id": "string",  // generated shard set ID
+				"method": "string",
+				"methodDetail": "string",
+				"hasContent": boolean,
+				"shardCount": number,
+				"shardHashSamples": {
+					"string": [   // shard ID as string
+						"string": // year as string
+					],
+					...
+				}
+			}
+		}
+

The following status codes should be expected:

+
+
200
+
OK
+
204
+
No ASIE shard information available
+
400
+
The parameters are invalid
+
404
+
The specified shard set or shard ID could not be found
+
+ ]]>
+ + + /inteligr8/asie/shard/{shardSet}/{shardId}?includeSampleHashes={includeSampleHashes?} + any + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/shards.get.desc.xml b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/shards.get.desc.xml new file mode 100644 index 0000000..b3101ce --- /dev/null +++ b/module/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/enterprise/shards.get.desc.xml @@ -0,0 +1,68 @@ + + + + Retrieve ASIE Shard Information/Status + Inteligr8 ASIE + Retrieve meta-data about all the ASIE shards registered with ACS.

+

The following query parameter is supported:

+
+
sampleHashType
+
A sample hash type; Sample hash types: PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek
+
+

The response will have the following format:

+
{
+			"string": {          // generated shard set ID
+				"methodSpec": "string", // generated shard set ID
+				"shardCount": number
+				"shards": {
+					"string": {  // shard ID as string 
+						"id": number,
+						"txsCompleted": number,
+						"txsRemaining": number,
+						"latestTx": "datetime",
+						"nodes": {
+							"string": {    // ASIE node URL
+								"id": "string",
+								"txsCompleted": number,
+								"latestTx": "datetime",
+							},
+							...
+						}
+					},
+					...
+				},
+				"shardHashSamples": {
+					"string": [   // shard ID as string
+						"string": // year as string
+					],
+					...
+				}
+			}
+		}
+

The following status codes should be expected:

+
+
200
+
OK
+
204
+
No ASIE shard information available
+
400
+
The query parameter is invalid
+
+ ]]>
+ + + /inteligr8/asie/shards?sampleHashType={sampleHashType?} + any + + + admin + + + + false + false + + +
\ No newline at end of file diff --git a/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/alfresco-global.properties b/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/alfresco-global.properties new file mode 100644 index 0000000..a29c9b1 --- /dev/null +++ b/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/alfresco-global.properties @@ -0,0 +1,8 @@ + +# defaulting to 3 days = 60 * 24 * 3 = 4320 +inteligr8.asie.backup.persistTimeMinutes=4320 + +inteligr8.asie.allowedAuthorities=ALFRESCO_ADMINISTRATORS + +# same as solr.baseUrl, but that property is private to the Search subsystem +inteligr8.asie.basePath=/solr diff --git a/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/log4j2.properties b/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/log4j2.properties new file mode 100644 index 0000000..6c345f1 --- /dev/null +++ b/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/log4j2.properties @@ -0,0 +1,3 @@ + +logger.inteligr8-asie.name=com.inteligr8.alfresco.asie +logger.inteligr8-asie.level=INFO diff --git a/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/module-context.xml b/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/module-context.xml new file mode 100644 index 0000000..769f7e9 --- /dev/null +++ b/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/module-context.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + org.alfresco.repo.index.shard.ShardRegistry + + + + + diff --git a/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/module.properties b/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/module.properties new file mode 100644 index 0000000..2d638d2 --- /dev/null +++ b/module/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-platform-module/module.properties @@ -0,0 +1,10 @@ +module.id=com_inteligr8_alfresco_${project.artifactId} +module.aliases= +module.title=${project.name} +module.description=${project.description} +module.version=${module.version} + +module.repo.version.min=23.0 + +# this is creating all sorts of problems; probably because of the non-standard versioning +module.depends.com.inteligr8.alfresco.cxf-jaxrs-platform-module=* diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..54fb4c6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,54 @@ + + 4.0.0 + + com.inteligr8.alfresco + asie-platform-module-parent + 1.0-SNAPSHOT + pom + + ASIE Platform Module Parent + + + UTF-8 + 11 + 11 + 11 + + + + + + + + maven-compiler-plugin + 3.13.0 + + + + maven-site-plugin + 3.12.1 + + + + maven-dependency-plugin + 3.8.0 + + + + + + + solr-api + asie-api + module + + + + + alfresco-private + https://artifacts.alfresco.com/nexus/content/groups/private + + + diff --git a/solr-api/.gitignore b/solr-api/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/solr-api/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/solr-api/pom.xml b/solr-api/pom.xml new file mode 100644 index 0000000..ca45aa1 --- /dev/null +++ b/solr-api/pom.xml @@ -0,0 +1,79 @@ + + 4.0.0 + + com.inteligr8 + solr-api + 1.0-SNAPSHOT-solr6 + jar + + Apache Solr JAX-RS API + + + UTF-8 + 11 + 11 + 11 + + 2.18.0 + + + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + ${jackson.version} + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + com.fasterxml.jackson.jakarta.rs + jackson-jakarta-rs-json-provider + ${jackson.version} + + + + + org.junit.jupiter + junit-jupiter-api + 5.11.2 + test + + + + + + + + + maven-compiler-plugin + 3.13.0 + + + + maven-site-plugin + 3.12.1 + + + + maven-dependency-plugin + 3.7.1 + + + + + diff --git a/solr-api/src/main/java/com/inteligr8/solr/api/CollectionAdminApi.java b/solr-api/src/main/java/com/inteligr8/solr/api/CollectionAdminApi.java new file mode 100644 index 0000000..96af2f3 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/api/CollectionAdminApi.java @@ -0,0 +1,26 @@ +package com.inteligr8.solr.api; + +import com.inteligr8.solr.model.ActionResponse; +import com.inteligr8.solr.model.ResponseAction; +import com.inteligr8.solr.model.collection.AliasesResponse; +import com.inteligr8.solr.model.collection.GetAliasesRequest; +import com.inteligr8.solr.model.core.ReloadRequest; + +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("admin/collections") +public interface CollectionAdminApi { + + @GET + @Produces(MediaType.APPLICATION_JSON) + ActionResponse reload(@BeanParam ReloadRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + AliasesResponse getAliases(@BeanParam GetAliasesRequest request); + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/api/CoreAdminApi.java b/solr-api/src/main/java/com/inteligr8/solr/api/CoreAdminApi.java new file mode 100644 index 0000000..2ad2a0c --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/api/CoreAdminApi.java @@ -0,0 +1,51 @@ +package com.inteligr8.solr.api; + +import com.inteligr8.solr.model.EmptyResponse; +import com.inteligr8.solr.model.core.CreateRequest; +import com.inteligr8.solr.model.core.ReloadRequest; +import com.inteligr8.solr.model.core.RenameRequest; +import com.inteligr8.solr.model.core.RequestStatusRequest; +import com.inteligr8.solr.model.core.RequestStatusResponse; +import com.inteligr8.solr.model.core.StatusRequest; +import com.inteligr8.solr.model.core.StatusResponse; +import com.inteligr8.solr.model.core.SwapRequest; +import com.inteligr8.solr.model.core.UnloadRequest; + +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("admin/cores") +public interface CoreAdminApi { + + @GET + @Produces(MediaType.APPLICATION_JSON) + StatusResponse getStatus(@BeanParam StatusRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + EmptyResponse create(@BeanParam CreateRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + EmptyResponse reload(@BeanParam ReloadRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + EmptyResponse rename(@BeanParam RenameRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + EmptyResponse swap(@BeanParam SwapRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + EmptyResponse unload(@BeanParam UnloadRequest request); + + @GET + @Produces(MediaType.APPLICATION_JSON) + RequestStatusResponse getRequestStatus(@BeanParam RequestStatusRequest request); + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/ActionResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/ActionResponse.java new file mode 100644 index 0000000..b78ffbc --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/ActionResponse.java @@ -0,0 +1,21 @@ +package com.inteligr8.solr.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ActionResponse extends BaseResponse { + + @JsonProperty(access = Access.READ_ONLY) + private T action; + + public T getAction() { + return action; + } + + protected void setAction(T action) { + this.action = action; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/BaseResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/BaseResponse.java new file mode 100644 index 0000000..1e40276 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/BaseResponse.java @@ -0,0 +1,21 @@ +package com.inteligr8.solr.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class BaseResponse { + + @JsonProperty(access = Access.READ_ONLY) + private ResponseHeader responseHeader; + + public ResponseHeader getResponseHeader() { + return responseHeader; + } + + protected void setResponseHeader(ResponseHeader responseHeader) { + this.responseHeader = responseHeader; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/CoreMetadata.java b/solr-api/src/main/java/com/inteligr8/solr/model/CoreMetadata.java new file mode 100644 index 0000000..0e0d4b3 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/CoreMetadata.java @@ -0,0 +1,67 @@ +package com.inteligr8.solr.model; + +import java.time.OffsetDateTime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CoreMetadata { + + @JsonProperty + private String name; + + @JsonProperty("instanceDir") + private String instancePath; + + @JsonProperty("dataDir") + private String dataPath; + + @JsonProperty("config") + private String configFilename; + + @JsonProperty("schema") + private String schemaFilename; + + @JsonProperty("startTime") + private OffsetDateTime startDateTime; + + @JsonProperty + private Long uptime; + + @JsonProperty + private IndexMetadata index; + + public String getName() { + return name; + } + + public String getInstancePath() { + return instancePath; + } + + public String getDataPath() { + return dataPath; + } + + public String getConfigFilename() { + return configFilename; + } + + public String getSchemaFilename() { + return schemaFilename; + } + + public OffsetDateTime getStartDateTime() { + return startDateTime; + } + + public Long getUptime() { + return uptime; + } + + public IndexMetadata getIndex() { + return index; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/EmptyResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/EmptyResponse.java new file mode 100644 index 0000000..a486377 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/EmptyResponse.java @@ -0,0 +1,8 @@ +package com.inteligr8.solr.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class EmptyResponse extends BaseResponse { + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/ErrorMetadata.java b/solr-api/src/main/java/com/inteligr8/solr/model/ErrorMetadata.java new file mode 100644 index 0000000..767ab94 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/ErrorMetadata.java @@ -0,0 +1,39 @@ +package com.inteligr8.solr.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ErrorMetadata { + + @JsonProperty + private List metadata; + + @JsonProperty("msg") + private String message; + + @JsonProperty + private Integer code; + + @JsonProperty("trace") + private String stacktrace; + + public List getMetadata() { + return metadata; + } + + public String getMessage() { + return message; + } + + public String getStacktrace() { + return stacktrace; + } + + public Integer getCode() { + return code; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/ExceptionResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/ExceptionResponse.java new file mode 100644 index 0000000..d6594f9 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/ExceptionResponse.java @@ -0,0 +1,16 @@ +package com.inteligr8.solr.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ExceptionResponse extends BaseResponse { + + @JsonProperty + private ErrorMetadata error; + + public ErrorMetadata getError() { + return error; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/IndexMetadata.java b/solr-api/src/main/java/com/inteligr8/solr/model/IndexMetadata.java new file mode 100644 index 0000000..475acb9 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/IndexMetadata.java @@ -0,0 +1,109 @@ +package com.inteligr8.solr.model; + +import java.time.OffsetDateTime; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class IndexMetadata { + + @JsonProperty + private Long numDocs; + + @JsonProperty + private Long maxDoc; + + @JsonProperty + private Long deletedDocs; + + @JsonProperty + private Long indexHeapUsageBytes; + + @JsonProperty + private Integer version; + + @JsonProperty + private Integer segmentCount; + + @JsonProperty + private Boolean current; + + @JsonProperty + private Boolean hasDeletions; + + @JsonProperty("directory") + private String directoryClassSpec; + + @JsonProperty("segmentsFile") + private String segmentsFilename; + + @JsonProperty + private Long segmentsFileSizeInBytes; + + @JsonProperty("lastModified") + private OffsetDateTime lastModifiedDateTime; + + @JsonProperty + private Long sizeInBytes; + + @JsonProperty("size") + private String sizeInText; + + public Long getNumDocs() { + return numDocs; + } + + public Long getMaxDoc() { + return maxDoc; + } + + public Long getDeletedDocs() { + return deletedDocs; + } + + public Long getIndexHeapUsageBytes() { + return indexHeapUsageBytes; + } + + public Integer getVersion() { + return version; + } + + public Integer getSegmentCount() { + return segmentCount; + } + + public Boolean getCurrent() { + return current; + } + + public Boolean getHasDeletions() { + return hasDeletions; + } + + public String getDirectoryClassSpec() { + return directoryClassSpec; + } + + public String getSegmentsFilename() { + return segmentsFilename; + } + + public Long getSegmentsFileSizeInBytes() { + return segmentsFileSizeInBytes; + } + + public OffsetDateTime getLastModifiedDateTime() { + return lastModifiedDateTime; + } + + public Long getSizeInBytes() { + return sizeInBytes; + } + + public String getSizeInText() { + return sizeInText; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/JsonFormattedResponseRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/JsonFormattedResponseRequest.java new file mode 100644 index 0000000..01acac0 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/JsonFormattedResponseRequest.java @@ -0,0 +1,35 @@ +package com.inteligr8.solr.model; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class JsonFormattedResponseRequest> { + + private static final String FORMAT = "json"; + + @QueryParam("wt") + @DefaultValue(FORMAT) + @Nonnull + public String responseType = FORMAT; + + public String getResponseType() { + return responseType; + } + + public void setResponseType(String responseType) { + this.responseType = responseType; + } + + public T withResponseType(String responseType) { + this.responseType = responseType; + return this.getThis(); + } + + @SuppressWarnings("unchecked") + protected T getThis() { + T t = (T) this; + return t; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/ResponseAction.java b/solr-api/src/main/java/com/inteligr8/solr/model/ResponseAction.java new file mode 100644 index 0000000..d5d1456 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/ResponseAction.java @@ -0,0 +1,41 @@ +package com.inteligr8.solr.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ResponseAction { + + public enum Status { + @JsonProperty("success") + Success, + @JsonProperty("scheduled") + Scheduled, + @JsonProperty("error") + Error, + } + + @JsonProperty(access = Access.READ_ONLY) + private Status status; + + @JsonProperty(access = Access.READ_ONLY) + private String errorMessage; + + public Status getStatus() { + return status; + } + + protected void setStatus(Status status) { + this.status = status; + } + + public String getErrorMessage() { + return errorMessage; + } + + protected void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/ResponseHeader.java b/solr-api/src/main/java/com/inteligr8/solr/model/ResponseHeader.java new file mode 100644 index 0000000..3d76474 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/ResponseHeader.java @@ -0,0 +1,32 @@ +package com.inteligr8.solr.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ResponseHeader { + + @JsonProperty(value = "QTime", access = Access.READ_ONLY) + private long executionTimeInMilliseconds; + + @JsonProperty(access = Access.READ_ONLY) + private int status; + + public long getExecutionTimeInMilliseconds() { + return executionTimeInMilliseconds; + } + + protected void setExecutionTimeInMilliseconds(long executionTimeInMilliseconds) { + this.executionTimeInMilliseconds = executionTimeInMilliseconds; + } + + public int getStatus() { + return status; + } + + protected void setStatus(int status) { + this.status = status; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/TransactionResponseStatus.java b/solr-api/src/main/java/com/inteligr8/solr/model/TransactionResponseStatus.java new file mode 100644 index 0000000..361c756 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/TransactionResponseStatus.java @@ -0,0 +1,45 @@ +package com.inteligr8.solr.model; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class TransactionResponseStatus { + + @JsonProperty(value = "txInIndexNotInDb", access = Access.READ_ONLY) + private Map transactionIdToDeletedNodeCount; + + @JsonProperty(value = "duplicatedTx", access = Access.READ_ONLY) + private Map transactionIdToDuplicateNodeCount; + + @JsonProperty(value = "missingTx", access = Access.READ_ONLY) + private Map transactionIdToUnindexNodeCount; + + public Map getTransactionIdToDeletedNodeCount() { + return transactionIdToDeletedNodeCount; + } + + protected void setTransactionIdToDeletedNodeCount(Map transactionIdToDeletedNodeCount) { + this.transactionIdToDeletedNodeCount = transactionIdToDeletedNodeCount; + } + + public Map getTransactionIdToDuplicateNodeCount() { + return transactionIdToDuplicateNodeCount; + } + + protected void setTransactionIdToDuplicateNodeCount(Map transactionIdToDuplicateNodeCount) { + this.transactionIdToDuplicateNodeCount = transactionIdToDuplicateNodeCount; + } + + public Map getTransactionIdToUnindexNodeCount() { + return transactionIdToUnindexNodeCount; + } + + protected void setTransactionIdToUnindexNodeCount(Map transactionIdToUnindexNodeCount) { + this.transactionIdToUnindexNodeCount = transactionIdToUnindexNodeCount; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/collection/AliasesResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/collection/AliasesResponse.java new file mode 100644 index 0000000..7d132d1 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/collection/AliasesResponse.java @@ -0,0 +1,24 @@ +package com.inteligr8.solr.model.collection; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonProperty.Access; +import com.inteligr8.solr.model.BaseResponse; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AliasesResponse extends BaseResponse { + + @JsonProperty(access = Access.READ_ONLY) + private Map aliases; + + public Map getAliases() { + return aliases; + } + + protected void setAliases(Map aliases) { + this.aliases = aliases; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/collection/GetAliasesRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/collection/GetAliasesRequest.java new file mode 100644 index 0000000..783a66a --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/collection/GetAliasesRequest.java @@ -0,0 +1,26 @@ +package com.inteligr8.solr.model.collection; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class GetAliasesRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "LISTALIASES"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + public String getAction() { + return action; + } + + protected void setAction(String action) { + this.action = action; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/collection/ReloadRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/collection/ReloadRequest.java new file mode 100644 index 0000000..7758d23 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/collection/ReloadRequest.java @@ -0,0 +1,59 @@ +package com.inteligr8.solr.model.collection; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class ReloadRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "RELOAD"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("name") + @Nonnull + private String name; + + @QueryParam("async") + private String requestId; + + public String getAction() { + return action; + } + + protected void setAction(String action) { + this.action = action; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ReloadRequest withName(String name) { + this.name = name; + return this; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public ReloadRequest withRequestId(String requestId) { + this.requestId = requestId; + return this; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/CreateRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/CreateRequest.java new file mode 100644 index 0000000..0efc503 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/CreateRequest.java @@ -0,0 +1,171 @@ +package com.inteligr8.solr.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class CreateRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "CREATE"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("name") + @Nonnull + private String core; + + @QueryParam("instanceDir") + private String configDirectory; + + @QueryParam("config") + private String configFilename; + + @QueryParam("schema") + private String schemaFilename; + + @QueryParam("dataDir") + private String dataDirectory; + + @QueryParam("configSet") + private String configSet; + + @QueryParam("collection") + private String collection; + + @QueryParam("shard") + private int shard; + + @QueryParam("async") + private String requestId; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public CreateRequest withCore(String core) { + this.core = core; + return this; + } + + public String getConfigDirectory() { + return configDirectory; + } + + public void setConfigDirectory(String configDirectory) { + this.configDirectory = configDirectory; + } + + public CreateRequest withConfigDirectory(String configDirectory) { + this.configDirectory = configDirectory; + return this; + } + + public String getConfigFilename() { + return configFilename; + } + + public void setConfigFilename(String configFilename) { + this.configFilename = configFilename; + } + + public CreateRequest withConfigFilename(String configFilename) { + this.configFilename = configFilename; + return this; + } + + public String getSchemaFilename() { + return schemaFilename; + } + + public void setSchemaFilename(String schemaFilename) { + this.schemaFilename = schemaFilename; + } + + public CreateRequest withSchemaFilename(String schemaFilename) { + this.schemaFilename = schemaFilename; + return this; + } + + public String getDataDirectory() { + return dataDirectory; + } + + public void setDataDirectory(String dataDirectory) { + this.dataDirectory = dataDirectory; + } + + public CreateRequest withDataDirectory(String dataDirectory) { + this.dataDirectory = dataDirectory; + return this; + } + + public String getConfigSet() { + return configSet; + } + + public void setConfigSet(String configSet) { + this.configSet = configSet; + } + + public CreateRequest withConfigSet(String configSet) { + this.configSet = configSet; + return this; + } + + public String getCollection() { + return collection; + } + + public void setCollection(String collection) { + this.collection = collection; + } + + public CreateRequest withCollection(String collection) { + this.collection = collection; + return this; + } + + public int getShard() { + return shard; + } + + public void setShard(int shard) { + this.shard = shard; + } + + public CreateRequest withShard(int shard) { + this.shard = shard; + return this; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public CreateRequest withRequestId(String requestId) { + this.requestId = requestId; + return this; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/ReloadRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/ReloadRequest.java new file mode 100644 index 0000000..ff4a609 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/ReloadRequest.java @@ -0,0 +1,43 @@ +package com.inteligr8.solr.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class ReloadRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "RELOAD"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + @Nonnull + private String core; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public ReloadRequest withCore(String core) { + this.core = core; + return this; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/RenameRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/RenameRequest.java new file mode 100644 index 0000000..de685de --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/RenameRequest.java @@ -0,0 +1,76 @@ +package com.inteligr8.solr.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class RenameRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "RENAME"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + @Nonnull + private String core; + + @QueryParam("other") + @Nonnull + private String newCore; + + @QueryParam("async") + private String requestId; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public RenameRequest withCore(String core) { + this.core = core; + return this; + } + + public String getNewCore() { + return newCore; + } + + public void setNewCore(String newCore) { + this.newCore = newCore; + } + + public RenameRequest withNewCore(String newCore) { + this.newCore = newCore; + return this; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public RenameRequest withRequestId(String requestId) { + this.requestId = requestId; + return this; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/RequestStatusRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/RequestStatusRequest.java new file mode 100644 index 0000000..721f939 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/RequestStatusRequest.java @@ -0,0 +1,43 @@ +package com.inteligr8.solr.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class RequestStatusRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "REQUESTSTATUS"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("requestid") + @Nonnull + private String requestId; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public RequestStatusRequest withRequestId(String requestId) { + this.requestId = requestId; + return this; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/RequestStatusResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/RequestStatusResponse.java new file mode 100644 index 0000000..78e2f66 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/RequestStatusResponse.java @@ -0,0 +1,9 @@ +package com.inteligr8.solr.model.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.inteligr8.solr.model.BaseResponse; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class RequestStatusResponse extends BaseResponse { + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/Status.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/Status.java new file mode 100644 index 0000000..12d50ff --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/Status.java @@ -0,0 +1,26 @@ +package com.inteligr8.solr.model.core; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.inteligr8.solr.model.CoreMetadata; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Status { + + private Map cores = new HashMap(); + + @JsonAnyGetter + public Map getCores() { + return cores; + } + + @JsonAnySetter + public void setCore(String core, CoreMetadata metadata) { + this.cores.put(core, metadata); + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/StatusRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/StatusRequest.java new file mode 100644 index 0000000..d2b41df --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/StatusRequest.java @@ -0,0 +1,58 @@ +package com.inteligr8.solr.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class StatusRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "STATUS"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + private String core; + + @QueryParam("indexInfo") + private Boolean includeIndexInformation; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public StatusRequest withCore(String core) { + this.core = core; + return this; + } + + public Boolean getIncludeIndexInformation() { + return includeIndexInformation; + } + + public void setIncludeIndexInformation(Boolean includeIndexInformation) { + this.includeIndexInformation = includeIndexInformation; + } + + public StatusRequest includeIndexInformation(boolean includeIndexInformation) { + this.includeIndexInformation = includeIndexInformation; + return this; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/StatusResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/StatusResponse.java new file mode 100644 index 0000000..a74d76d --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/StatusResponse.java @@ -0,0 +1,21 @@ +package com.inteligr8.solr.model.core; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.inteligr8.solr.model.BaseResponse; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class StatusResponse extends BaseResponse { + + @JsonProperty + private Status status; + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/SwapRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/SwapRequest.java new file mode 100644 index 0000000..ff88b10 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/SwapRequest.java @@ -0,0 +1,76 @@ +package com.inteligr8.solr.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class SwapRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "SWAP"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + @Nonnull + private String core; + + @QueryParam("other") + @Nonnull + private String newCore; + + @QueryParam("async") + private String requestId; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public SwapRequest withCore(String core) { + this.core = core; + return this; + } + + public String getNewCore() { + return newCore; + } + + public void setNewCore(String newCore) { + this.newCore = newCore; + } + + public SwapRequest withNewCore(String newCore) { + this.newCore = newCore; + return this; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public SwapRequest withRequestId(String requestId) { + this.requestId = requestId; + return this; + } + +} diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/core/UnloadRequest.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/UnloadRequest.java new file mode 100644 index 0000000..ead7f55 --- /dev/null +++ b/solr-api/src/main/java/com/inteligr8/solr/model/core/UnloadRequest.java @@ -0,0 +1,107 @@ +package com.inteligr8.solr.model.core; + +import com.inteligr8.solr.model.JsonFormattedResponseRequest; + +import jakarta.annotation.Nonnull; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.QueryParam; + +public class UnloadRequest extends JsonFormattedResponseRequest { + + private static final String ACTION = "UNLOAD"; + + @QueryParam("action") + @DefaultValue(ACTION) + @Nonnull + private String action = ACTION; + + @QueryParam("core") + @Nonnull + private String core; + + @QueryParam("deleteIndex") + private Boolean deleteIndex; + + @QueryParam("deleteDataDir") + private Boolean deleteDataDirectory; + + @QueryParam("deleteInstanceDir") + private Boolean deleteInstanceDirectory; + + @QueryParam("async") + private String requestId; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } + + public UnloadRequest withCore(String core) { + this.core = core; + return this; + } + + public Boolean getDeleteIndex() { + return this.deleteIndex; + } + + public void setDeleteIndex(Boolean deleteIndex) { + this.deleteIndex = deleteIndex; + } + + public UnloadRequest deleteIndex(boolean deleteIndex) { + this.deleteIndex = deleteIndex; + return this; + } + + public Boolean getDeleteDataDirectory() { + return deleteDataDirectory; + } + + public void setDeleteDataDirectory(Boolean deleteDataDirectory) { + this.deleteDataDirectory = deleteDataDirectory; + } + + public UnloadRequest deleteDataDirectory(boolean deleteDataDirectory) { + this.deleteDataDirectory = deleteDataDirectory; + return this; + } + + public Boolean getDeleteInstanceDirectory() { + return deleteInstanceDirectory; + } + + public void setDeleteInstanceDirectory(Boolean deleteInstanceDirectory) { + this.deleteInstanceDirectory = deleteInstanceDirectory; + } + + public UnloadRequest deleteInstanceDirectory(boolean deleteInstanceDirectory) { + this.deleteInstanceDirectory = deleteInstanceDirectory; + return this; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public UnloadRequest withRequestId(String requestId) { + this.requestId = requestId; + return this; + } + +}