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;
+ }
+
+}