diff --git a/asie-api/pom.xml b/asie-api/pom.xml
index dc2eed0..576e2e2 100644
--- a/asie-api/pom.xml
+++ b/asie-api/pom.xml
@@ -15,11 +15,11 @@
1.0.0-asie2
jar
- ASIE JAX-RS API
- Alfresco Search & Insight Engine JAX-RS API
+ ASIE Jakarta RS API
+ Alfresco Search & Insight Engine Jakarta RS API
- 6.0.0
+ 23.2.0
@@ -42,7 +42,41 @@
org.alfresco
- alfresco-repository
+ alfresco-data-model
+
+
+ *
+ *
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.17.0
+
+
+ org.apache.logging.log4j
+ log4j-slf4j2-impl
+ test
+
+
+ com.inteligr8
+ common-rest-client
+ 3.0.2-jersey
+ test
+
+
+ org.glassfish.jersey.inject
+ jersey-hk2
+ 3.1.10
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.11.2
+ test
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
index 0251fe9..112c3d1 100644
--- 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
@@ -1,12 +1,12 @@
package com.inteligr8.alfresco.asie.api;
-import com.inteligr8.alfresco.asie.model.ActionResponse;
+import com.inteligr8.alfresco.asie.model.ActionCoreResponse;
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.FixAction;
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;
@@ -14,14 +14,15 @@ 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.RetryAction;
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 com.inteligr8.solr.model.Action;
+import com.inteligr8.solr.model.ActionResponse;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.GET;
@@ -42,43 +43,43 @@ public interface CoreAdminApi extends com.inteligr8.solr.api.CoreAdminApi {
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse updateCore(@BeanParam UpdateCoreRequest request);
+ ActionResponse updateCore(@BeanParam UpdateCoreRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse check(@BeanParam CheckRequest request);
+ ActionResponse check(@BeanParam CheckRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse updateShared(@BeanParam UpdateSharedRequest request);
+ ActionResponse updateShared(@BeanParam UpdateSharedRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse updateLog4j(@BeanParam UpdateLog4jRequest request);
+ ActionResponse updateLog4j(@BeanParam UpdateLog4jRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse purge(@BeanParam PurgeRequest request);
+ ActionCoreResponse purge(@BeanParam PurgeRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse reindex(@BeanParam ReindexRequest request);
+ ActionCoreResponse reindex(@BeanParam ReindexRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse retry(@BeanParam RetryRequest request);
+ ActionCoreResponse retry(@BeanParam RetryRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse fix(@BeanParam FixRequest request);
+ ActionCoreResponse fix(@BeanParam FixRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse enableIndexing(@BeanParam EnableIndexingRequest request);
+ ActionCoreResponse enableIndexing(@BeanParam EnableIndexingRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse disableIndexing(@BeanParam DisableIndexingRequest request);
+ ActionCoreResponse disableIndexing(@BeanParam DisableIndexingRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/ActionCoreResponse.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/ActionCoreResponse.java
new file mode 100644
index 0000000..b73d281
--- /dev/null
+++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/ActionCoreResponse.java
@@ -0,0 +1,18 @@
+package com.inteligr8.alfresco.asie.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.inteligr8.solr.model.Action;
+import com.inteligr8.solr.model.Cores;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class ActionCoreResponse extends BaseResponse {
+
+ @JsonProperty(value = "action")
+ private Cores cores;
+
+ public Cores getCores() {
+ return cores;
+ }
+
+}
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
deleted file mode 100644
index ca6b586..0000000
--- a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/ActionResponse.java
+++ /dev/null
@@ -1,22 +0,0 @@
-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
index 9ebcb05..5400e46 100644
--- 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
@@ -1,41 +1,28 @@
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)
+ @JsonProperty(value = "STATUS")
private String reason;
- @JsonProperty(value = "exception", access = Access.READ_ONLY)
+ @JsonProperty(value = "exception")
private String exception;
- @JsonProperty(value = "msg", access = Access.READ_ONLY)
+ @JsonProperty(value = "msg")
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/core/FixAction.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixAction.java
new file mode 100644
index 0000000..bb51d0b
--- /dev/null
+++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixAction.java
@@ -0,0 +1,25 @@
+package com.inteligr8.alfresco.asie.model.core;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.inteligr8.solr.model.Action;
+import com.inteligr8.solr.model.TransactionStatus;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class FixAction extends Action {
+
+ @JsonProperty(value = "txToReindex")
+ private TransactionStatus transactionStatus;
+
+ @JsonProperty(value = "aclChangeSetToReindex")
+ private TransactionStatus aclStatus;
+
+ public TransactionStatus getTransactionStatus() {
+ return transactionStatus;
+ }
+
+ public TransactionStatus getAclStatus() {
+ return aclStatus;
+ }
+
+}
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
deleted file mode 100644
index b435cd8..0000000
--- a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/FixResponseAction.java
+++ /dev/null
@@ -1,34 +0,0 @@
-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
index 270bbd8..8320989 100644
--- 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
@@ -1,25 +1,43 @@
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;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.inteligr8.solr.model.Action;
@JsonIgnoreProperties(ignoreUnknown = true)
-public class IndexingStatusAction extends ResponseAction {
+public class IndexingStatusAction extends Action {
- private Map cores;
-
- @JsonAnyGetter
- public Map getCores() {
- return cores;
+ @JsonProperty(value = "ACL")
+ private Boolean aclIndexed;
+
+ @JsonProperty(value = "CONTENT")
+ private Boolean contentIndexed;
+
+ @JsonProperty(value = "METADATA")
+ private Boolean metadataIndexed;
+
+ public boolean isAclIndexed() {
+ return Boolean.TRUE.equals(this.aclIndexed);
}
- @JsonAnySetter
- public void setCores(Map cores) {
- this.cores = cores;
+ public Boolean getAclIndexed() {
+ return aclIndexed;
+ }
+
+ public boolean isContentIndexed() {
+ return Boolean.TRUE.equals(this.contentIndexed);
+ }
+
+ public Boolean getContentIndexed() {
+ return contentIndexed;
+ }
+
+ public boolean isMetadataIndexed() {
+ return Boolean.TRUE.equals(this.metadataIndexed);
+ }
+
+ public Boolean getMetadataIndexed() {
+ return metadataIndexed;
}
}
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
deleted file mode 100644
index e8fe4fd..0000000
--- a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/IndexingStatusMetadata.java
+++ /dev/null
@@ -1,56 +0,0 @@
-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/PurgeRequest.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/PurgeRequest.java
index 42662e5..2d0171c 100644
--- 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
@@ -19,16 +19,16 @@ public class PurgeRequest extends JsonFormattedResponseRequest {
private String core;
@QueryParam("txid")
- private Integer transactionId;
+ private Long transactionId;
@QueryParam("acltxid")
- private Integer aclTransactionId;
+ private Long aclTransactionId;
@QueryParam("nodeId")
- private Integer nodeId;
+ private Long nodeId;
@QueryParam("aclid")
- private Integer aclId;
+ private Long aclId;
public String getAction() {
return action;
@@ -51,54 +51,54 @@ public class PurgeRequest extends JsonFormattedResponseRequest {
return this;
}
- public Integer getTransactionId() {
+ public Long getTransactionId() {
return transactionId;
}
- public void setTransactionId(Integer transactionId) {
+ public void setTransactionId(Long transactionId) {
this.transactionId = transactionId;
}
- public PurgeRequest withTransactionId(Integer transactionId) {
+ public PurgeRequest withTransactionId(Long transactionId) {
this.transactionId = transactionId;
return this;
}
- public Integer getAclTransactionId() {
+ public Long getAclTransactionId() {
return aclTransactionId;
}
- public void setAclTransactionId(Integer aclTransactionId) {
+ public void setAclTransactionId(Long aclTransactionId) {
this.aclTransactionId = aclTransactionId;
}
- public PurgeRequest withAclTransactionId(Integer aclTransactionId) {
+ public PurgeRequest withAclTransactionId(Long aclTransactionId) {
this.aclTransactionId = aclTransactionId;
return this;
}
- public Integer getNodeId() {
+ public Long getNodeId() {
return nodeId;
}
- public void setNodeId(Integer nodeId) {
+ public void setNodeId(Long nodeId) {
this.nodeId = nodeId;
}
- public PurgeRequest withNodeId(Integer nodeId) {
+ public PurgeRequest withNodeId(Long nodeId) {
this.nodeId = nodeId;
return this;
}
- public Integer getAclId() {
+ public Long getAclId() {
return aclId;
}
- public void setAclId(Integer aclId) {
+ public void setAclId(Long aclId) {
this.aclId = aclId;
}
- public PurgeRequest withAclId(Integer aclId) {
+ public PurgeRequest withAclId(Long 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
index 15500c0..5cbdd58 100644
--- 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
@@ -19,16 +19,16 @@ public class ReindexRequest extends JsonFormattedResponseRequest
private String core;
@QueryParam("txid")
- private Integer transactionId;
+ private Long transactionId;
@QueryParam("acltxid")
- private Integer aclTransactionId;
+ private Long aclTransactionId;
@QueryParam("nodeId")
- private Integer nodeId;
+ private Long nodeId;
@QueryParam("aclid")
- private Integer aclId;
+ private Long aclId;
@QueryParam("query")
private String query;
@@ -54,54 +54,54 @@ public class ReindexRequest extends JsonFormattedResponseRequest
return this;
}
- public Integer getTransactionId() {
+ public Long getTransactionId() {
return transactionId;
}
- public void setTransactionId(Integer transactionId) {
+ public void setTransactionId(Long transactionId) {
this.transactionId = transactionId;
}
- public ReindexRequest withTransactionId(Integer transactionId) {
+ public ReindexRequest withTransactionId(Long transactionId) {
this.transactionId = transactionId;
return this;
}
- public Integer getAclTransactionId() {
+ public Long getAclTransactionId() {
return aclTransactionId;
}
- public void setAclTransactionId(Integer aclTransactionId) {
+ public void setAclTransactionId(Long aclTransactionId) {
this.aclTransactionId = aclTransactionId;
}
- public ReindexRequest withAclTransactionId(Integer aclTransactionId) {
+ public ReindexRequest withAclTransactionId(Long aclTransactionId) {
this.aclTransactionId = aclTransactionId;
return this;
}
- public Integer getNodeId() {
+ public Long getNodeId() {
return nodeId;
}
- public void setNodeId(Integer nodeId) {
+ public void setNodeId(Long nodeId) {
this.nodeId = nodeId;
}
- public ReindexRequest withNodeId(Integer nodeId) {
+ public ReindexRequest withNodeId(Long nodeId) {
this.nodeId = nodeId;
return this;
}
- public Integer getAclId() {
+ public Long getAclId() {
return aclId;
}
- public void setAclId(Integer aclId) {
+ public void setAclId(Long aclId) {
this.aclId = aclId;
}
- public ReindexRequest withAclId(Integer aclId) {
+ public ReindexRequest withAclId(Long aclId) {
this.aclId = aclId;
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
index 78646ec..7b52ab3 100644
--- 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
@@ -1,24 +1,9 @@
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.Metadata;
@JsonIgnoreProperties(ignoreUnknown = true)
-public class Report {
-
- private Map> report;
-
- @JsonAnyGetter
- public Map> getReport() {
- return report;
- }
-
- @JsonAnySetter
- protected void setReport(Map> report) {
- this.report = report;
- }
+public class Report extends Metadata {
}
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
index c325299..346a76c 100644
--- 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
@@ -25,10 +25,10 @@ public class ReportRequest extends JsonFormattedResponseRequest {
private Long toTime;
@QueryParam("fromTx")
- private Integer fromTransactionId;
+ private Long fromTransactionId;
@QueryParam("toTx")
- private Integer toTransactionId;
+ private Long toTransactionId;
public String getAction() {
return action;
@@ -77,28 +77,28 @@ public class ReportRequest extends JsonFormattedResponseRequest {
return this;
}
- public Integer getFromTransactionId() {
+ public Long getFromTransactionId() {
return fromTransactionId;
}
- public void setFromTransactionId(Integer fromTransactionId) {
+ public void setFromTransactionId(Long fromTransactionId) {
this.fromTransactionId = fromTransactionId;
}
- public ReportRequest fromTransactionId(Integer fromTransactionId) {
+ public ReportRequest fromTransactionId(Long fromTransactionId) {
this.fromTransactionId = fromTransactionId;
return this;
}
- public Integer getToTransactionId() {
+ public Long getToTransactionId() {
return toTransactionId;
}
- public void setToTransactionId(Integer toTransactionId) {
+ public void setToTransactionId(Long toTransactionId) {
this.toTransactionId = toTransactionId;
}
- public ReportRequest toTransactionId(Integer toTransactionId) {
+ public ReportRequest toTransactionId(Long 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
index 4556f0a..5adb7a8 100644
--- 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
@@ -2,21 +2,17 @@ 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;
+import com.inteligr8.solr.model.Cores;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ReportResponse extends BaseResponse {
- @JsonProperty(access = Access.READ_ONLY)
- private Report report;
+ @JsonProperty(required = true)
+ private Cores cores;
- public Report getReport() {
- return report;
- }
-
- protected void setReport(Report report) {
- this.report = report;
+ public Cores getCores() {
+ return cores;
}
}
diff --git a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryAction.java b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryAction.java
new file mode 100644
index 0000000..f63f2bd
--- /dev/null
+++ b/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryAction.java
@@ -0,0 +1,17 @@
+package com.inteligr8.alfresco.asie.model.core;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.inteligr8.solr.model.Action;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RetryAction extends Action {
+
+ @JsonProperty(value = "alfresco")
+ private int[] nodeIds;
+
+ public int[] getNodeIds() {
+ return nodeIds;
+ }
+
+}
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
deleted file mode 100644
index cdf4eed..0000000
--- a/asie-api/src/main/java/com/inteligr8/alfresco/asie/model/core/RetryResponseAction.java
+++ /dev/null
@@ -1,22 +0,0 @@
-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
index a6b4e2f..bc69e70 100644
--- 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
@@ -1,24 +1,7 @@
package com.inteligr8.alfresco.asie.model.core;
-import java.util.Map;
+import com.inteligr8.solr.model.Metadata;
-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;
- }
+public class Summary extends Metadata {
}
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
index 8aa7675..073ea46 100644
--- 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
@@ -2,21 +2,17 @@ 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;
+import com.inteligr8.solr.model.Cores;
@JsonIgnoreProperties(ignoreUnknown = true)
public class SummaryResponse extends BaseResponse {
- @JsonProperty(value = "Summary", access = Access.READ_ONLY)
- private Summary summary;
+ @JsonProperty(value = "Summary", required = true)
+ private Cores cores;
- public Summary getSummary() {
- return summary;
- }
-
- public void setSummary(Summary summary) {
- this.summary = summary;
+ public Cores getCores() {
+ return cores;
}
}
diff --git a/asie-api/src/test/java/com/inteligr8/alfresco/asie/AsieClient.java b/asie-api/src/test/java/com/inteligr8/alfresco/asie/AsieClient.java
new file mode 100644
index 0000000..aa13e1d
--- /dev/null
+++ b/asie-api/src/test/java/com/inteligr8/alfresco/asie/AsieClient.java
@@ -0,0 +1,24 @@
+package com.inteligr8.alfresco.asie;
+
+import java.net.URL;
+
+import com.inteligr8.alfresco.asie.api.CoreAdminApi;
+import com.inteligr8.rs.ClientJerseyImpl;
+
+public class AsieClient extends ClientJerseyImpl {
+
+ public AsieClient(String hostname) {
+ super(new AsieClientConfiguration().withHostname(hostname));
+ this.register();
+ }
+
+ public AsieClient(URL baseUrl) {
+ super(new AsieClientConfiguration().withBaseUrl(baseUrl.toString()));
+ this.register();
+ }
+
+ public CoreAdminApi getCoreAdminApi() {
+ return this.getApi(this.getConfig().createAuthorizationFilter(), CoreAdminApi.class);
+ }
+
+}
diff --git a/asie-api/src/test/java/com/inteligr8/alfresco/asie/AsieClientConfiguration.java b/asie-api/src/test/java/com/inteligr8/alfresco/asie/AsieClientConfiguration.java
new file mode 100644
index 0000000..1540b8e
--- /dev/null
+++ b/asie-api/src/test/java/com/inteligr8/alfresco/asie/AsieClientConfiguration.java
@@ -0,0 +1,54 @@
+package com.inteligr8.alfresco.asie;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.inteligr8.rs.AuthorizationFilter;
+import com.inteligr8.rs.ClientJerseyConfiguration;
+
+import jakarta.ws.rs.client.ClientRequestContext;
+
+public class AsieClientConfiguration implements ClientJerseyConfiguration {
+
+ private Logger logger = LoggerFactory.getLogger(AsieClientConfiguration.class);
+ private String baseUrl = "http://locahost:8983/solr";
+ private String searchSecret = "alfresco-secret";
+
+ @Override
+ public String getBaseUrl() {
+ return this.baseUrl;
+ }
+
+ public AsieClientConfiguration withBaseUrl(String baseUrl) {
+ this.baseUrl = baseUrl;
+ return this;
+ }
+
+ public AsieClientConfiguration withHostname(String hostname) {
+ this.baseUrl = "http://" + hostname + ":8983/solr";
+ return this;
+ }
+
+ public AsieClientConfiguration withSearchSecret(String searchSecret) {
+ this.searchSecret = searchSecret;
+ return this;
+ }
+
+ @Override
+ public AuthorizationFilter createAuthorizationFilter() {
+ if (this.searchSecret == null)
+ return null;
+
+ return new AuthorizationFilter() {
+ @Override
+ public void filter(ClientRequestContext requestContext) throws IOException {
+ logger.trace("Adding ASIE secret for authorization ...");
+ requestContext.getHeaders().addAll("X-Alfresco-Search-Secret", Arrays.asList(searchSecret));
+ }
+ };
+ }
+
+}
diff --git a/asie-api/src/test/java/com/inteligr8/alfresco/asie/AssertionUtil.java b/asie-api/src/test/java/com/inteligr8/alfresco/asie/AssertionUtil.java
new file mode 100644
index 0000000..350c345
--- /dev/null
+++ b/asie-api/src/test/java/com/inteligr8/alfresco/asie/AssertionUtil.java
@@ -0,0 +1,27 @@
+package com.inteligr8.alfresco.asie;
+
+import org.junit.jupiter.api.Assertions;
+
+public class AssertionUtil {
+
+ public static T assertNotNull(T obj) {
+ Assertions.assertNotNull(obj);
+ return obj;
+ }
+
+ public static T assertNotNull(T obj, String message) {
+ Assertions.assertNotNull(obj, message);
+ return obj;
+ }
+
+ public static void assertType(Object obj, Class> type) {
+ Assertions.assertNotNull(obj);
+ Assertions.assertEquals(type, obj.getClass());
+ }
+
+ public static void assertType(Object obj, Class> type, String message) {
+ Assertions.assertNotNull(obj, message);
+ Assertions.assertEquals(type, obj.getClass(), message);
+ }
+
+}
diff --git a/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/AbstractApiUnitTest.java b/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/AbstractApiUnitTest.java
new file mode 100644
index 0000000..3d6ac07
--- /dev/null
+++ b/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/AbstractApiUnitTest.java
@@ -0,0 +1,23 @@
+package com.inteligr8.alfresco.asie.api;
+
+import java.net.MalformedURLException;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.inteligr8.alfresco.asie.AsieClient;
+
+public class AbstractApiUnitTest {
+
+ protected Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ protected static AsieClient client;
+ protected static String defaultCore = "alfresco";
+
+ @BeforeAll
+ private static void init() throws MalformedURLException {
+ client = new AsieClient("localhost");
+ }
+
+}
diff --git a/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/CoreAdminReindexUnitTest.java b/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/CoreAdminReindexUnitTest.java
new file mode 100644
index 0000000..0457f3f
--- /dev/null
+++ b/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/CoreAdminReindexUnitTest.java
@@ -0,0 +1,34 @@
+package com.inteligr8.alfresco.asie.api;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import com.inteligr8.alfresco.asie.AssertionUtil;
+import com.inteligr8.alfresco.asie.model.ActionCoreResponse;
+import com.inteligr8.alfresco.asie.model.core.ReindexRequest;
+import com.inteligr8.solr.model.Action;
+import com.inteligr8.solr.model.Action.Status;
+import com.inteligr8.solr.model.Cores;
+import com.inteligr8.solr.model.ResponseHeader;
+
+public class CoreAdminReindexUnitTest extends AbstractApiUnitTest {
+
+ @Test
+ public void reindex() {
+ CoreAdminApi api = client.getCoreAdminApi();
+
+ ActionCoreResponse response = api.reindex(
+ new ReindexRequest()
+ .withCore(defaultCore));
+ Assertions.assertNotNull(response);
+
+ ResponseHeader responseHeader = AssertionUtil.assertNotNull(response.getResponseHeader());
+ Assertions.assertEquals(0, responseHeader.getStatus());
+
+ Cores cores = AssertionUtil.assertNotNull(response.getCores());
+ Action action = AssertionUtil.assertNotNull(cores.getByCore(defaultCore));
+
+ Assertions.assertEquals(Status.Scheduled, action.getStatus());
+ }
+
+}
diff --git a/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/CoreAdminStatusUnitTest.java b/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/CoreAdminStatusUnitTest.java
new file mode 100644
index 0000000..fb35228
--- /dev/null
+++ b/asie-api/src/test/java/com/inteligr8/alfresco/asie/api/CoreAdminStatusUnitTest.java
@@ -0,0 +1,96 @@
+package com.inteligr8.alfresco.asie.api;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import com.inteligr8.alfresco.asie.AsieClient;
+import com.inteligr8.alfresco.asie.AssertionUtil;
+import com.inteligr8.alfresco.asie.model.core.Report;
+import com.inteligr8.alfresco.asie.model.core.ReportRequest;
+import com.inteligr8.alfresco.asie.model.core.ReportResponse;
+import com.inteligr8.alfresco.asie.model.core.Summary;
+import com.inteligr8.alfresco.asie.model.core.SummaryRequest;
+import com.inteligr8.alfresco.asie.model.core.SummaryResponse;
+import com.inteligr8.solr.model.CoreMetadata;
+import com.inteligr8.solr.model.Cores;
+import com.inteligr8.solr.model.ResponseHeader;
+import com.inteligr8.solr.model.core.StatusRequest;
+import com.inteligr8.solr.model.core.StatusResponse;
+
+import jakarta.ws.rs.ProcessingException;
+
+public class CoreAdminStatusUnitTest extends AbstractApiUnitTest {
+
+ @Test
+ public void noHost() {
+ AsieClient client = new AsieClient("doesnotexist");
+ CoreAdminApi api = client.getCoreAdminApi();
+
+ Assertions.assertThrows(ProcessingException.class, () -> {
+ api.getStatus(
+ new StatusRequest()
+ .withCore(defaultCore));
+ });
+ }
+
+ @Test
+ public void summary() {
+ CoreAdminApi api = client.getCoreAdminApi();
+
+ SummaryResponse response = api.getSummary(
+ new SummaryRequest()
+ .withCore(defaultCore));
+ Assertions.assertNotNull(response);
+
+ ResponseHeader responseHeader = AssertionUtil.assertNotNull(response.getResponseHeader());
+ Assertions.assertEquals(0, responseHeader.getStatus());
+
+ Cores cores = AssertionUtil.assertNotNull(response.getCores());
+ Summary summary = AssertionUtil.assertNotNull(cores.getByCore(defaultCore));
+
+ AssertionUtil.assertType(summary.getByField("Active"), Boolean.class);
+ AssertionUtil.assertType(summary.getByField("Number of Searchers"), Integer.class);
+ AssertionUtil.assertType(summary.getByField("Last Index TX Commit Time"), Long.class);
+ AssertionUtil.assertType(summary.getByField("Last Index TX Commit Date"), String.class);
+ }
+
+ @Test
+ public void status() {
+ CoreAdminApi api = client.getCoreAdminApi();
+
+ StatusResponse response = api.getStatus(
+ new StatusRequest()
+ .withCore(defaultCore));
+ Assertions.assertNotNull(response);
+
+ ResponseHeader responseHeader = AssertionUtil.assertNotNull(response.getResponseHeader());
+ Assertions.assertEquals(0, responseHeader.getStatus());
+
+ Cores cores = AssertionUtil.assertNotNull(response.getCores());
+ CoreMetadata core = AssertionUtil.assertNotNull(cores.getByCore(defaultCore));
+
+ Assertions.assertEquals(core, core.getName());
+ Assertions.assertNotNull(core.getStartDateTime());
+ }
+
+ @Test
+ public void report() {
+ CoreAdminApi api = client.getCoreAdminApi();
+
+ ReportResponse response = api.getReport(
+ new ReportRequest()
+ .withCore(defaultCore));
+ Assertions.assertNotNull(response);
+
+ ResponseHeader responseHeader = AssertionUtil.assertNotNull(response.getResponseHeader());
+ Assertions.assertEquals(0, responseHeader.getStatus());
+
+ Cores cores = AssertionUtil.assertNotNull(response.getCores());
+ Report report = AssertionUtil.assertNotNull(cores.getByCore(defaultCore));
+
+ AssertionUtil.assertType(report.getByField("Index error count"), Integer.class);
+ AssertionUtil.assertType(report.getByField("Last indexed transaction commit time"), Long.class);
+ AssertionUtil.assertType(report.getByField("Last indexed transaction commit date"), String.class);
+ }
+
+}
diff --git a/asie-api/src/test/resources/log4j2-test.properties b/asie-api/src/test/resources/log4j2-test.properties
new file mode 100644
index 0000000..e9efd7d
--- /dev/null
+++ b/asie-api/src/test/resources/log4j2-test.properties
@@ -0,0 +1,23 @@
+rootLogger.level=trace
+rootLogger.appenderRef.stdout.ref=STDOUT
+
+logger.inteligr8-rs-request.name=jaxrs.request
+logger.inteligr8-rs-request.level=trace
+logger.inteligr8-rs-response.name=jaxrs.response
+logger.inteligr8-rs-response.level=off
+
+logger.this.name=com.inteligr8.alfresco.asie
+logger.this.level=trace
+
+# hide framework
+logger.apache-http.name=org.apache.http
+logger.apache-http.level=debug
+logger.jersey.name=org.glassfish.jersey
+logger.jersey.level=trace
+logger.jersey-client.name=org.glassfish.jersey.client
+logger.jersey-client.level=trace
+
+appender.stdout.type=Console
+appender.stdout.name=STDOUT
+appender.stdout.layout.type=PatternLayout
+appender.stdout.layout.pattern=%d{ABSOLUTE_MICROS} %level{length=1} %c{1}: %m%n
diff --git a/enterprise-module/pom.xml b/enterprise-module/pom.xml
index 0b7ff0f..c293b5a 100644
--- a/enterprise-module/pom.xml
+++ b/enterprise-module/pom.xml
@@ -83,7 +83,7 @@
com.inteligr8.alfresco
cxf-jaxrs-platform-module
- 1.3.1-acs-v23.3
+ 1.3.2-acs-v23.3
amp
diff --git a/enterprise-module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractUnregisterNodeWebScript.java b/enterprise-module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractUnregisterNodeWebScript.java
index 0b22feb..cef7d4c 100755
--- a/enterprise-module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractUnregisterNodeWebScript.java
+++ b/enterprise-module/src/main/java/com/inteligr8/alfresco/asie/enterprise/rest/AbstractUnregisterNodeWebScript.java
@@ -91,7 +91,7 @@ public abstract class AbstractUnregisterNodeWebScript
UTF-8
- 11
- 11
- 11
+ 17
+ 17
+ 17
true
@@ -56,12 +56,34 @@
maven-site-plugin
- 3.12.1
+ 3.21.0
maven-dependency-plugin
- 3.8.0
+ 3.8.1
+
+
+ maven-surefire-plugin
+ 3.4.0
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.11.0
+
+
+
+
+ maven-failsafe-plugin
+ 3.4.0
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.11.0
+
+
@@ -72,7 +94,6 @@
asie-api
shared
enterprise-module
- community-module
diff --git a/shared/pom.xml b/shared/pom.xml
index ae5781f..d8dbd77 100644
--- a/shared/pom.xml
+++ b/shared/pom.xml
@@ -41,10 +41,15 @@
com.inteligr8
common-rest-client
- 3.0.1-cxf
+ 3.0.3-cxf
+
+ org.alfresco
+ alfresco-data-model
+ provided
+
org.alfresco
alfresco-repository
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/model/Shard.java b/shared/src/main/java/com/inteligr8/alfresco/asie/model/Shard.java
index 6841050..21491d0 100644
--- a/shared/src/main/java/com/inteligr8/alfresco/asie/model/Shard.java
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/model/Shard.java
@@ -19,7 +19,7 @@ public class Shard implements Serializable {
private final String spec;
protected Shard(ShardSet shardSet, int shardId) {
- this.spec = shardSet.getCore() + "~" + shardId;
+ this.spec = shardSet.getCore() + "-" + shardId;
}
protected Shard(String spec) {
@@ -34,16 +34,20 @@ public class Shard implements Serializable {
}
public String getSpec() {
- return spec;
+ return this.spec;
+ }
+
+ public String getCoreName() {
+ return this.spec;
}
public String extractShardSetCore() {
- int pos = this.spec.indexOf('~');
+ int pos = this.spec.lastIndexOf('-');
return this.spec.substring(0, pos);
}
public int extractShardId() {
- int pos = this.spec.indexOf('~');
+ int pos = this.spec.lastIndexOf('-');
return Integer.parseInt(this.spec.substring(pos+1));
}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/model/ShardInstance.java b/shared/src/main/java/com/inteligr8/alfresco/asie/model/ShardInstance.java
index 53aa456..cbd5dbf 100644
--- a/shared/src/main/java/com/inteligr8/alfresco/asie/model/ShardInstance.java
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/model/ShardInstance.java
@@ -13,7 +13,7 @@ public class ShardInstance implements Serializable {
private final String spec;
protected ShardInstance(Shard shard, SolrHost node) {
- this.spec = shard.getSpec() + "~" + node.getSpec();
+ this.spec = node.getSpec() + "~" + shard.getSpec();
}
public org.alfresco.repo.index.shard.ShardInstance toAlfrescoModel(org.alfresco.repo.index.shard.Shard shard) {
@@ -33,14 +33,14 @@ public class ShardInstance implements Serializable {
return spec;
}
- public Shard extractShard() {
- int pos = this.spec.indexOf('~');
- return Shard.from(this.spec.substring(0, pos));
- }
-
public SolrHost extractNode() {
int pos = this.spec.indexOf('~');
- return SolrHost.from(this.spec.substring(pos+1));
+ return SolrHost.from(this.spec.substring(0, pos));
+ }
+
+ public Shard extractShard() {
+ int pos = this.spec.indexOf('~');
+ return Shard.from(this.spec.substring(pos+1));
}
@Override
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/model/SolrHost.java b/shared/src/main/java/com/inteligr8/alfresco/asie/model/SolrHost.java
index b935e97..c674e33 100644
--- a/shared/src/main/java/com/inteligr8/alfresco/asie/model/SolrHost.java
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/model/SolrHost.java
@@ -41,6 +41,8 @@ public class SolrHost implements Serializable {
this.spec = spec;
Matcher matcher = PATTERN.matcher(spec);
+ if (!matcher.find())
+ throw new IllegalArgumentException();
this.hostname = matcher.group(1);
this.port = Integer.parseInt(matcher.group(2));
this.path = matcher.group(3);
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAcsNodeActionWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAcsNodeActionWebScript.java
new file mode 100644
index 0000000..e333913
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAcsNodeActionWebScript.java
@@ -0,0 +1,119 @@
+package com.inteligr8.alfresco.asie.rest;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+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.model.ShardInstance;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+
+public abstract class AbstractAcsNodeActionWebScript extends AbstractAsieWebScript {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Autowired
+ private NodeService nodeService;
+
+ @Override
+ public void executeAuthorized(WebScriptRequest request, WebScriptResponse response) throws IOException {
+ String nodeId = request.getServiceMatch().getTemplateVars().get("nodeId");
+ NodeRef nodeRef = new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, nodeId);
+ long nodeDbId = this.findNodeDbId(nodeRef);
+ this.logger.trace("Found node database ID: {}: {}", nodeId, nodeDbId);
+
+ try {
+ Map responseMap = new HashMap<>();
+ responseMap.put("nodeDbId", nodeDbId);
+
+ ActionCallback callback = new ActionCallback() {
+
+ @Override
+ public void success(ShardInstance instance) {
+ @SuppressWarnings("unchecked")
+ List instances = (List) responseMap.get("success");
+ if (instances == null)
+ responseMap.put("success", instances = new LinkedList<>());
+ instances.add(instance.getSpec());
+ }
+
+ @Override
+ public void scheduled(ShardInstance instance) {
+ @SuppressWarnings("unchecked")
+ List instances = (List) responseMap.get("scheduled");
+ if (instances == null)
+ responseMap.put("scheduled", instances = new LinkedList<>());
+ instances.add(instance.getSpec());
+ }
+
+ @Override
+ public void error(ShardInstance instance, String message) {
+ @SuppressWarnings("unchecked")
+ Map instances = (Map) responseMap.get("error");
+ if (instances == null)
+ responseMap.put("error", instances = new HashMap<>());
+ instances.put(instance.getSpec(), Collections.singletonMap("message", message));
+ }
+
+ @Override
+ public void unknownResult(ShardInstance instance) {
+ @SuppressWarnings("unchecked")
+ List instances = (List) responseMap.get("unknown");
+ if (instances == null)
+ responseMap.put("unknown", instances = new LinkedList<>());
+ instances.add(instance.getSpec());
+ }
+ };
+
+ this.executeAction(nodeDbId, callback, 10L, TimeUnit.SECONDS, 30L, TimeUnit.SECONDS);
+
+ if (responseMap.containsKey("error")) {
+ response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+ } else if (responseMap.containsKey("scheduled")) {
+ response.setStatus(HttpStatus.ACCEPTED.value());
+ } else {
+ response.setStatus(HttpStatus.OK.value());
+ }
+
+ response.setContentType("application/json");
+ this.getObjectMapper().writeValue(response.getWriter(), responseMap);
+ } catch (UnsupportedOperationException uoe) {
+ throw new WebScriptException(HttpStatus.NOT_IMPLEMENTED.value(), uoe.getMessage(), uoe);
+ } catch (InterruptedException ie) {
+ throw new WebScriptException(HttpStatus.SERVICE_UNAVAILABLE.value(), "The execution was interrupted", ie);
+ } catch (TimeoutException te) {
+ throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "The execution may continue, but timed-out waiting", te);
+ }
+ }
+
+ protected abstract void executeAction(
+ long nodeDbId, ActionCallback callback,
+ long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException;
+
+ private long findNodeDbId(NodeRef nodeRef) {
+ try {
+ return (Long) this.nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID);
+ } catch (InvalidNodeRefException inre) {
+ throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The node does not exist");
+ }
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractActionWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractActionWebScript.java
new file mode 100644
index 0000000..dd2be85
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractActionWebScript.java
@@ -0,0 +1,102 @@
+package com.inteligr8.alfresco.asie.rest;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+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.model.ShardInstance;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+
+public abstract class AbstractActionWebScript extends AbstractAsieWebScript {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Override
+ public void executeAuthorized(WebScriptRequest request, WebScriptResponse response) throws IOException {
+ try {
+ Map responseMap = new HashMap<>();
+
+ ActionCallback callback = new ActionCallback() {
+
+ @Override
+ public void success(ShardInstance instance) {
+ @SuppressWarnings("unchecked")
+ List instances = (List) responseMap.get("success");
+ if (instances == null)
+ responseMap.put("success", instances = new LinkedList<>());
+ instances.add(instance.getSpec());
+ }
+
+ @Override
+ public void scheduled(ShardInstance instance) {
+ @SuppressWarnings("unchecked")
+ List instances = (List) responseMap.get("scheduled");
+ if (instances == null)
+ responseMap.put("scheduled", instances = new LinkedList<>());
+ instances.add(instance.getSpec());
+ }
+
+ @Override
+ public void error(ShardInstance instance, String message) {
+ @SuppressWarnings("unchecked")
+ Map instances = (Map) responseMap.get("error");
+ if (instances == null)
+ responseMap.put("error", instances = new HashMap<>());
+ instances.put(instance.getSpec(), Collections.singletonMap("message", message));
+ }
+
+ @Override
+ public void unknownResult(ShardInstance instance) {
+ @SuppressWarnings("unchecked")
+ List instances = (List) responseMap.get("unknown");
+ if (instances == null)
+ responseMap.put("unknown", instances = new LinkedList<>());
+ instances.add(instance.getSpec());
+ }
+ };
+
+ this.executeAction(callback, 10L, TimeUnit.SECONDS, 30L, TimeUnit.SECONDS);
+
+ if (responseMap.containsKey("error")) {
+ response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+ } else if (responseMap.containsKey("scheduled")) {
+ response.setStatus(HttpStatus.ACCEPTED.value());
+ } else {
+ response.setStatus(HttpStatus.OK.value());
+ }
+
+ response.setContentType("application/json");
+ this.getObjectMapper().writeValue(response.getWriter(), responseMap);
+ } catch (UnsupportedOperationException uoe) {
+ throw new WebScriptException(HttpStatus.NOT_IMPLEMENTED.value(), uoe.getMessage(), uoe);
+ } catch (InterruptedException ie) {
+ throw new WebScriptException(HttpStatus.SERVICE_UNAVAILABLE.value(), "The execution was interrupted", ie);
+ } catch (TimeoutException te) {
+ throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "The execution may continue, but timed-out waiting", te);
+ }
+ }
+
+ protected abstract void executeAction(
+ ActionCallback callback,
+ long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException;
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieNodeShardWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieNodeShardWebScript.java
index 2e9689a..72a6a6d 100644
--- a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieNodeShardWebScript.java
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieNodeShardWebScript.java
@@ -20,7 +20,7 @@ public abstract class AbstractAsieNodeShardWebScript extends AbstractAsieShardab
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));
+ int nodePort = colon < 0 ? this.getApiService().getDefaultSolrPort() : Integer.parseInt(nodeEndpoint.substring(colon+1));
ShardSet shardSet = this.getRequiredPathParameter(req, "shardSet", ShardSet.class);
int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class);
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieNodeWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieNodeWebScript.java
index e40244b..cf86102 100644
--- a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieNodeWebScript.java
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieNodeWebScript.java
@@ -31,7 +31,7 @@ public abstract class AbstractAsieNodeWebScript extends AbstractAsieShardableWeb
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));
+ int nodePort = colon < 0 ? this.getApiService().getDefaultSolrPort() : Integer.parseInt(nodeEndpoint.substring(colon+1));
this.execute(req, res, nodeHostname, nodePort);
}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieShardableWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieShardableWebScript.java
index 2b4d3d7..fb71d0c 100644
--- a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieShardableWebScript.java
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieShardableWebScript.java
@@ -122,7 +122,7 @@ public abstract class AbstractAsieShardableWebScript extends AbstractAsieWebScri
}
protected CoreAdminApi getApi(ShardInstance shard) {
- return this.createApi(shard.getHostName(), shard.getPort());
+ return this.getApiService().createApi(shard.getHostName(), shard.getPort(), CoreAdminApi.class);
}
}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieWebScript.java
index 66dd013..cfb3bf1 100644
--- a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieWebScript.java
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/AbstractAsieWebScript.java
@@ -1,117 +1,34 @@
package com.inteligr8.alfresco.asie.rest;
-import java.io.IOException;
-
-import org.apache.commons.lang3.StringUtils;
-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.extensions.webscripts.WebScriptRequest;
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 com.inteligr8.alfresco.asie.service.ApiService;
public abstract class AbstractAsieWebScript extends AbstractWebScript {
-
- 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.basePath}")
- private String solrBaseUrl;
@Autowired
@Qualifier(Constants.QUALIFIER_ASIE)
private ObjectMapper objectMapper;
- @Override
- public void afterPropertiesSet() throws Exception {
- super.afterPropertiesSet();
- this.solrSharedSecret = StringUtils.trimToNull(this.solrSharedSecret);
- }
+ @Autowired
+ private ApiService api;
protected ObjectMapper getObjectMapper() {
return this.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 ApiService getApiService() {
+ return this.api;
}
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;
+ return this.api.formulateSolrBaseUrl(hostname, port);
}
}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/FixWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/FixWebScript.java
new file mode 100644
index 0000000..a4bd437
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/FixWebScript.java
@@ -0,0 +1,24 @@
+package com.inteligr8.alfresco.asie.rest;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.service.FixService;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+
+@Component(value = "webscript.com.inteligr8.alfresco.asie.fix.post")
+public class FixWebScript extends AbstractActionWebScript {
+
+ @Autowired
+ private FixService fixSerivce;
+
+ @Override
+ protected void executeAction(ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ this.fixSerivce.fix(callback, 10L, TimeUnit.SECONDS, 30L, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/PurgeAcsNodeWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/PurgeAcsNodeWebScript.java
new file mode 100644
index 0000000..504b593
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/PurgeAcsNodeWebScript.java
@@ -0,0 +1,24 @@
+package com.inteligr8.alfresco.asie.rest;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.service.PurgeService;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+
+@Component(value = "webscript.com.inteligr8.alfresco.asie.purgeAcsNode.put")
+public class PurgeAcsNodeWebScript extends AbstractAcsNodeActionWebScript {
+
+ @Autowired
+ private PurgeService purgeSerivce;
+
+ @Override
+ protected void executeAction(long nodeDbId, ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ this.purgeSerivce.purge(nodeDbId, callback, 10L, TimeUnit.SECONDS, 30L, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/ReconcileAcsNodesWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/ReconcileAcsNodesWebScript.java
new file mode 100644
index 0000000..a7e5a46
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/ReconcileAcsNodesWebScript.java
@@ -0,0 +1,128 @@
+package com.inteligr8.alfresco.asie.rest;
+
+import java.io.IOException;
+import java.util.Collections;
+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.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+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.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.model.ShardInstance;
+import com.inteligr8.alfresco.asie.service.AcsReconcileService;
+import com.inteligr8.alfresco.asie.spi.ReconcileCallback;
+
+@Component(value = "webscript.com.inteligr8.alfresco.asie.reconcileAcsNodes.post")
+public class ReconcileAcsNodesWebScript extends AbstractAsieWebScript {
+
+ @Autowired
+ private AcsReconcileService reconcileService;
+
+ @Override
+ public void executeAuthorized(WebScriptRequest request, WebScriptResponse response) throws IOException {
+ final int fromDbId = this.getRequestTemplateIntegerVariable(request, "fromDbId");
+ final int toDbId = this.getRequestTemplateIntegerVariable(request, "toDbId");
+ final boolean reindex = Boolean.TRUE.equals(this.getOptionalQueryParameter(request, "reindex", Boolean.class));
+ final boolean includeReconciled = Boolean.TRUE.equals(this.getOptionalQueryParameter(request, "includeReconciled", Boolean.class));
+
+ final Map responseMap = new HashMap<>();
+
+ ReconcileCallback callback = new ReconcileCallback() {
+
+ @Override
+ public void reconciled(long nodeDbId) {
+ if (includeReconciled) {
+ @SuppressWarnings("unchecked")
+ List unreconciledNodeDbIds = (List) responseMap.get("reconciled");
+ if (unreconciledNodeDbIds == null)
+ responseMap.put("reconciled", unreconciledNodeDbIds = new LinkedList<>());
+ unreconciledNodeDbIds.add(nodeDbId);
+ }
+ }
+
+ @Override
+ public void unreconciled(long nodeDbId) {
+ @SuppressWarnings("unchecked")
+ List unreconciledNodeDbIds = (List) responseMap.get("unreconciled");
+ if (unreconciledNodeDbIds == null)
+ responseMap.put("unreconciled", unreconciledNodeDbIds = new LinkedList<>());
+ unreconciledNodeDbIds.add(nodeDbId);
+ }
+
+ @Override
+ public void processed(long nodeDbId, Set instsReconciled, Set instsReconciling,
+ Map instsErrorMessages) {
+ if (!instsReconciled.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ Map> nodeHosts = (Map>) responseMap.get("success");
+ if (nodeHosts == null)
+ responseMap.put("success", nodeHosts = new HashMap<>());
+
+ List instances = new LinkedList<>();
+ for (ShardInstance instance : instsReconciled)
+ instances.add(instance.getSpec());
+ nodeHosts.put(nodeDbId, instances);
+ }
+
+ if (!instsReconciling.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ Map> nodeHosts = (Map>) responseMap.get("scheduled");
+ if (nodeHosts == null)
+ responseMap.put("scheduled", nodeHosts = new HashMap<>());
+
+ List instances = new LinkedList<>();
+ for (ShardInstance instance : instsReconciled)
+ instances.add(instance.getSpec());
+ nodeHosts.put(nodeDbId, instances);
+ }
+
+ if (!instsErrorMessages.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ Map>> nodeHosts = (Map>>) responseMap.get("error");
+ if (nodeHosts == null)
+ responseMap.put("error", nodeHosts = new HashMap<>());
+
+ Map> nodeHost = new HashMap<>();
+ for (Entry message : instsErrorMessages.entrySet())
+ nodeHost.put(message.getKey().getSpec(), Collections.singletonMap("message", message.getValue()));
+ nodeHosts.put(nodeDbId, nodeHost);
+ }
+ }
+ };
+
+ try {
+ this.reconcileService.reconcile(fromDbId, toDbId, null, reindex, callback, 1L, TimeUnit.HOURS, 2L, TimeUnit.MINUTES);
+
+ if (responseMap.containsKey("error")) {
+ response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
+ } else if (responseMap.containsKey("scheduled")) {
+ response.setStatus(HttpStatus.ACCEPTED.value());
+ } else {
+ response.setStatus(HttpStatus.OK.value());
+ }
+
+ response.setContentType("application/json");
+ this.getObjectMapper().writeValue(response.getWriter(), responseMap);
+ } catch (InterruptedException ie) {
+ throw new WebScriptException(HttpStatus.SERVICE_UNAVAILABLE.value(), "The reindex was interrupted", ie);
+ } catch (TimeoutException te) {
+ throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "The reindex may continue, but timed-out waiting", te);
+ }
+ }
+
+ private int getRequestTemplateIntegerVariable(WebScriptRequest request, String templateVariableName) {
+ String str = request.getServiceMatch().getTemplateVars().get(templateVariableName);
+ return Integer.valueOf(str);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/ReindexAcsNodeWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/ReindexAcsNodeWebScript.java
new file mode 100644
index 0000000..ed764fa
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/ReindexAcsNodeWebScript.java
@@ -0,0 +1,24 @@
+package com.inteligr8.alfresco.asie.rest;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.service.ReindexService;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+
+@Component(value = "webscript.com.inteligr8.alfresco.asie.reindexAcsNode.put")
+public class ReindexAcsNodeWebScript extends AbstractAcsNodeActionWebScript {
+
+ @Autowired
+ private ReindexService reindexSerivce;
+
+ @Override
+ protected void executeAction(long nodeDbId, ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ this.reindexSerivce.reindex(nodeDbId, callback, 10L, TimeUnit.SECONDS, 30L, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/rest/RetryWebScript.java b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/RetryWebScript.java
new file mode 100644
index 0000000..1ab65e9
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/rest/RetryWebScript.java
@@ -0,0 +1,24 @@
+package com.inteligr8.alfresco.asie.rest;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.service.RetryService;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+
+@Component(value = "webscript.com.inteligr8.alfresco.asie.retry.post")
+public class RetryWebScript extends AbstractActionWebScript {
+
+ @Autowired
+ private RetryService retrySerivce;
+
+ @Override
+ protected void executeAction(ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ this.retrySerivce.retry(callback, 10L, TimeUnit.SECONDS, 30L, TimeUnit.SECONDS);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/AbstractActionService.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/AbstractActionService.java
new file mode 100644
index 0000000..e3bea63
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/AbstractActionService.java
@@ -0,0 +1,236 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.alfresco.model.ContentModel;
+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.ShardRegistry;
+import org.alfresco.repo.index.shard.ShardState;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.SearchParameters;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+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 com.inteligr8.alfresco.asie.Constants;
+import com.inteligr8.alfresco.asie.api.CoreAdminApi;
+import com.inteligr8.alfresco.asie.model.ActionCoreResponse;
+import com.inteligr8.alfresco.asie.model.ShardSet;
+import com.inteligr8.alfresco.asie.model.SolrHost;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+import com.inteligr8.alfresco.asie.util.CompositeFuture;
+import com.inteligr8.alfresco.asie.util.ThrottledThreadPoolExecutor;
+import com.inteligr8.solr.model.Action;
+import com.inteligr8.solr.model.ActionResponse;
+import com.inteligr8.solr.model.BaseResponse;
+
+public abstract class AbstractActionService {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Autowired
+ private NamespaceService namespaceService;
+
+ @Autowired
+ private ApiService apiService;
+
+ @Autowired
+ private ExecutorManager executorManager;
+
+ @Autowired(required = false)
+ @Qualifier(Constants.QUALIFIER_ASIE)
+ private ShardRegistry shardRegistry;
+
+ @Value("${inteligr8.asie.default.concurrentQueueSize:64}")
+ private int concurrentQueueSize;
+
+ @Value("${inteligr8.asie.default.concurrency:16}")
+ private int concurrency;
+
+ protected int getConcurrency() {
+ return this.concurrency;
+ }
+
+ protected int getConcurrentQueueSize() {
+ return this.concurrentQueueSize;
+ }
+
+ protected abstract String getThreadNamePrefix();
+
+ protected abstract String getActionName();
+
+ /**
+ * This method executes an action on the specified node in Solr using its
+ * ACS unique database identifier. The callback handles all the return
+ * values. This is the synchronous alternative to the other `action`
+ * method.
+ *
+ * There are two sets of parameters regarding timeouts. The queue timeouts
+ * are for how long the requesting thread should wait for a full queue to
+ * open up space for new executions. The execution timeouts are for how
+ * long the execution should be allowed to take once dequeued. There is no
+ * timeout for how long the execution is queued.
+ *
+ * @param callback A callback to process multiple returned values from the action.
+ * @param fullQueueTimeout A timeout for how long the calling thread should wait for space on the queue.
+ * @param fullQueueUnit The time units for the `queueTimeout`.
+ * @param execTimeout A timeout for the elapsed time the execution should take when dequeued.
+ * @param execUnit The time units for the `execTimeout`.
+ * @throws TimeoutException Either the queue or execution timeout lapsed.
+ * @throws InterruptedException The execution was interrupted (server shutdown).
+ */
+ protected void action(ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit, long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ long fullQueueExpireTimeMillis = System.currentTimeMillis() + fullQueueUnit.toMillis(fullQueueTimeout);
+ Future future = this._action(callback, fullQueueExpireTimeMillis);
+ try {
+ future.get(execTimeout, execUnit);
+ } catch (ExecutionException ee) {
+ this.logger.error("Reindex thread failed: " + ee.getMessage(), ee);
+ }
+ }
+
+ /**
+ * This method executes an action on the specified node in Solr using its
+ * ACS unique database identifier. The callback handles all the return
+ * values. This is the asynchronous alternative to the other `action`
+ * method.
+ *
+ * This method may block indefinitely when the queue is full. Once all
+ * executions are queued, this will return a single future representing all
+ * the executions.
+ *
+ * @param callback A callback to process multiple returned values from the execution.
+ * @return A reference to the future or active executing task.
+ * @throws InterruptedException The execution was interrupted (server shutdown).
+ */
+ protected Future action(ActionCallback callback) throws InterruptedException {
+ try {
+ return this._action(callback, null);
+ } catch (TimeoutException te) {
+ throw new RuntimeException("This should never happen: " + te.getMessage(), te);
+ }
+ }
+
+ private Future _action(ActionCallback callback, Long fullQueueExpireTimeMillis) throws TimeoutException, InterruptedException {
+ List eligibleInstances = this.findPossibleShardInstances();
+ this.logger.debug("Will attempt to {} {} shard instances", this.getActionName(), eligibleInstances.size());
+
+ CompositeFuture future = new CompositeFuture<>();
+
+ ThrottledThreadPoolExecutor executor = this.executorManager.createThrottled(
+ this.getThreadNamePrefix(),
+ this.getConcurrency(), this.getConcurrency(), this.getConcurrentQueueSize(),
+ 1L, TimeUnit.MINUTES);
+
+ for (final com.inteligr8.alfresco.asie.model.ShardInstance instance : eligibleInstances) {
+ this.logger.trace("Will attempt to {} shard instance: {}", this.getActionName(), instance);
+
+ Callable callable = new Callable<>() {
+ @Override
+ public Void call() {
+ String core = instance.extractShard().getCoreName();
+ SolrHost host = instance.extractNode();
+ URL url = host.toUrl(apiService.isSecure() ? "https" : "http");
+ CoreAdminApi api = apiService.createApi(url.toString(), CoreAdminApi.class);
+
+ try {
+ logger.debug("Performing {} of shard instance: {}", getActionName(), instance);
+ BaseResponse apiResponse = execute(api, core);
+ logger.trace("Performed {} of shard instance: {}", getActionName(), instance);
+
+ Action action = null;
+ if (apiResponse instanceof ActionCoreResponse>) {
+ action = ((ActionCoreResponse) apiResponse).getCores().getByCore(core);
+ } else if (apiResponse instanceof ActionResponse>) {
+ action = ((ActionResponse) apiResponse).getAction();
+ }
+
+ if (action == null) {
+ callback.unknownResult(instance);
+ } else {
+ switch (action.getStatus()) {
+ case Scheduled:
+ callback.scheduled(instance);
+ break;
+ case Success:
+ callback.success(instance);
+ break;
+ default:
+ if (apiResponse instanceof com.inteligr8.alfresco.asie.model.BaseResponse) {
+ com.inteligr8.alfresco.asie.model.BaseResponse asieResponse = (com.inteligr8.alfresco.asie.model.BaseResponse) apiResponse;
+ logger.debug("Performance of {} of shard instance failed: {}: {}", getActionName(), instance, asieResponse.getException());
+ callback.error(instance, asieResponse.getException());
+ } else {
+ logger.debug("Performance of {} of shard instance failed: {}: {}", getActionName(), instance, apiResponse.getResponseHeader().getStatus());
+ callback.error(instance, String.valueOf(apiResponse.getResponseHeader().getStatus()));
+ }
+ }
+ }
+ } catch (Exception e) {
+ logger.error("An exception occurred", e);
+ callback.error(instance, e.getMessage());
+ }
+
+ return null;
+ }
+ };
+
+ if (fullQueueExpireTimeMillis == null) {
+ future.combine(executor.submit(callable, -1L, null));
+ } else {
+ future.combine(executor.submit(callable, fullQueueExpireTimeMillis - System.currentTimeMillis(), TimeUnit.MILLISECONDS));
+ }
+ }
+
+ return future;
+ }
+
+ protected abstract BaseResponse execute(CoreAdminApi api, String core);
+
+ private List findPossibleShardInstances() {
+ if (this.shardRegistry == null)
+ throw new UnsupportedOperationException("ACS instances without a sharding configuration are not yet implemented");
+
+ List instances = new LinkedList<>();
+
+ for (Entry>> floc : this.shardRegistry.getFlocs().entrySet()) {
+ if (!floc.getKey().getStoreRefs().contains(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE))
+ continue;
+ for (Entry> shard : floc.getValue().entrySet()) {
+ for (ShardState shardState : shard.getValue())
+ instances.add(this.toModel(shardState.getShardInstance(), shardState));
+ }
+ }
+
+ this.logger.trace("Despite sharding, considering all shards and nodes: {}", instances);
+
+ return instances;
+ }
+
+ private com.inteligr8.alfresco.asie.model.ShardInstance toModel(ShardInstance instance, ShardState anyShardState) {
+ Floc floc = instance.getShard().getFloc();
+
+ ShardSet shardSet = ShardSet.from(floc, anyShardState);
+ SolrHost host = SolrHost.from(instance);
+ com.inteligr8.alfresco.asie.model.Shard shard = com.inteligr8.alfresco.asie.model.Shard.from(shardSet, instance.getShard().getInstance());
+ return com.inteligr8.alfresco.asie.model.ShardInstance.from(shard, host);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/AbstractNodeActionService.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/AbstractNodeActionService.java
new file mode 100644
index 0000000..8beadc7
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/AbstractNodeActionService.java
@@ -0,0 +1,268 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.alfresco.model.ContentModel;
+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.ShardRegistry;
+import org.alfresco.repo.index.shard.ShardState;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.SearchParameters;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+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 com.inteligr8.alfresco.asie.Constants;
+import com.inteligr8.alfresco.asie.api.CoreAdminApi;
+import com.inteligr8.alfresco.asie.model.ActionCoreResponse;
+import com.inteligr8.alfresco.asie.model.ShardSet;
+import com.inteligr8.alfresco.asie.model.SolrHost;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+import com.inteligr8.alfresco.asie.util.CompositeFuture;
+import com.inteligr8.alfresco.asie.util.ThrottledThreadPoolExecutor;
+import com.inteligr8.solr.model.Action;
+import com.inteligr8.solr.model.ActionResponse;
+import com.inteligr8.solr.model.BaseResponse;
+
+public abstract class AbstractNodeActionService {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Autowired
+ private NamespaceService namespaceService;
+
+ @Autowired
+ private ApiService apiService;
+
+ @Autowired
+ private ExecutorManager executorManager;
+
+ @Autowired(required = false)
+ @Qualifier(Constants.QUALIFIER_ASIE)
+ private ShardRegistry shardRegistry;
+
+ @Value("${inteligr8.asie.default.concurrentQueueSize:64}")
+ private int concurrentQueueSize;
+
+ @Value("${inteligr8.asie.default.concurrency:16}")
+ private int concurrency;
+
+ protected int getConcurrency() {
+ return this.concurrency;
+ }
+
+ protected int getConcurrentQueueSize() {
+ return this.concurrentQueueSize;
+ }
+
+ protected abstract String getThreadNamePrefix();
+
+ protected abstract String getActionName();
+
+ /**
+ * This method executes an action on the specified node in Solr using its
+ * ACS unique database identifier. The callback handles all the return
+ * values. This is the synchronous alternative to the other `action`
+ * method.
+ *
+ * There are two sets of parameters regarding timeouts. The queue timeouts
+ * are for how long the requesting thread should wait for a full queue to
+ * open up space for new executions. The execution timeouts are for how
+ * long the execution should be allowed to take once dequeued. There is no
+ * timeout for how long the execution is queued.
+ *
+ * @param nodeDbId A node database ID.
+ * @param callback A callback to process multiple returned values from the action.
+ * @param fullQueueTimeout A timeout for how long the calling thread should wait for space on the queue.
+ * @param fullQueueUnit The time units for the `queueTimeout`.
+ * @param execTimeout A timeout for the elapsed time the execution should take when dequeued.
+ * @param execUnit The time units for the `execTimeout`.
+ * @throws TimeoutException Either the queue or execution timeout lapsed.
+ * @throws InterruptedException The execution was interrupted (server shutdown).
+ */
+ protected void action(long nodeDbId, ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit, long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ long fullQueueExpireTimeMillis = System.currentTimeMillis() + fullQueueUnit.toMillis(fullQueueTimeout);
+ Future future = this._action(nodeDbId, callback, fullQueueExpireTimeMillis);
+ try {
+ future.get(execTimeout, execUnit);
+ } catch (ExecutionException ee) {
+ this.logger.error("Reindex thread failed: " + ee.getMessage(), ee);
+ }
+ }
+
+ /**
+ * This method executes an action on the specified node in Solr using its
+ * ACS unique database identifier. The callback handles all the return
+ * values. This is the asynchronous alternative to the other `action`
+ * method.
+ *
+ * This method may block indefinitely when the queue is full. Once all
+ * executions are queued, this will return a single future representing all
+ * the executions.
+ *
+ * @param nodeDbId A node database ID.
+ * @param callback A callback to process multiple returned values from the execution.
+ * @return A reference to the future or active executing task.
+ * @throws InterruptedException The execution was interrupted (server shutdown).
+ */
+ protected Future action(long nodeDbId, ActionCallback callback) throws InterruptedException {
+ try {
+ return this._action(nodeDbId, callback, null);
+ } catch (TimeoutException te) {
+ throw new RuntimeException("This should never happen: " + te.getMessage(), te);
+ }
+ }
+
+ private Future _action(long nodeDbId, ActionCallback callback, Long fullQueueExpireTimeMillis) throws TimeoutException, InterruptedException {
+ List eligibleInstances = this.findPossibleShardInstances(nodeDbId);
+ this.logger.debug("Will attempt to {} ACS node against {} shard instances: {}", this.getActionName(), eligibleInstances.size(), nodeDbId);
+
+ CompositeFuture future = new CompositeFuture<>();
+
+ ThrottledThreadPoolExecutor executor = this.executorManager.createThrottled(
+ this.getThreadNamePrefix(),
+ this.getConcurrency(), this.getConcurrency(), this.getConcurrentQueueSize(),
+ 1L, TimeUnit.MINUTES);
+
+ for (final com.inteligr8.alfresco.asie.model.ShardInstance instance : eligibleInstances) {
+ this.logger.trace("Will attempt to {} ACS node against shard instance: {}: {}", this.getActionName(), nodeDbId, instance);
+
+ Callable callable = new Callable<>() {
+ @Override
+ public Void call() {
+ String core = instance.extractShard().getCoreName();
+ SolrHost host = instance.extractNode();
+ URL url = host.toUrl(apiService.isSecure() ? "https" : "http");
+ CoreAdminApi api = apiService.createApi(url.toString(), CoreAdminApi.class);
+
+ try {
+ logger.debug("Performing {} of ACS node against shard instance: {}: {}", getActionName(), nodeDbId, instance);
+ BaseResponse apiResponse = execute(api, core, nodeDbId);
+ logger.trace("Performed {} of ACS node against shard instance: {}: {}", getActionName(), nodeDbId, instance);
+
+ Action action = null;
+ if (apiResponse instanceof ActionCoreResponse>) {
+ action = ((ActionCoreResponse) apiResponse).getCores().getByCore(core);
+ } else if (apiResponse instanceof ActionResponse>) {
+ action = ((ActionResponse) apiResponse).getAction();
+ }
+
+ if (action == null) {
+ callback.unknownResult(instance);
+ } else {
+ switch (action.getStatus()) {
+ case Scheduled:
+ callback.scheduled(instance);
+ break;
+ case Success:
+ callback.success(instance);
+ break;
+ default:
+ if (apiResponse instanceof com.inteligr8.alfresco.asie.model.BaseResponse) {
+ com.inteligr8.alfresco.asie.model.BaseResponse asieResponse = (com.inteligr8.alfresco.asie.model.BaseResponse) apiResponse;
+ logger.debug("Performance of {} of ACS node against shard instance failed: {}: {}: {}", getActionName(), nodeDbId, instance, asieResponse.getException());
+ callback.error(instance, asieResponse.getException());
+ } else {
+ logger.debug("Performance of {} of ACS node against shard instance failed: {}: {}: {}", getActionName(), nodeDbId, instance, apiResponse.getResponseHeader().getStatus());
+ callback.error(instance, String.valueOf(apiResponse.getResponseHeader().getStatus()));
+ }
+ }
+ }
+ } catch (Exception e) {
+ logger.error("An exception occurred", e);
+ callback.error(instance, e.getMessage());
+ }
+
+ return null;
+ }
+ };
+
+ if (fullQueueExpireTimeMillis == null) {
+ future.combine(executor.submit(callable, -1L, null));
+ } else {
+ future.combine(executor.submit(callable, fullQueueExpireTimeMillis - System.currentTimeMillis(), TimeUnit.MILLISECONDS));
+ }
+ }
+
+ return future;
+ }
+
+ protected abstract BaseResponse execute(CoreAdminApi api, String core, long nodeDbId);
+
+ private List findPossibleShardInstances(long nodeDbId) {
+ if (this.shardRegistry == null)
+ throw new UnsupportedOperationException("ACS instances without a sharding configuration are not yet implemented");
+
+ SearchParameters searchParams = new SearchParameters();
+ searchParams.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO);
+ searchParams.setQuery("@" + this.formatForFts(ContentModel.PROP_NODE_DBID) + ":" + nodeDbId);
+
+ List instances = new LinkedList<>();
+
+ List slicedInstances = this.shardRegistry.getIndexSlice(searchParams);
+ if (slicedInstances != null) {
+ this.logger.trace("Due to a sharding method, considering only applicable shards and their ASIE nodes: {}: {}", nodeDbId, slicedInstances);
+
+ for (ShardInstance instance : slicedInstances)
+ instances.add(this.toModel(instance));
+ } else {
+ for (Entry>> floc : this.shardRegistry.getFlocs().entrySet()) {
+ if (!floc.getKey().getStoreRefs().contains(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE))
+ continue;
+ for (Entry> shard : floc.getValue().entrySet()) {
+ for (ShardState shardState : shard.getValue())
+ instances.add(this.toModel(shardState.getShardInstance(), shardState));
+ }
+ }
+
+ this.logger.trace("Despite sharding, considering all shards and nodes: {}: {}", nodeDbId, instances);
+ }
+
+ return instances;
+ }
+
+ private com.inteligr8.alfresco.asie.model.ShardInstance toModel(ShardInstance instance) {
+ // get any random shardState
+ Floc floc = instance.getShard().getFloc();
+ Map> shardsStates = this.shardRegistry.getFlocs().get(floc);
+ if (shardsStates == null)
+ throw new IllegalStateException();
+ Set shardStates = shardsStates.get(instance.getShard());
+ if (shardStates == null || shardStates.isEmpty())
+ throw new IllegalStateException();
+ ShardState anyShardState = shardStates.iterator().next();
+
+ return this.toModel(instance, anyShardState);
+ }
+
+ private com.inteligr8.alfresco.asie.model.ShardInstance toModel(ShardInstance instance, ShardState anyShardState) {
+ Floc floc = instance.getShard().getFloc();
+
+ ShardSet shardSet = ShardSet.from(floc, anyShardState);
+ SolrHost host = SolrHost.from(instance);
+ com.inteligr8.alfresco.asie.model.Shard shard = com.inteligr8.alfresco.asie.model.Shard.from(shardSet, instance.getShard().getInstance());
+ return com.inteligr8.alfresco.asie.model.ShardInstance.from(shard, host);
+ }
+
+ private String formatForFts(QName qname) {
+ return qname.toPrefixString(this.namespaceService).replace("-", "\\-").replace(":", "\\:");
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/AcsReconcileService.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/AcsReconcileService.java
new file mode 100644
index 0000000..048f373
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/AcsReconcileService.java
@@ -0,0 +1,309 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.model.RenditionModel;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeRef.Status;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.QueryConsistency;
+import org.alfresco.service.cmr.search.ResultSet;
+import org.alfresco.service.cmr.search.SearchParameters;
+import org.alfresco.service.cmr.search.SearchService;
+import org.alfresco.service.namespace.NamespaceService;
+import org.alfresco.service.namespace.QName;
+import org.apache.commons.collections4.SetUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.model.ShardInstance;
+import com.inteligr8.alfresco.asie.spi.ReconcileCallback;
+import com.inteligr8.alfresco.asie.spi.ReindexCallback;
+import com.inteligr8.alfresco.asie.util.CompositeFuture;
+import com.inteligr8.alfresco.asie.util.ThrottledThreadPoolExecutor;
+
+@Component
+public class AcsReconcileService implements InitializingBean, DisposableBean {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+ private final Logger reconcileLogger = LoggerFactory.getLogger("inteligr8.asie.reconcile");
+ private final Set ignoreNodesWithAspects = SetUtils.unmodifiableSet(
+ RenditionModel.ASPECT_RENDITION,
+ RenditionModel.ASPECT_RENDITION2);
+
+ @Autowired
+ private NodeService nodeService;
+
+ @Autowired
+ private NamespaceService namespaceService;
+
+ @Autowired
+ private SearchService searchService;
+
+ @Autowired
+ private ReindexService reindexService;
+
+ @Value("${inteligr8.asie.reconciliation.nodesChunkSize:250}")
+ private int nodesChunkSize;
+
+ @Value("${inteligr8.asie.reconciliation.nodeTimeoutSeconds:10}")
+ private int nodeTimeoutSeconds;
+
+ @Value("${inteligr8.asie.reconciliation.concurrentQueueSize:64}")
+ private int concurrentQueueSize;
+
+ @Value("${inteligr8.asie.reconciliation.concurrency:2}")
+ private int concurrency;
+
+ private ThrottledThreadPoolExecutor executor;
+
+ @Override
+ public void afterPropertiesSet() {
+ this.executor = new ThrottledThreadPoolExecutor(this.concurrency, this.concurrency, this.concurrentQueueSize, 1L, TimeUnit.MINUTES, "solr-reconcile");
+ this.executor.prestartAllCoreThreads();
+ }
+
+ @Override
+ public void destroy() {
+ this.executor.shutdown();
+ }
+
+ /**
+ * This method reconciles the specified node range between ACS and Solr.
+ * The node range is specified using the ACS unique database identifiers.
+ * There is no other reasonably efficient attack vector. The callback
+ * handles all the return values. This is the synchronous alternative to
+ * the other `reconcile` method.
+ *
+ * There are two sets of parameters regarding timeouts. The queue timeouts
+ * are for how long the requesting thread should wait for a full queue to
+ * open up space for new re-index executions. The execution timeouts are
+ * for how long the execution should be allowed to take once dequeued.
+ * There is no timeout for how long the execution is queued.
+ *
+ * @param fromDbId A node database ID, inclusive.
+ * @param toDbId A node database ID, exclusive.
+ * @param reindexUnreconciled For nodes not found in Solr, attempt to re-index against all applicable Solr instances.
+ * @param callback A callback to process multiple returned values from the re-index.
+ * @param queueTimeout A timeout for how long the calling thread should wait for space on the queue.
+ * @param queueUnit The time units for the `queueTimeout`.
+ * @param execTimeout A timeout for the elapsed time the reindex execution should take when dequeued.
+ * @param execUnit The time units for the `execTimeout`.
+ * @throws TimeoutException Either the queue or execution timeout lapsed.
+ * @throws InterruptedException The re-index was interrupted (server shutdown).
+ */
+ public void reconcile(
+ long fromDbId, long toDbId, Integer nodesChunkSize,
+ boolean reindexUnreconciled,
+ ReconcileCallback callback,
+ long queueTimeout, TimeUnit queueUnit,
+ long execTimeout, TimeUnit execUnit) throws InterruptedException, TimeoutException {
+ if (nodesChunkSize == null)
+ nodesChunkSize = this.nodesChunkSize;
+ if (this.logger.isTraceEnabled())
+ this.logger.trace("reconcile({}, {}, {}, {}, {}, {})", fromDbId, toDbId, nodesChunkSize, reindexUnreconciled, queueUnit.toMillis(queueTimeout), execUnit.toMillis(execTimeout));
+
+ CompositeFuture future = new CompositeFuture<>();
+
+ for (long startDbId = fromDbId; startDbId < toDbId; startDbId += nodesChunkSize) {
+ long endDbId = Math.min(toDbId, startDbId + nodesChunkSize);
+ future.combine(this.reconcileChunk(startDbId, endDbId, reindexUnreconciled, callback, queueTimeout, queueUnit, execTimeout, execUnit));
+ future.purge(true);
+ }
+
+ try {
+ future.get(execTimeout, execUnit);
+ } catch (ExecutionException ee) {
+ this.logger.error("Reconciliation thread failed: " + ee.getMessage(), ee);
+ }
+ }
+
+ public Future reconcile(
+ long fromDbId, long toDbId, Integer nodesChunkSize,
+ boolean reindexUnreconciled,
+ ReconcileCallback callback) throws InterruptedException {
+ if (nodesChunkSize == null)
+ nodesChunkSize = this.nodesChunkSize;
+ this.logger.trace("reconcile({}, {}, {}, {})", fromDbId, toDbId, nodesChunkSize, reindexUnreconciled);
+
+ CompositeFuture future = new CompositeFuture<>();
+
+ try {
+ for (long startDbId = fromDbId; startDbId < toDbId; startDbId += nodesChunkSize) {
+ long endDbId = Math.min(toDbId, startDbId + nodesChunkSize);
+ future.combine(this.reconcileChunk(startDbId, endDbId, reindexUnreconciled, callback, -1L, null, -1L, null));
+ future.purge(true);
+ }
+ } catch (TimeoutException te) {
+ throw new RuntimeException("This should never happen: " + te.getMessage(), te);
+ }
+
+ return future;
+ }
+
+ protected Future reconcileChunk(
+ long fromDbId, long toDbId,
+ boolean reindexUnreconciled,
+ ReconcileCallback callback,
+ long queueTimeout, TimeUnit queueUnit,
+ long execTimeout, TimeUnit execUnit) throws InterruptedException, TimeoutException {
+ if (this.logger.isTraceEnabled())
+ this.logger.trace("reconcileChunk({}, {}, {}, {}, {})", fromDbId, toDbId, reindexUnreconciled, queueUnit.toMillis(queueTimeout), execUnit.toMillis(execTimeout));
+
+ int dbIdCount = (int) (toDbId - fromDbId);
+
+ SearchParameters searchParams = new SearchParameters();
+ searchParams.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
+ searchParams.setQueryConsistency(QueryConsistency.EVENTUAL); // force Solr
+ searchParams.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO);
+ searchParams.setQuery("@" + this.formatForFts(ContentModel.PROP_NODE_DBID) + ":[" + fromDbId + " TO " + toDbId + ">");
+ searchParams.setMaxItems(dbIdCount);
+ searchParams.setBulkFetchEnabled(false);
+ searchParams.setIncludeMetadata(false);
+
+ // `null`: unknown; or does not exist in DB
+ // `true`: exists in DB and Solr
+ // `false`: exists in DB, but not Solr
+ NodeRef[] nodeRefs = new NodeRef[dbIdCount];
+
+ this.logger.trace("Querying for nodes in chunk: {}", searchParams.getQuery());
+ ResultSet nodes = this.searchService.query(searchParams);
+ this.logger.debug("Found {} of {} possible nodes in chunk: {}-{}", nodes.getNumberFound(), dbIdCount, fromDbId, toDbId);
+ for (NodeRef nodeRef : nodes.getNodeRefs()) {
+ Status nodeStatus = this.nodeService.getNodeStatus(nodeRef);
+ long nodeDbId = nodeStatus.getDbId();
+ if (nodeDbId < fromDbId || nodeDbId >= toDbId) {
+ this.logger.warn("An unexpected DB ID was included in the result set; ignoring: {} != [{}, {})", nodeDbId, fromDbId, toDbId);
+ continue;
+ }
+
+ int dbIdIndex = (int) (nodeDbId - fromDbId);
+ nodeRefs[dbIdIndex] = nodeRef;
+ }
+
+ CompositeFuture future = new CompositeFuture<>();
+
+ for (long _nodeDbId = fromDbId; _nodeDbId < toDbId; _nodeDbId++) {
+ final long nodeDbId = _nodeDbId;
+ this.logger.trace("Attempting to reconcile ACS node: {}", nodeDbId);
+
+ final int dbIdIndex = (int) (nodeDbId - fromDbId);
+ if (nodeRefs[dbIdIndex] != null) {
+ this.logger.trace("A node in the DB is already indexed in Solr: {}: {}", nodeDbId, nodeRefs[dbIdIndex]);
+ this.reconcileLogger.info("RECONCILED: {} <=> {}", nodeDbId, nodeRefs[dbIdIndex]);
+ callback.reconciled(nodeDbId);
+ continue;
+ }
+
+ Callable callable = new Callable() {
+ @Override
+ public Void call() throws InterruptedException, TimeoutException {
+ reconcile(nodeDbId, reindexUnreconciled, callback, execTimeout, execUnit);
+ return null;
+ }
+ };
+
+ if (queueTimeout < 0L) {
+ future.combine(this.executor.submit(callable, -1L, null));
+ } else {
+ future.combine(this.executor.submit(callable, queueTimeout, queueUnit));
+ }
+ }
+
+ return future;
+ }
+
+ public void reconcile(long nodeDbId,
+ boolean reindexUnreconciled,
+ ReconcileCallback callback,
+ long execTimeout, TimeUnit execUnit) throws InterruptedException, TimeoutException {
+ NodeRef nodeRef = this.nodeService.getNodeRef(nodeDbId);
+ if (nodeRef == null) {
+ this.logger.trace("No such ACS node: {}; skipping ...", nodeDbId);
+ return;
+ }
+
+ if (!StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.equals(nodeRef.getStoreRef())) {
+ this.logger.trace("A deliberately ignored store in the DB is not indexed in Solr: {}: {}", nodeDbId, nodeRef);
+ return;
+ }
+
+ Set aspects = this.nodeService.getAspects(nodeRef);
+ aspects.retainAll(this.ignoreNodesWithAspects);
+ if (!aspects.isEmpty()) {
+ this.logger.trace("A deliberately ignored node in the DB is not indexed in Solr: {}: {}: {}", nodeDbId, nodeRef, aspects);
+ return;
+ }
+
+ if (!reindexUnreconciled) {
+ this.logger.debug("A node in the DB is not indexed in Solr: {}: {}", nodeDbId, nodeRef);
+ this.reconcileLogger.info("UNRECONCILED: {} <=> {}", nodeDbId, nodeRef);
+ callback.unreconciled(nodeDbId);
+ } else {
+ logger.debug("A node in the DB is not indexed in Solr; attempt to reindex: {}: {}", nodeDbId, nodeRef);
+ this.reindex(nodeDbId, nodeRef, callback, execTimeout, execUnit);
+ }
+ }
+
+ public void reindex(long nodeDbId, NodeRef nodeRef,
+ ReconcileCallback callback,
+ long execTimeout, TimeUnit execUnit) throws InterruptedException, TimeoutException {
+ Set syncHosts = new HashSet<>();
+ Set asyncHosts = new HashSet<>();
+ Map errorHosts = new HashMap<>();
+
+ ReindexCallback reindexCallback = new ReindexCallback() {
+
+ @Override
+ public void success(ShardInstance instance) {
+ reconcileLogger.info("REINDEXED: {} <=> {}", nodeDbId, nodeRef);
+ syncHosts.add(instance);
+ }
+
+ @Override
+ public void scheduled(ShardInstance instance) {
+ reconcileLogger.info("REINDEXING: {} <=> {}", nodeDbId, nodeRef);
+ asyncHosts.add(instance);
+ }
+
+ @Override
+ public void error(ShardInstance instance, String message) {
+ reconcileLogger.info("UNINDEXED: {} <=> {}", nodeDbId, nodeRef);
+ errorHosts.put(instance, message);
+ }
+ };
+
+ try {
+ if (execTimeout < 0L) {
+ this.reindexService.reindex(nodeDbId, reindexCallback).get();
+ } else {
+ this.reindexService.reindex(nodeDbId, reindexCallback).get(execTimeout, execUnit);
+ }
+ } catch (ExecutionException ee) {
+ throw new RuntimeException("An unexpected exception occurred: " + ee.getMessage(), ee);
+ }
+
+ if (callback != null)
+ callback.processed(nodeDbId, syncHosts, asyncHosts, errorHosts);
+ }
+
+ private String formatForFts(QName qname) {
+ return qname.toPrefixString(this.namespaceService).replace("-", "\\-").replace(":", "\\:");
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/ApiService.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/ApiService.java
new file mode 100644
index 0000000..f4703f3
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/ApiService.java
@@ -0,0 +1,118 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.alfresco.repo.index.shard.ShardInstance;
+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.Value;
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.model.SolrHost;
+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;
+
+@Component
+public class ApiService implements InitializingBean {
+
+ public static final String SOLR_CORE = "alfresco";
+ 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.basePath}")
+ private String solrBaseUrl;
+
+ @Value("${inteligr8.asie.reconciliation.nodesChunkSize:250}")
+ private int nodesChunkSize;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ this.solrSharedSecret = StringUtils.trimToNull(this.solrSharedSecret);
+ }
+
+ public T createApi(String hostname, int port, Class apiClass) {
+ String solrBaseUrl = this.formulateSolrBaseUrl(hostname, port);
+ this.logger.trace("Using Solr base URL: {}", solrBaseUrl);
+ return this.createApi(solrBaseUrl, apiClass);
+ }
+
+ public T createApi(ShardInstance instance, Class apiClass) {
+ URL url = SolrHost.from(instance).toUrl("http");
+ return this.createApi(url.toString(), apiClass);
+ }
+
+ public T createApi(String solrBaseUrl, Class apiClass) {
+ Client solrClient = this.createClient(solrBaseUrl);
+ return this.getApi(solrClient, apiClass);
+ }
+
+ public T getApi(Client solrClient, Class apiClass) {
+ return solrClient.getApi(apiClass);
+ }
+
+ public boolean isSecure() {
+ return "https".equals(this.solrSecureComms);
+ }
+
+ public int getDefaultSolrPort() {
+ return this.isSecure() ? this.solrSslPort : this.solrPort;
+ }
+
+ public 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();
+ }
+
+ public 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.trace("Adding authorization headers for ASIE shared auth: {}", solrSharedSecretHeader);
+ requestContext.getHeaders().putSingle(solrSharedSecretHeader, solrSharedSecret);
+ }
+ };
+ }
+
+ @Override
+ public boolean isDefaultBusEnabled() {
+ return false;
+ }
+ });
+
+ client.register();
+ return client;
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/ExecutorManager.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/ExecutorManager.java
new file mode 100644
index 0000000..d3f09c4
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/ExecutorManager.java
@@ -0,0 +1,138 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.RemovalListener;
+import com.google.common.cache.RemovalNotification;
+import com.inteligr8.alfresco.asie.util.ThrottledThreadPoolExecutor;
+
+/**
+ * This class manages the instantiation and shutdown of `ExecutorService`
+ * instances that are "rarely" used.
+ *
+ * The default implementation of each `ExecutorService` do not support 0 core
+ * threads unless you want undesirable effects. So they hold threads and could
+ * hold them for weeks without being used.
+ *
+ * We will shutdown and dereference the whole `ExecutorService` when there are
+ * no other hard references and after the `keepAliveTime` expires.
+ */
+@Component
+public class ExecutorManager implements InitializingBean, DisposableBean, RemovalListener {
+
+ @Value("${inteligr8.asie.executors.expireTimeInMinutes:30}")
+ private int expireTimeInMinutes;
+
+ private Cache refCache;
+ private Cache expiringCache;
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ // a weak value happens when the executor is no longer referenced
+ // the possible references are by the caller temporarily using and the `expiringCache` (below; so it expired)
+ // this keeps the pool from being shutdown after it expires if the caller is still referencing it
+ // ultimately, if it is cached, it will be in this cache and MAY be in the `expiringCache`.
+ this.refCache = CacheBuilder.newBuilder()
+ .initialCapacity(8)
+ .weakValues()
+ .removalListener(this)
+ .build();
+
+ this.expiringCache = CacheBuilder.newBuilder()
+ .initialCapacity(8)
+ .expireAfterAccess(this.expireTimeInMinutes, TimeUnit.MINUTES)
+ .build();
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ this.refCache.invalidateAll();
+ this.refCache.cleanUp();
+ this.expiringCache.invalidateAll();
+ this.expiringCache.cleanUp();
+ }
+
+ @Override
+ public void onRemoval(RemovalNotification notification) {
+ notification.getValue().shutdown();
+ }
+
+ public ThrottledThreadPoolExecutor createThrottled(
+ String name,
+ int coreThreadPoolSize,
+ int maximumThreadPoolSize,
+ int maximumQueueSize,
+ long keepAliveTime,
+ TimeUnit unit) {
+ return this.createThrottled(name, coreThreadPoolSize, maximumThreadPoolSize, maximumQueueSize, keepAliveTime, unit, null);
+ }
+
+ public ThrottledThreadPoolExecutor createThrottled(
+ final String name,
+ final int coreThreadPoolSize,
+ final int maximumThreadPoolSize,
+ final int maximumQueueSize,
+ final long keepAliveTime,
+ final TimeUnit unit,
+ final RejectedExecutionHandler rejectedExecutionHandler) {
+ try {
+ // if it is already cached, reuse the cache; otherwise create one
+ final ExecutorService executor = this.refCache.get(name, new Callable() {
+ @Override
+ public ThrottledThreadPoolExecutor call() {
+ ThrottledThreadPoolExecutor executor = null;
+ if (rejectedExecutionHandler == null) {
+ executor = new ThrottledThreadPoolExecutor(coreThreadPoolSize, maximumThreadPoolSize, maximumQueueSize,
+ keepAliveTime, unit,
+ name);
+ } else {
+ executor = new ThrottledThreadPoolExecutor(coreThreadPoolSize, maximumThreadPoolSize, maximumQueueSize,
+ keepAliveTime, unit,
+ name,
+ rejectedExecutionHandler);
+ }
+
+ executor.prestartAllCoreThreads();
+ return executor;
+ }
+ });
+
+ return (ThrottledThreadPoolExecutor) this.expiringCache.get(name, new Callable() {
+ @Override
+ public ExecutorService call() throws Exception {
+ return executor;
+ }
+ });
+ } catch (ExecutionException ee) {
+ throw new RuntimeException("This should never happen", ee);
+ }
+ }
+
+ public ExecutorService get(String name) {
+ // grab from the expiring cache first, so we can
+ ExecutorService executor = this.expiringCache.getIfPresent(name);
+ if (executor != null)
+ return executor;
+
+ executor = this.refCache.getIfPresent(name);
+ if (executor == null)
+ return null;
+
+ // the executor expired, but it was still referenced by the caller
+ // re-cache it
+ this.expiringCache.put(name, executor);
+ return executor;
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/FixService.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/FixService.java
new file mode 100644
index 0000000..01e47e2
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/FixService.java
@@ -0,0 +1,43 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.api.CoreAdminApi;
+import com.inteligr8.alfresco.asie.model.ActionCoreResponse;
+import com.inteligr8.alfresco.asie.model.core.FixAction;
+import com.inteligr8.alfresco.asie.model.core.FixRequest;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+
+@Component
+public class FixService extends AbstractActionService {
+
+ @Override
+ protected String getActionName() {
+ return "fix";
+ }
+
+ @Override
+ protected String getThreadNamePrefix() {
+ return "solr-fix";
+ }
+
+ @Override
+ protected ActionCoreResponse execute(CoreAdminApi api, String core) {
+ FixRequest apiRequest = new FixRequest().withCore(core);
+ return api.fix(apiRequest);
+ }
+
+ public Future fix(ActionCallback callback) throws InterruptedException {
+ return super.action(callback);
+ }
+
+ public void fix(ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ super.action(callback, fullQueueTimeout, fullQueueUnit, execTimeout, execUnit);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/PurgeService.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/PurgeService.java
new file mode 100644
index 0000000..ca7cecb
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/PurgeService.java
@@ -0,0 +1,43 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.api.CoreAdminApi;
+import com.inteligr8.alfresco.asie.model.ActionCoreResponse;
+import com.inteligr8.alfresco.asie.model.core.PurgeRequest;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+import com.inteligr8.solr.model.Action;
+
+@Component
+public class PurgeService extends AbstractNodeActionService {
+
+ @Override
+ protected String getActionName() {
+ return "purge";
+ }
+
+ @Override
+ protected String getThreadNamePrefix() {
+ return "solr-purge";
+ }
+
+ @Override
+ protected ActionCoreResponse execute(CoreAdminApi api, String core, long nodeDbId) {
+ PurgeRequest apiRequest = new PurgeRequest().withCore(core).withNodeId(nodeDbId);
+ return api.purge(apiRequest);
+ }
+
+ public Future purge(long nodeDbId, ActionCallback callback) throws InterruptedException {
+ return super.action(nodeDbId, callback);
+ }
+
+ public void purge(long nodeDbId, ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ super.action(nodeDbId, callback, fullQueueTimeout, fullQueueUnit, execTimeout, execUnit);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/ReindexService.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/ReindexService.java
new file mode 100644
index 0000000..ff8374f
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/ReindexService.java
@@ -0,0 +1,43 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.api.CoreAdminApi;
+import com.inteligr8.alfresco.asie.model.ActionCoreResponse;
+import com.inteligr8.alfresco.asie.model.core.ReindexRequest;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+import com.inteligr8.solr.model.Action;
+
+@Component
+public class ReindexService extends AbstractNodeActionService {
+
+ @Override
+ protected String getActionName() {
+ return "re-index";
+ }
+
+ @Override
+ protected String getThreadNamePrefix() {
+ return "solr-reindex";
+ }
+
+ @Override
+ protected ActionCoreResponse execute(CoreAdminApi api, String core, long nodeDbId) {
+ ReindexRequest apiRequest = new ReindexRequest().withCore(core).withNodeId(nodeDbId);
+ return api.reindex(apiRequest);
+ }
+
+ public Future reindex(long nodeDbId, ActionCallback callback) throws InterruptedException {
+ return super.action(nodeDbId, callback);
+ }
+
+ public void reindex(long nodeDbId, ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ super.action(nodeDbId, callback, fullQueueTimeout, fullQueueUnit, execTimeout, execUnit);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/service/RetryService.java b/shared/src/main/java/com/inteligr8/alfresco/asie/service/RetryService.java
new file mode 100644
index 0000000..3edee85
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/service/RetryService.java
@@ -0,0 +1,43 @@
+package com.inteligr8.alfresco.asie.service;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.springframework.stereotype.Component;
+
+import com.inteligr8.alfresco.asie.api.CoreAdminApi;
+import com.inteligr8.alfresco.asie.model.ActionCoreResponse;
+import com.inteligr8.alfresco.asie.model.core.RetryAction;
+import com.inteligr8.alfresco.asie.model.core.RetryRequest;
+import com.inteligr8.alfresco.asie.spi.ActionCallback;
+
+@Component
+public class RetryService extends AbstractActionService {
+
+ @Override
+ protected String getActionName() {
+ return "retry";
+ }
+
+ @Override
+ protected String getThreadNamePrefix() {
+ return "solr-retry";
+ }
+
+ @Override
+ protected ActionCoreResponse execute(CoreAdminApi api, String core) {
+ RetryRequest apiRequest = new RetryRequest().withCore(core);
+ return api.retry(apiRequest);
+ }
+
+ public Future retry(ActionCallback callback) throws InterruptedException {
+ return super.action(callback);
+ }
+
+ public void retry(ActionCallback callback, long fullQueueTimeout, TimeUnit fullQueueUnit,
+ long execTimeout, TimeUnit execUnit) throws TimeoutException, InterruptedException {
+ super.action(callback, fullQueueTimeout, fullQueueUnit, execTimeout, execUnit);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ActionCallback.java b/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ActionCallback.java
new file mode 100644
index 0000000..52d1049
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ActionCallback.java
@@ -0,0 +1,15 @@
+package com.inteligr8.alfresco.asie.spi;
+
+import com.inteligr8.alfresco.asie.model.ShardInstance;
+
+public interface ActionCallback {
+
+ void success(ShardInstance instance);
+
+ void scheduled(ShardInstance instance);
+
+ void error(ShardInstance instance, String message);
+
+ void unknownResult(ShardInstance instance);
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ReconcileCallback.java b/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ReconcileCallback.java
new file mode 100644
index 0000000..e80ee5c
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ReconcileCallback.java
@@ -0,0 +1,19 @@
+package com.inteligr8.alfresco.asie.spi;
+
+import java.util.Map;
+import java.util.Set;
+
+import com.inteligr8.alfresco.asie.model.ShardInstance;
+
+public interface ReconcileCallback {
+
+ void reconciled(long nodeDbId);
+
+ void unreconciled(long nodeDbId);
+
+ void processed(long nodeDbId,
+ Set instsReconciled,
+ Set instsReconciling,
+ Map instsErrorMessages);
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ReindexCallback.java b/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ReindexCallback.java
new file mode 100644
index 0000000..6442259
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/spi/ReindexCallback.java
@@ -0,0 +1,12 @@
+package com.inteligr8.alfresco.asie.spi;
+
+import com.inteligr8.alfresco.asie.model.ShardInstance;
+
+public interface ReindexCallback extends ActionCallback {
+
+ @Override
+ default void unknownResult(ShardInstance instance) {
+ throw new IllegalStateException();
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/util/CompositeFuture.java b/shared/src/main/java/com/inteligr8/alfresco/asie/util/CompositeFuture.java
new file mode 100644
index 0000000..d51319d
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/util/CompositeFuture.java
@@ -0,0 +1,150 @@
+package com.inteligr8.alfresco.asie.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CompositeFuture implements Future {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+ private final Collection> futures = new ConcurrentLinkedQueue<>();
+
+ public MutablePair counts() {
+ MutablePair counts = MutablePair.of(0, 0);
+ for (Future future : this.futures) {
+ if (future.isDone()) {
+ counts.setRight(counts.getRight().intValue() + 1);
+ } else {
+ counts.setLeft(counts.getLeft().intValue() + 1);
+ }
+ }
+
+ return counts;
+ }
+
+ public int countIncomplete() {
+ return this.counts().getLeft().intValue();
+ }
+
+ public int countCompleted() {
+ return this.counts().getRight().intValue();
+ }
+
+ public void combine(Future future) {
+ this.futures.add(future);
+ }
+
+ public void combine(Collection> futures) {
+ this.futures.addAll(futures);
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ boolean cancelled = true;
+ for (Future future : this.futures)
+ if (!future.cancel(mayInterruptIfRunning))
+ cancelled = false;
+
+ return cancelled;
+ }
+
+ public List getList() throws InterruptedException, ExecutionException {
+ List results = new ArrayList<>(this.futures.size());
+ for (Future future : this.futures)
+ results.add(future.get());
+
+ return results;
+ }
+
+ @Override
+ public T get() throws InterruptedException, ExecutionException {
+ this.getList();
+ return null;
+ }
+
+ public List getList(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ long expireTimeMillis = System.currentTimeMillis() + unit.toMillis(timeout);
+
+ List results = new ArrayList<>(this.futures.size());
+ for (Future future : this.futures) {
+ if (future instanceof RunnableFuture>) {
+ this.logger.debug("Waiting {} ms since the start of the exectuion of the future to complete", unit.toMillis(timeout));
+ results.add(((RunnableFuture) future).get(timeout, unit));
+ } else {
+ long remainingTimeMillis = expireTimeMillis - System.currentTimeMillis();
+ this.logger.debug("Waiting {} ms for the future to complete", remainingTimeMillis);
+ results.add(future.get(remainingTimeMillis, TimeUnit.MILLISECONDS));
+ }
+ }
+
+ return results;
+ }
+
+ @Override
+ public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ this.getList(timeout, unit);
+ return null;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ for (Future future : this.futures)
+ if (!future.isCancelled())
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public boolean isDone() {
+ for (Future future : this.futures)
+ if (!future.isDone())
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Remove any futures that are done (or cancelled).
+ *
+ * @param includeCancelled `true` to purge cancelled futures; `false` to purge only completed futures
+ */
+ public void purge(boolean includeCancelled) {
+ List> cfutures = new LinkedList<>();
+ int removedCancelled = 0;
+ int removedDone = 0;
+
+ Iterator> i = this.futures.iterator();
+ while (i.hasNext()) {
+ Future future = i.next();
+ if (future.isCancelled()) {
+ if (includeCancelled) {
+ removedCancelled++;
+ i.remove();
+ }
+ } else if (future.isDone()) {
+ removedDone++;
+ i.remove();
+ } else if (future instanceof CompositeFuture>) {
+ cfutures.add((CompositeFuture>) future);
+ }
+ }
+
+ this.logger.debug("Purged {} cancelled and {} completed futures", removedCancelled, removedDone);
+
+ for (CompositeFuture> cfuture : cfutures)
+ cfuture.purge(includeCancelled);
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/util/ElapsedInterruptableRunnable.java b/shared/src/main/java/com/inteligr8/alfresco/asie/util/ElapsedInterruptableRunnable.java
new file mode 100644
index 0000000..6892acf
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/util/ElapsedInterruptableRunnable.java
@@ -0,0 +1,43 @@
+package com.inteligr8.alfresco.asie.util;
+
+public abstract class ElapsedInterruptableRunnable extends InterruptableRunnable {
+
+ private final Object runLock = new Object();
+ private boolean runOnce = true;
+ private boolean run = false;
+ private Long executionStartTimeMillis;
+ private Long executionEndTimeMillis;
+
+ public void setRunOnce(boolean runOnce) {
+ this.runOnce = runOnce;
+ }
+
+ @Override
+ public final void runInterruptable() throws InterruptedException {
+ synchronized (this.runLock) {
+ if (this.runOnce && this.run)
+ return;
+ this.run = true;
+ }
+
+ this.executionStartTimeMillis = System.currentTimeMillis();
+ try {
+ this.runElapsed();
+ } finally {
+ this.executionEndTimeMillis = System.currentTimeMillis();
+ }
+ }
+
+ protected abstract void runElapsed() throws InterruptedException;
+
+ public Long computeElapsedExecutionMillis() {
+ if (this.executionEndTimeMillis != null) {
+ return this.executionEndTimeMillis - this.executionStartTimeMillis;
+ } else if (this.executionStartTimeMillis != null) {
+ return System.currentTimeMillis() - this.executionStartTimeMillis;
+ } else {
+ return null;
+ }
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/util/InterruptableRunnable.java b/shared/src/main/java/com/inteligr8/alfresco/asie/util/InterruptableRunnable.java
new file mode 100644
index 0000000..f77391d
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/util/InterruptableRunnable.java
@@ -0,0 +1,75 @@
+package com.inteligr8.alfresco.asie.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class InterruptableRunnable implements Runnable {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+ private final Object threadLock = new Object();
+ private boolean run = false;
+ private Thread runThread = null;
+ private boolean completed = false;
+ private boolean interrupted = false;
+
+ @Override
+ public final void run() {
+ synchronized (this.threadLock) {
+ this.run = true;
+ this.runThread = Thread.currentThread();
+ }
+
+ try {
+ this.runInterruptable();
+ this.completed = true;
+ } catch (InterruptedException ie) {
+ this.logger.debug("Runnable interrupted");
+ this.interrupted = true;
+ this.interrupted(ie);
+ } finally {
+ this.runThread = null;
+ }
+ }
+
+ protected abstract void runInterruptable() throws InterruptedException;
+
+ public boolean interrupt() {
+ synchronized (this.threadLock) {
+ if (this.runThread == null)
+ return false;
+
+ this.logger.trace("Runnable interrupting ...");
+ this.runThread.interrupt();
+ }
+
+ return true;
+ }
+
+ protected void interrupted(InterruptedException ie) {
+ }
+
+ public boolean isQueued() {
+ return !this.run;
+ }
+
+ public boolean isInterrupted() {
+ return this.interrupted;
+ }
+
+ public boolean isCompleted() {
+ return this.completed;
+ }
+
+ public boolean isFailed() {
+ synchronized (this.threadLock) {
+ return this.run && !this.completed && !this.interrupted && this.runThread == null;
+ }
+ }
+
+ public boolean isDone() {
+ synchronized (this.threadLock) {
+ return this.run && this.runThread == null;
+ }
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/util/RunnableFuture.java b/shared/src/main/java/com/inteligr8/alfresco/asie/util/RunnableFuture.java
new file mode 100644
index 0000000..fb293aa
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/util/RunnableFuture.java
@@ -0,0 +1,72 @@
+package com.inteligr8.alfresco.asie.util;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RunnableFuture implements Future {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+ private final ThreadPoolExecutor executor;
+ private final WaitableRunnable runnable;
+
+ public RunnableFuture(ThreadPoolExecutor executor, WaitableRunnable runnable) {
+ this.executor = executor;
+ this.runnable = runnable;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ if (this.executor.remove(this.runnable)) {
+ this.logger.debug("Cancelled runnable by removing from queue");
+ return true;
+ } else if (mayInterruptIfRunning && this.runnable.interrupt()) {
+ this.logger.debug("Cancelled runnable by interrupting it");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * This method will not never timeout and will only return or throw when
+ * the runnable is complete or interrupted.
+ *
+ * @return Always `null`.
+ */
+ @Override
+ public T get() throws InterruptedException {
+ this.runnable.waitUntilDone();
+ return null;
+ }
+
+ /**
+ * This method will timeout in the specified amount of time after the
+ * execution commences. This will wait indefinitely as the execution is
+ * queued. Once it is in the executing state, the clock starts. This
+ * alters the default behavior of the `Future` interface.
+ *
+ * @param timeout A positive integer representing a period of time.
+ * @param unit The time unit of the `timeout` parameter.
+ * @return Always `null`.
+ */
+ @Override
+ public T get(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ this.runnable.waitUntilExecutionElapsed(timeout, unit);
+ return null;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return this.runnable.isInterrupted();
+ }
+
+ @Override
+ public boolean isDone() {
+ return this.runnable.isDone();
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/util/ThrottledThreadPoolExecutor.java b/shared/src/main/java/com/inteligr8/alfresco/asie/util/ThrottledThreadPoolExecutor.java
new file mode 100644
index 0000000..dcf1bcf
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/util/ThrottledThreadPoolExecutor.java
@@ -0,0 +1,96 @@
+package com.inteligr8.alfresco.asie.util;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+
+public class ThrottledThreadPoolExecutor extends ThreadPoolExecutor {
+
+ public ThrottledThreadPoolExecutor(
+ int coreThreadPoolSize,
+ int maximumThreadPoolSize,
+ int maximumQueueSize,
+ long keepAliveTime,
+ TimeUnit unit,
+ String threadNamePrefix) {
+ super(coreThreadPoolSize, maximumThreadPoolSize, keepAliveTime, unit,
+ new ArrayBlockingQueue<>(maximumQueueSize),
+ new ThreadFactoryBuilder()
+ .setNameFormat(threadNamePrefix + "-%d")
+ .build());
+ }
+
+ public ThrottledThreadPoolExecutor(
+ int coreThreadPoolSize,
+ int maximumThreadPoolSize,
+ int maximumQueueSize,
+ long keepAliveTime,
+ TimeUnit unit,
+ String threadNamePrefix,
+ RejectedExecutionHandler rejectedExecutionHandler) {
+ super(coreThreadPoolSize, maximumThreadPoolSize, keepAliveTime, unit,
+ new ArrayBlockingQueue<>(maximumQueueSize),
+ new ThreadFactoryBuilder()
+ .setNameFormat(threadNamePrefix + "-%d")
+ .build(),
+ rejectedExecutionHandler);
+ }
+
+ /**
+ * @param timeout Negative to not wait
+ */
+ public RunnableFuture submit(Callable task, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ WaitableRunnable wrunnable = new WaitableRunnable() {
+
+ @Override
+ protected void runWaitable() throws InterruptedException {
+ try {
+ task.call();
+ } catch (InterruptedException ie) {
+ throw ie;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ };
+
+ return new RunnableFuture(this, this.submit(wrunnable, timeout, unit));
+ }
+
+ /**
+ * @param timeout Negative to not wait
+ */
+ public RunnableFuture> submit(Runnable runnable, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ WaitableRunnable wrunnable = new WaitableRunnable() {
+
+ @Override
+ protected void runWaitable() {
+ runnable.run();
+ }
+
+ };
+
+ return new RunnableFuture(this, this.submit(wrunnable, timeout, unit));
+ }
+
+ private WaitableRunnable submit(WaitableRunnable runnable, long throttlingBlockTimeout, TimeUnit throttlingBlockUnit) throws InterruptedException, TimeoutException {
+ // if no core threads are running, the queue won't be monitored for runnables
+ this.prestartAllCoreThreads();
+
+ if (throttlingBlockTimeout < 0L) {
+ this.getQueue().put(runnable);
+ } else {
+ if (!this.getQueue().offer(runnable, throttlingBlockTimeout, throttlingBlockUnit))
+ throw new TimeoutException();
+ }
+
+ return runnable;
+ }
+
+}
diff --git a/shared/src/main/java/com/inteligr8/alfresco/asie/util/WaitableRunnable.java b/shared/src/main/java/com/inteligr8/alfresco/asie/util/WaitableRunnable.java
new file mode 100644
index 0000000..e2783c6
--- /dev/null
+++ b/shared/src/main/java/com/inteligr8/alfresco/asie/util/WaitableRunnable.java
@@ -0,0 +1,60 @@
+package com.inteligr8.alfresco.asie.util;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public abstract class WaitableRunnable extends ElapsedInterruptableRunnable {
+
+ private final Object lock = new Object();
+ private boolean done = false;
+
+ @Override
+ public final void runElapsed() throws InterruptedException {
+ this.done = false;
+ try {
+ this.runWaitable();
+ } finally {
+ synchronized (this.lock) {
+ this.done = true;
+ this.lock.notifyAll();
+ }
+ }
+ }
+
+ protected abstract void runWaitable() throws InterruptedException;
+
+ public void waitUntilDone() throws InterruptedException {
+ synchronized (this.lock) {
+ if (!this.done && !this.isDone())
+ this.lock.wait();
+ }
+ }
+
+ public void waitUntilDone(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ synchronized (this.lock) {
+ if (!this.done && !this.isDone()) {
+ long waitTime = unit.toMillis(timeout);
+ long startTime = System.currentTimeMillis();
+ this.lock.wait(waitTime);
+ if (System.currentTimeMillis() - startTime >= waitTime)
+ throw new TimeoutException();
+ }
+ }
+ }
+
+ public void waitUntilExecutionElapsed(long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
+ synchronized (this.lock) {
+ if (!this.done && !this.isDone()) {
+ while (this.isQueued())
+ Thread.sleep(50L);
+ Long elapsedExecutionMillis = this.computeElapsedExecutionMillis();
+ long waitTime = unit.toMillis(timeout) - elapsedExecutionMillis;
+ long startTime = System.currentTimeMillis();
+ this.lock.wait(waitTime);
+ if (System.currentTimeMillis() - startTime >= waitTime)
+ throw new TimeoutException();
+ }
+ }
+ }
+
+}
diff --git a/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/fix.post.desc.xml b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/fix.post.desc.xml
new file mode 100644
index 0000000..71855e7
--- /dev/null
+++ b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/fix.post.desc.xml
@@ -0,0 +1,46 @@
+
+
+
+ Fix ASIE Indexes
+ Inteligr8 ASIE
+ Issue a 'fix' command to the ASIE indexes.
+ This call will attempt to fix all Solr nodes.
+ The fix operation is asynchronous and could fail on any Solr node without notification.
+ The following response body should be expected in most cases (202 and 500 status codes):
+
+ {
+ "scheduled": [
+ "solrHostAsync:8983/solr",
+ ...
+ ],
+ "error": [
+ "solrHostThatFailed:8983/solr": {
+ "message": "string"
+ },
+ ...
+ ]
+ }
+
+ The following status codes should be expected:
+
+ - 202
+ - Accepted
+
+ ]]>
+
+
+ /inteligr8/asie/acs/fix
+ any
+
+
+ user
+
+
+
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/purgeAcsNode.put.desc.xml b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/purgeAcsNode.put.desc.xml
new file mode 100644
index 0000000..231a589
--- /dev/null
+++ b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/purgeAcsNode.put.desc.xml
@@ -0,0 +1,63 @@
+
+
+
+ Purge ACS Node in ASIE Indexes
+ Inteligr8 ASIE
+ Purge the specified ACS node in the ASIE indexes.
+ This call will attempt to purge the ACS node on all applicable Solr nodes.
+ The purge operation could be synchronous or asynchronous and could fail on any Solr node.
+ If any Solr node failed synchronously in the execution, then expect a status code of 500.
+ The response body will still be identical to the 200/202 status codes.
+ If any Solr node is executing the purge asynchronously and there are no synchronous failures, then expect a status code of 202.
+ The following path parameters are supported:
+
+ - nodeId
+ - An ACS node ID.
+
+ The following response body should be expected in most cases (200, 202, and 500 status codes):
+
+ {
+ "nodeDbId": number,
+ "success": [
+ "solrHostSync:8983/solr",
+ ...
+ ],
+ "scheduled": [
+ "solrHostAsync:8983/solr",
+ ...
+ ],
+ "error": [
+ "solrHostThatFailed:8983/solr": {
+ "message": "string"
+ },
+ ...
+ ]
+ }
+
+ The following status codes should be expected:
+
+ - 200
+ - OK
+ - 202
+ - Accepted
+ - 400
+ - The path or query parameters are invalid
+
+ ]]>
+
+
+ /inteligr8/asie/acs/node/{nodeId}/purge
+ any
+
+
+ user
+
+
+
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/reconcileAcsNodes.post.desc.xml b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/reconcileAcsNodes.post.desc.xml
new file mode 100644
index 0000000..82e3963
--- /dev/null
+++ b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/reconcileAcsNodes.post.desc.xml
@@ -0,0 +1,73 @@
+
+
+
+ Reconcile ACS Nodes against ASIE Indexes
+ Inteligr8 ASIE
+ Reconcile the ACS node range against the ASIE indexes.
+ This call will loop through the specified ACS node range, reconciling each against ASIE indexes.
+ It will discover all unindexed nodes within the range, logging the details with logger `inteligr8.asie.reconcile`.
+ Optionally, an ASIE `REINDEX` command may be issued against ASIE.
+ The following path parameters are supported:
+
+ - fromDbId
+ - A DB ID integer for the starting point of a range, inclusive.
+ - toDbId
+ - A DB ID integer for the ending point of a range, exclusive.
+
+ The following response body should be expected in most cases (200, 202, and 500 status codes):
+
+ {
+ "unreconciled": [
+ number, // node DB ID
+ ...
+ ],
+ "success": {
+ "number": [ // node DB ID
+ "solrHostSync:8983/solr",
+ ...
+ ],
+ ...
+ },
+ "scheduled": {
+ "number": [ // node DB ID
+ "solrHostAsync:8983/solr",
+ ...
+ ],
+ ...
+ },
+ "error": {
+ "number": [ // node DB ID
+ "solrHostThatFailed:8983/solr": {
+ "message": "string"
+ },
+ ...
+ ],
+ ...
+ }
+ }
+
+ The following status codes should be expected:
+
+ - 202
+ - Accepted
+ - 400
+ - The path or query parameters are invalid
+
+ ]]>
+
+
+ /inteligr8/asie/acs/nodes/{fromDbId}/{toDbId}/reconcile?reindex={reindex?}
+ any
+
+
+ user
+
+
+
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/reindexAcsNode.put.desc.xml b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/reindexAcsNode.put.desc.xml
new file mode 100644
index 0000000..1bf205d
--- /dev/null
+++ b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/reindexAcsNode.put.desc.xml
@@ -0,0 +1,63 @@
+
+
+
+ Reindex ACS Node in ASIE Indexes
+ Inteligr8 ASIE
+ Reindex the specified ACS node in the ASIE indexes.
+ This call will attempt to reindex the ACS node on all applicable Solr nodes.
+ The reindex operation could be synchronous or asynchronous and could fail on any Solr node.
+ If any Solr node failed synchronously in the execution, then expect a status code of 500.
+ The response body will still be identical to the 200/202 status codes.
+ If any Solr node is executing the reindex asynchronously and there are no synchronous failures, then expect a status code of 202.
+ The following path parameters are supported:
+
+ - nodeId
+ - An ACS node ID.
+
+ The following response body should be expected in most cases (200, 202, and 500 status codes):
+
+ {
+ "nodeDbId": number,
+ "success": [
+ "solrHostSync:8983/solr",
+ ...
+ ],
+ "scheduled": [
+ "solrHostAsync:8983/solr",
+ ...
+ ],
+ "error": [
+ "solrHostThatFailed:8983/solr": {
+ "message": "string"
+ },
+ ...
+ ]
+ }
+
+ The following status codes should be expected:
+
+ - 200
+ - OK
+ - 202
+ - Accepted
+ - 400
+ - The path or query parameters are invalid
+
+ ]]>
+
+
+ /inteligr8/asie/acs/node/{nodeId}/reindex
+ any
+
+
+ user
+
+
+
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/retry.post.desc.xml b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/retry.post.desc.xml
new file mode 100644
index 0000000..9496fd6
--- /dev/null
+++ b/shared/src/main/resources/alfresco/extension/templates/webscripts/com/inteligr8/alfresco/asie/retry.post.desc.xml
@@ -0,0 +1,46 @@
+
+
+
+ Retry ASIE Indexes
+ Inteligr8 ASIE
+ Issue a 'retry' command to the ASIE indexes.
+ This call will attempt to retry all remembered failed ACS nodes on Solr nodes.
+ The retry operation is asynchronous and could fail on any Solr node without notification.
+ The following response body should be expected in most cases (202 and 500 status codes):
+
+ {
+ "scheduled": [
+ "solrHostAsync:8983/solr",
+ ...
+ ],
+ "error": [
+ "solrHostThatFailed:8983/solr": {
+ "message": "string"
+ },
+ ...
+ ]
+ }
+
+ The following status codes should be expected:
+
+ - 202
+ - Accepted
+
+ ]]>
+
+
+ /inteligr8/asie/acs/retry
+ any
+
+
+ user
+
+
+
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/shared/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-shared/log4j2.properties b/shared/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-shared/log4j2.properties
index 6c345f1..1dd5278 100644
--- a/shared/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-shared/log4j2.properties
+++ b/shared/src/main/resources/alfresco/module/com_inteligr8_alfresco_asie-shared/log4j2.properties
@@ -1,3 +1,6 @@
logger.inteligr8-asie.name=com.inteligr8.alfresco.asie
logger.inteligr8-asie.level=INFO
+
+logger.reconcile.name=inteligr8.asie.reconcile
+logger.reconcile.level=INFO
diff --git a/shared/src/test/java/com/inteligr8/alfresco/asie/util/CompositeFutureUnitTest.java b/shared/src/test/java/com/inteligr8/alfresco/asie/util/CompositeFutureUnitTest.java
new file mode 100644
index 0000000..926d08d
--- /dev/null
+++ b/shared/src/test/java/com/inteligr8/alfresco/asie/util/CompositeFutureUnitTest.java
@@ -0,0 +1,92 @@
+package com.inteligr8.alfresco.asie.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.tuple.Pair;
+import org.junit.Assert;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CompositeFutureUnitTest {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Test
+ public void test() throws InterruptedException, ExecutionException, TimeoutException {
+ ExecutorService executor = Executors.newWorkStealingPool(3);
+
+ CompositeFuture futures = new CompositeFuture();
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(100L)));
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(200L)));
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(300L)));
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(400L)));
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(500L)));
+
+ Thread.sleep(50L);
+
+ Assert.assertEquals(Pair.of(5, 0), futures.counts());
+
+ Thread.sleep(100L);
+ Assert.assertEquals(Pair.of(4, 1), futures.counts());
+
+ Thread.sleep(100L);
+ Assert.assertEquals(Pair.of(3, 2), futures.counts());
+
+ Thread.sleep(100L);
+ Assert.assertEquals(Pair.of(2, 3), futures.counts());
+
+ Thread.sleep(100L);
+ Assert.assertEquals(Pair.of(2, 3), futures.counts());
+
+ Thread.sleep(100L);
+ Assert.assertEquals(Pair.of(1, 4), futures.counts());
+
+ Thread.sleep(200L);
+ Assert.assertEquals(Pair.of(0, 5), futures.counts());
+
+ long startTime = System.currentTimeMillis();
+ futures.get();
+ Assert.assertTrue(System.currentTimeMillis() - startTime < 5);
+
+ startTime = System.currentTimeMillis();
+ futures.get(10L, TimeUnit.SECONDS);
+ Assert.assertTrue(System.currentTimeMillis() - startTime < 5);
+ }
+
+
+
+ private class Pausable implements Callable {
+
+ private static final MutableInt SEQUENCE = new MutableInt(1);
+ private final int seq;
+ private final long pauseInMillis;
+
+ public Pausable(long pauseInMillis) {
+ synchronized (SEQUENCE) {
+ this.seq = SEQUENCE.getAndIncrement();
+ }
+ this.pauseInMillis = pauseInMillis;
+ }
+
+ @Override
+ public Void call() throws InterruptedException {
+ logger.trace("Thread started execution: " + Thread.currentThread().getName() + ": " + this.seq);
+ Thread.sleep(this.pauseInMillis);
+ logger.trace("Thread ending execution: " + Thread.currentThread().getName() + ": " + this.seq);
+ return null;
+ }
+ }
+
+}
diff --git a/shared/src/test/java/com/inteligr8/alfresco/asie/util/ThrottledThreadPoolExecutorUnitTest.java b/shared/src/test/java/com/inteligr8/alfresco/asie/util/ThrottledThreadPoolExecutorUnitTest.java
new file mode 100644
index 0000000..faa87a6
--- /dev/null
+++ b/shared/src/test/java/com/inteligr8/alfresco/asie/util/ThrottledThreadPoolExecutorUnitTest.java
@@ -0,0 +1,146 @@
+package com.inteligr8.alfresco.asie.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.commons.lang3.tuple.Pair;
+import org.junit.Assert;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ThrottledThreadPoolExecutorUnitTest {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Test(expected = RejectedExecutionException.class)
+ public void testHardRejection() throws InterruptedException, ExecutionException, TimeoutException {
+ ThrottledThreadPoolExecutor executor = new ThrottledThreadPoolExecutor(1, 1, 1, 10L, TimeUnit.SECONDS, "not-throttled");
+ try {
+ long startTime = System.currentTimeMillis();
+
+ CompositeFuture futures = new CompositeFuture();
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(100L)));
+
+ // confirm there is no blocking as the pool is filled
+ this.assertElapsed(startTime, 0L, 50L);
+ Assert.assertEquals(Pair.of(1, 0), futures.counts());
+
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(200L)));
+
+ // confirm there is no blocking as the queue is filled
+ this.assertElapsed(startTime, 0L, 50L);
+ Assert.assertEquals(Pair.of(2, 0), futures.counts());
+
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(300L)));
+ } finally {
+ executor.shutdown();
+ Assert.assertTrue(executor.awaitTermination(2L, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void testBlocking() throws InterruptedException, ExecutionException, TimeoutException {
+ ThrottledThreadPoolExecutor executor = new ThrottledThreadPoolExecutor(2, 2, 2, 10L, TimeUnit.SECONDS, "throttled");
+ try {
+ long startTime = System.currentTimeMillis();
+
+ CompositeFuture futures = new CompositeFuture();
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(100L), 1L, TimeUnit.SECONDS));
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(200L), 1L, TimeUnit.SECONDS));
+
+ // confirm there is no blocking as the pool is filled
+ this.assertElapsed(startTime, 0L, 50L);
+ Assert.assertEquals(Pair.of(2, 0), futures.counts());
+
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(300L), 1L, TimeUnit.SECONDS));
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(400L), 1L, TimeUnit.SECONDS));
+
+ // confirm there is no blocking as the queue is filled
+ this.assertElapsed(startTime, 0L, 50L);
+ Assert.assertEquals(Pair.of(4, 0), futures.counts());
+
+ this.logger.trace("Execution submitting");
+ futures.combine(executor.submit(new Pausable(500L), 10L, TimeUnit.SECONDS));
+
+ // confirm there is blocking as the pool and queue are full
+ this.assertElapsed(startTime, 100L, 150L);
+ Assert.assertEquals(Pair.of(4, 1), futures.counts());
+
+ executor.shutdown();
+
+ Thread.sleep(150L); // 250ms
+ Assert.assertEquals(Pair.of(3, 2), futures.counts());
+
+ Thread.sleep(100L); // 350ms
+ Assert.assertEquals(Pair.of(3, 2), futures.counts());
+
+ Thread.sleep(100L); // 450ms
+ Assert.assertEquals(Pair.of(2, 3), futures.counts());
+
+ Thread.sleep(100L); // 550ms
+ Assert.assertEquals(Pair.of(2, 3), futures.counts());
+
+ Thread.sleep(100L); // 650ms
+ Assert.assertEquals(Pair.of(1, 4), futures.counts());
+
+ Thread.sleep(200L); // 850ms
+ Assert.assertEquals(Pair.of(1, 4), futures.counts());
+
+ Thread.sleep(100L); // 950 ms
+ Assert.assertEquals(Pair.of(0, 5), futures.counts());
+
+ startTime = System.currentTimeMillis();
+ futures.get();
+ this.assertElapsed(startTime, 0L, 25L);
+
+ startTime = System.currentTimeMillis();
+ futures.get(10L, TimeUnit.SECONDS);
+ this.assertElapsed(startTime, 0L, 25L);
+ } finally {
+ executor.awaitTermination(2L, TimeUnit.SECONDS);
+ }
+ }
+
+ private void assertElapsed(long startTime, long minRangeInclusive, long maxRangeExclusive) {
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ Assert.assertTrue("The execution time was shorter than expected: " + elapsedTime + " ms < " + minRangeInclusive + " ms", elapsedTime >= minRangeInclusive);
+ Assert.assertTrue("The execution time was longer than expected: " + elapsedTime + " ms >= " + maxRangeExclusive + " ms", elapsedTime < maxRangeExclusive);
+ }
+
+
+
+ private class Pausable implements Callable {
+
+ private static final MutableInt SEQUENCE = new MutableInt(1);
+ private final int seq;
+ private final long pauseInMillis;
+
+ public Pausable(long pauseInMillis) {
+ synchronized (SEQUENCE) {
+ this.seq = SEQUENCE.getAndIncrement();
+ }
+ this.pauseInMillis = pauseInMillis;
+ }
+
+ @Override
+ public Void call() throws InterruptedException {
+ logger.trace("Thread started execution: " + Thread.currentThread().getName() + ": " + this.seq);
+ Thread.sleep(this.pauseInMillis);
+ logger.trace("Thread ending execution: " + Thread.currentThread().getName() + ": " + this.seq);
+ return null;
+ }
+ }
+
+}
diff --git a/shared/src/test/resources/log4j2-test.properties b/shared/src/test/resources/log4j2-test.properties
new file mode 100644
index 0000000..be69a85
--- /dev/null
+++ b/shared/src/test/resources/log4j2-test.properties
@@ -0,0 +1,10 @@
+rootLogger.level=trace
+rootLogger.appenderRef.stdout.ref=STDOUT
+
+logger.this.name=com.inteligr8.alfresco.asie
+logger.this.level=trace
+
+appender.stdout.type=Console
+appender.stdout.name=STDOUT
+appender.stdout.layout.type=PatternLayout
+appender.stdout.layout.pattern=%d{ABSOLUTE_MICROS} %level{length=1} %c{1}: %m%n
diff --git a/solr-api/pom.xml b/solr-api/pom.xml
index 1a3ad92..87702f6 100644
--- a/solr-api/pom.xml
+++ b/solr-api/pom.xml
@@ -15,7 +15,7 @@
1.0.0-solr6
jar
- Apache Solr JAX-RS API
+ Apache Solr Jakarta RS API
2.18.0
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
index 96af2f3..472b147 100644
--- a/solr-api/src/main/java/com/inteligr8/solr/api/CollectionAdminApi.java
+++ b/solr-api/src/main/java/com/inteligr8/solr/api/CollectionAdminApi.java
@@ -1,7 +1,7 @@
package com.inteligr8.solr.api;
import com.inteligr8.solr.model.ActionResponse;
-import com.inteligr8.solr.model.ResponseAction;
+import com.inteligr8.solr.model.Action;
import com.inteligr8.solr.model.collection.AliasesResponse;
import com.inteligr8.solr.model.collection.GetAliasesRequest;
import com.inteligr8.solr.model.core.ReloadRequest;
@@ -17,7 +17,7 @@ public interface CollectionAdminApi {
@GET
@Produces(MediaType.APPLICATION_JSON)
- ActionResponse reload(@BeanParam ReloadRequest request);
+ ActionResponse reload(@BeanParam ReloadRequest request);
@GET
@Produces(MediaType.APPLICATION_JSON)
diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/ResponseAction.java b/solr-api/src/main/java/com/inteligr8/solr/model/Action.java
similarity index 59%
rename from solr-api/src/main/java/com/inteligr8/solr/model/ResponseAction.java
rename to solr-api/src/main/java/com/inteligr8/solr/model/Action.java
index d5d1456..02718a0 100644
--- a/solr-api/src/main/java/com/inteligr8/solr/model/ResponseAction.java
+++ b/solr-api/src/main/java/com/inteligr8/solr/model/Action.java
@@ -2,10 +2,9 @@ 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 class Action {
public enum Status {
@JsonProperty("success")
@@ -16,26 +15,18 @@ public class ResponseAction {
Error,
}
- @JsonProperty(access = Access.READ_ONLY)
+ @JsonProperty
private Status status;
- @JsonProperty(access = Access.READ_ONLY)
+ @JsonProperty
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/ActionResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/ActionResponse.java
index b78ffbc..613058d 100644
--- a/solr-api/src/main/java/com/inteligr8/solr/model/ActionResponse.java
+++ b/solr-api/src/main/java/com/inteligr8/solr/model/ActionResponse.java
@@ -2,12 +2,11 @@ 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 {
+public class ActionResponse extends BaseResponse {
- @JsonProperty(access = Access.READ_ONLY)
+ @JsonProperty
private T action;
public T getAction() {
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
index 1e40276..24847bc 100644
--- a/solr-api/src/main/java/com/inteligr8/solr/model/BaseResponse.java
+++ b/solr-api/src/main/java/com/inteligr8/solr/model/BaseResponse.java
@@ -2,20 +2,15 @@ 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)
+ @JsonProperty(required = true)
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/Cores.java b/solr-api/src/main/java/com/inteligr8/solr/model/Cores.java
new file mode 100644
index 0000000..3e88023
--- /dev/null
+++ b/solr-api/src/main/java/com/inteligr8/solr/model/Cores.java
@@ -0,0 +1,23 @@
+package com.inteligr8.solr.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Cores {
+
+ private Map cores = new HashMap<>();
+
+ public T getByCore(String core) {
+ return cores.get(core);
+ }
+
+ @JsonAnySetter
+ public void addCore(String core, T value) {
+ this.cores.put(core, value);
+ }
+
+}
diff --git a/solr-api/src/main/java/com/inteligr8/solr/model/Metadata.java b/solr-api/src/main/java/com/inteligr8/solr/model/Metadata.java
new file mode 100644
index 0000000..67f11b5
--- /dev/null
+++ b/solr-api/src/main/java/com/inteligr8/solr/model/Metadata.java
@@ -0,0 +1,27 @@
+package com.inteligr8.solr.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Metadata {
+
+ private Map metadata = new HashMap<>();
+
+ public Map getAll() {
+ return this.metadata;
+ }
+
+ public Object getByField(String field) {
+ return this.metadata.get(field);
+ }
+
+ @JsonAnySetter
+ public void setMetadata(String field, Object value) {
+ this.metadata.put(field, value);
+ }
+
+}
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
index 3d76474..bfa9d3f 100644
--- a/solr-api/src/main/java/com/inteligr8/solr/model/ResponseHeader.java
+++ b/solr-api/src/main/java/com/inteligr8/solr/model/ResponseHeader.java
@@ -2,31 +2,22 @@ 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)
+ @JsonProperty(value = "QTime", required = true)
private long executionTimeInMilliseconds;
- @JsonProperty(access = Access.READ_ONLY)
+ @JsonProperty(required = true)
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
deleted file mode 100644
index 361c756..0000000
--- a/solr-api/src/main/java/com/inteligr8/solr/model/TransactionResponseStatus.java
+++ /dev/null
@@ -1,45 +0,0 @@
-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/TransactionStatus.java b/solr-api/src/main/java/com/inteligr8/solr/model/TransactionStatus.java
new file mode 100644
index 0000000..733e3fd
--- /dev/null
+++ b/solr-api/src/main/java/com/inteligr8/solr/model/TransactionStatus.java
@@ -0,0 +1,32 @@
+package com.inteligr8.solr.model;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TransactionStatus {
+
+ @JsonProperty(value = "txInIndexNotInDb")
+ private Map transactionIdToDeletedNodeCount;
+
+ @JsonProperty(value = "duplicatedTx")
+ private Map transactionIdToDuplicateNodeCount;
+
+ @JsonProperty(value = "missingTx")
+ private Map transactionIdToUnindexNodeCount;
+
+ public Map getTransactionIdToDeletedNodeCount() {
+ return transactionIdToDeletedNodeCount;
+ }
+
+ public Map getTransactionIdToDuplicateNodeCount() {
+ return transactionIdToDuplicateNodeCount;
+ }
+
+ public Map getTransactionIdToUnindexNodeCount() {
+ return 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
index 7d132d1..9593e78 100644
--- 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
@@ -4,21 +4,16 @@ 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)
+ @JsonProperty
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/core/Status.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/Status.java
deleted file mode 100644
index 12d50ff..0000000
--- a/solr-api/src/main/java/com/inteligr8/solr/model/core/Status.java
+++ /dev/null
@@ -1,26 +0,0 @@
-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/StatusResponse.java b/solr-api/src/main/java/com/inteligr8/solr/model/core/StatusResponse.java
index a74d76d..7eb4e85 100644
--- 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
@@ -3,19 +3,17 @@ package com.inteligr8.solr.model.core;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.inteligr8.solr.model.BaseResponse;
+import com.inteligr8.solr.model.CoreMetadata;
+import com.inteligr8.solr.model.Cores;
@JsonIgnoreProperties(ignoreUnknown = true)
public class StatusResponse extends BaseResponse {
- @JsonProperty
- private Status status;
+ @JsonProperty(value = "status", required = true)
+ private Cores cores;
- public Status getStatus() {
- return status;
- }
-
- public void setStatus(Status status) {
- this.status = status;
+ public Cores getCores() {
+ return cores;
}
}