refactored; split enterprise from generic
This commit is contained in:
12
shared/.gitignore
vendored
Normal file
12
shared/.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# Maven
|
||||
target
|
||||
pom.xml.versionsBackup
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
.vscode
|
||||
|
||||
# IDEA
|
||||
/.idea/
|
1
shared/README.md
Normal file
1
shared/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# ASIE Platform Module Library
|
73
shared/pom.xml
Normal file
73
shared/pom.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.inteligr8.alfresco</groupId>
|
||||
<artifactId>asie-platform-module-parent</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<relativePath>../</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>asie-shared</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ASIE Shared Library for Platform Modules</name>
|
||||
|
||||
<properties>
|
||||
<alfresco.sdk.version>5.2.0</alfresco.sdk.version>
|
||||
<alfresco.platform.version>23.3.0</alfresco.platform.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>acs-community-packaging</artifactId>
|
||||
<version>${alfresco.platform.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.inteligr8.alfresco</groupId>
|
||||
<artifactId>asie-api</artifactId>
|
||||
<version>1.0-SNAPSHOT-asie2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.inteligr8</groupId>
|
||||
<artifactId>common-rest-client</artifactId>
|
||||
<version>3.0.1-cxf</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Needed by this module, but provided by ACS -->
|
||||
<dependency>
|
||||
<groupId>org.alfresco</groupId>
|
||||
<artifactId>alfresco-repository</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Including for testing purposes only -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>alfresco-public</id>
|
||||
<url>https://artifacts.alfresco.com/nexus/content/groups/public</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
74
shared/rad.ps1
Normal file
74
shared/rad.ps1
Normal file
@@ -0,0 +1,74 @@
|
||||
|
||||
function discoverArtifactId {
|
||||
$script:ARTIFACT_ID=(mvn -q -Dexpression=project"."artifactId -DforceStdout help:evaluate)
|
||||
}
|
||||
|
||||
function rebuild {
|
||||
echo "Rebuilding project ..."
|
||||
mvn process-classes
|
||||
}
|
||||
|
||||
function start_ {
|
||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
||||
mvn -Drad process-classes
|
||||
}
|
||||
|
||||
function start_log {
|
||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
||||
mvn -Drad "-Ddocker.showLogs" process-classes
|
||||
}
|
||||
|
||||
function stop_ {
|
||||
discoverArtifactId
|
||||
echo "Stopping Docker containers that supported rapid application development ..."
|
||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
||||
echo "Stopping containers ..."
|
||||
docker container stop (docker container ls -q --filter name=${ARTIFACT_ID}-*)
|
||||
echo "Removing containers ..."
|
||||
docker container rm (docker container ls -aq --filter name=${ARTIFACT_ID}-*)
|
||||
}
|
||||
|
||||
function tail_logs {
|
||||
param (
|
||||
$container
|
||||
)
|
||||
|
||||
discoverArtifactId
|
||||
docker container logs -f (docker container ls -q --filter name=${ARTIFACT_ID}-${container})
|
||||
}
|
||||
|
||||
function list {
|
||||
discoverArtifactId
|
||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
||||
}
|
||||
|
||||
switch ($args[0]) {
|
||||
"start" {
|
||||
start_
|
||||
}
|
||||
"start_log" {
|
||||
start_log
|
||||
}
|
||||
"stop" {
|
||||
stop_
|
||||
}
|
||||
"restart" {
|
||||
stop_
|
||||
start_
|
||||
}
|
||||
"rebuild" {
|
||||
rebuild
|
||||
}
|
||||
"tail" {
|
||||
tail_logs $args[1]
|
||||
}
|
||||
"containers" {
|
||||
list
|
||||
}
|
||||
default {
|
||||
echo "Usage: .\rad.ps1 [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
|
||||
}
|
||||
}
|
||||
|
||||
echo "Completed!"
|
||||
|
71
shared/rad.sh
Normal file
71
shared/rad.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
discoverArtifactId() {
|
||||
ARTIFACT_ID=`mvn -q -Dexpression=project.artifactId -DforceStdout help:evaluate`
|
||||
}
|
||||
|
||||
rebuild() {
|
||||
echo "Rebuilding project ..."
|
||||
mvn process-classes
|
||||
}
|
||||
|
||||
start() {
|
||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
||||
mvn -Drad process-classes
|
||||
}
|
||||
|
||||
start_log() {
|
||||
echo "Rebuilding project and starting Docker containers to support rapid application development ..."
|
||||
mvn -Drad -Ddocker.showLogs process-classes
|
||||
}
|
||||
|
||||
stop() {
|
||||
discoverArtifactId
|
||||
echo "Stopping Docker containers that supported rapid application development ..."
|
||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
||||
echo "Stopping containers ..."
|
||||
docker container stop `docker container ls -q --filter name=${ARTIFACT_ID}-*`
|
||||
echo "Removing containers ..."
|
||||
docker container rm `docker container ls -aq --filter name=${ARTIFACT_ID}-*`
|
||||
}
|
||||
|
||||
tail_logs() {
|
||||
discoverArtifactId
|
||||
docker container logs -f `docker container ls -q --filter name=${ARTIFACT_ID}-$1`
|
||||
}
|
||||
|
||||
list() {
|
||||
discoverArtifactId
|
||||
docker container ls --filter name=${ARTIFACT_ID}-*
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
start_log)
|
||||
start_log
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
rebuild)
|
||||
rebuild
|
||||
;;
|
||||
tail)
|
||||
tail_logs $2
|
||||
;;
|
||||
containers)
|
||||
list
|
||||
;;
|
||||
*)
|
||||
echo "Usage: ./rad.sh [ start | start_log | stop | restart | rebuild | tail {container} | containers ]"
|
||||
exit 1
|
||||
esac
|
||||
|
||||
echo "Completed!"
|
||||
|
@@ -0,0 +1,23 @@
|
||||
package com.inteligr8.alfresco.asie;
|
||||
|
||||
public interface Constants {
|
||||
|
||||
static final String QUALIFIER_ASIE = "asie";
|
||||
|
||||
// OOTB
|
||||
static final String BEAN_SHARD_STATE_CACHE = "shardStateCache";
|
||||
static final String BEAN_SHARD_GUID_CACHE = "shardToGuidCache";
|
||||
|
||||
static final String BEAN_OFFILINE_SHARD_STATE_CACHE = "offlineShardStateCache";
|
||||
static final String BEAN_CORE_EXPLICIT_CACHE = "coreExplicitIdCache";
|
||||
static final String BEAN_OBJECT_MAPPER = "asie.ObjectMapper";
|
||||
static final String BEAN_ATTRIBUTE_SERVICE = "asie.AttributeService";
|
||||
static final String BEAN_SHARD_REGISTRY = "asie.ShardRegistry";
|
||||
|
||||
static final String ATTR_ASIE = "inteligr8.asie";
|
||||
static final String ATTR_ASIE_NODES = "inteligr8.asie.nodes";
|
||||
static final String ATTR_STATE = "state";
|
||||
static final String ATTR_ONLINE = "online";
|
||||
static final String ATTR_UNLOADED = "unloadedNode.cores";
|
||||
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package com.inteligr8.alfresco.asie.compute;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.service.SolrShardHashService;
|
||||
|
||||
@Component
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
public class SolrShardEnumeratedHashTable extends SolrShardHashTable<Object> {
|
||||
|
||||
/**
|
||||
* For Spring use only
|
||||
*/
|
||||
public SolrShardEnumeratedHashTable(int shards) {
|
||||
super(shards);
|
||||
}
|
||||
|
||||
/**
|
||||
* For non-Spring use
|
||||
*/
|
||||
public SolrShardEnumeratedHashTable(int shards, SolrShardHashService sshs) {
|
||||
super(shards, sshs);
|
||||
}
|
||||
|
||||
public SolrShardEnumeratedHashTable build(Collection<?> objs) {
|
||||
for (Object obj : objs) {
|
||||
int hashed = this.hashForward(obj);
|
||||
|
||||
Set<Object> ns = this.reverseHash.get(hashed);
|
||||
if (ns == null)
|
||||
this.reverseHash.put(hashed, ns = new HashSet<>());
|
||||
ns.add(obj);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public SolrShardEnumeratedHashTable build(Object... objs) {
|
||||
for (Object obj : objs) {
|
||||
int hashed = this.hashForward(obj);
|
||||
|
||||
Set<Object> ns = this.reverseHash.get(hashed);
|
||||
if (ns == null)
|
||||
this.reverseHash.put(hashed, ns = new HashSet<>());
|
||||
ns.add(obj);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package com.inteligr8.alfresco.asie.compute;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.inteligr8.alfresco.asie.service.SolrShardHashService;
|
||||
|
||||
public class SolrShardHashTable<T> {
|
||||
|
||||
private final int shards;
|
||||
protected Map<Integer, Set<T>> reverseHash;
|
||||
|
||||
@Autowired
|
||||
private SolrShardHashService sshs;
|
||||
|
||||
/**
|
||||
* For Spring use only
|
||||
*/
|
||||
public SolrShardHashTable(int shards) {
|
||||
this.shards = shards;
|
||||
this.reverseHash = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* For non-Spring use
|
||||
*/
|
||||
public SolrShardHashTable(int shards, SolrShardHashService sshs) {
|
||||
this(shards);
|
||||
this.sshs = sshs;
|
||||
}
|
||||
|
||||
public int hashForward(Object obj) {
|
||||
return this.sshs.hash(obj, this.shards);
|
||||
}
|
||||
|
||||
public Set<T> hashReverse(int hash) {
|
||||
return this.reverseHash.get(hash);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
package com.inteligr8.alfresco.asie.compute;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.service.SolrShardHashService;
|
||||
|
||||
@Component
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
|
||||
public class SolrShardNumericHashTable extends SolrShardHashTable<Long> {
|
||||
|
||||
/**
|
||||
* For Spring use only
|
||||
*/
|
||||
public SolrShardNumericHashTable(int shards) {
|
||||
super(shards);
|
||||
}
|
||||
|
||||
/**
|
||||
* For non-Spring use
|
||||
*/
|
||||
public SolrShardNumericHashTable(int shards, SolrShardHashService sshs) {
|
||||
super(shards, sshs);
|
||||
}
|
||||
|
||||
public SolrShardNumericHashTable build(long start, long end) {
|
||||
for (long n = start; n <= end; n++) {
|
||||
int hashed = this.hashForward(n);
|
||||
|
||||
Set<Long> ns = this.reverseHash.get(hashed);
|
||||
if (ns == null)
|
||||
this.reverseHash.put(hashed, ns = new HashSet<>());
|
||||
ns.add(n);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
package com.inteligr8.alfresco.asie.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
|
||||
public class Node implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -8834744746109388928L;
|
||||
|
||||
private final String id;
|
||||
private final ShardInstance shardNode;
|
||||
|
||||
public Node(ShardInstance shardNode) {
|
||||
this.shardNode = shardNode;
|
||||
this.id = this.getHostname() + ":" + this.getPort() + this.getPath();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return this.shardNode.getHostName();
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return this.shardNode.getPort();
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
// baseUrl is to the shard; we want to the node, so exclude the core
|
||||
int lastSlash = this.shardNode.getBaseUrl().lastIndexOf('/');
|
||||
return this.shardNode.getBaseUrl().substring(0, lastSlash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof Node))
|
||||
return false;
|
||||
|
||||
Node node = (Node) obj;
|
||||
return this.id.equals(node.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.id.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
package com.inteligr8.alfresco.asie.model;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
public class NodeParameterSet implements RequestParameterSet {
|
||||
|
||||
private String hostname;
|
||||
private int port;
|
||||
|
||||
public NodeParameterSet(String hostname, int port) {
|
||||
this.hostname = hostname;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getHostname() {
|
||||
return hostname;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public InetAddress getAddress() {
|
||||
try {
|
||||
return InetAddress.getByName(this.hostname);
|
||||
} catch (UnknownHostException uhe) {
|
||||
// suppress
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package com.inteligr8.alfresco.asie.model;
|
||||
|
||||
public class NodeShardParameterSet extends NodeParameterSet {
|
||||
|
||||
private ShardSet shardSet;
|
||||
private int shardId;
|
||||
|
||||
public NodeShardParameterSet(String nodeHostname, int nodePort, ShardSet shardSet, int shardId) {
|
||||
super(nodeHostname, nodePort);
|
||||
this.shardSet = shardSet;
|
||||
this.shardId = shardId;
|
||||
}
|
||||
|
||||
public ShardSet getShardSet() {
|
||||
return shardSet;
|
||||
}
|
||||
|
||||
public int getShardId() {
|
||||
return shardId;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
package com.inteligr8.alfresco.asie.model;
|
||||
|
||||
public interface RequestParameterSet {
|
||||
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package com.inteligr8.alfresco.asie.model;
|
||||
|
||||
public class ShardParameterSet implements RequestParameterSet {
|
||||
|
||||
private ShardSet set;
|
||||
private int id;
|
||||
|
||||
public ShardParameterSet(ShardSet set, int id) {
|
||||
this.set = set;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public ShardSet getSet() {
|
||||
return set;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
package com.inteligr8.alfresco.asie.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.alfresco.repo.index.shard.Floc;
|
||||
import org.alfresco.repo.index.shard.ShardMethodEnum;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||
|
||||
public class ShardSet {
|
||||
|
||||
/**
|
||||
* Examples:
|
||||
*
|
||||
* MOD_ACL_ID
|
||||
* ACL_ID
|
||||
* DB_ID
|
||||
* DB_ID_RANGE;range:0-20000
|
||||
* DATE;key:cm:created
|
||||
* DATE;key:cm:created;date.grouping:3
|
||||
* PROPERTY;key:cm:created;regex:^d{4}
|
||||
*/
|
||||
private final Pattern shardSetPattern = Pattern.compile("([A-Z]+)(;fulltext)?(;([a-z]+):([^;]+))?(;([a-z]+):([^;]+))?");
|
||||
|
||||
private final ShardMethodEnum method;
|
||||
private final boolean hasContent;
|
||||
private final Map<String, String> config;
|
||||
private Integer hash;
|
||||
|
||||
public ShardSet(Floc floc, ShardState anyShardNode) {
|
||||
this.method = floc.getShardMethod();
|
||||
this.hasContent = floc.hasContent();
|
||||
this.config = (floc.getPropertyBag().isEmpty() && anyShardNode != null) ? anyShardNode.getPropertyBag() : floc.getPropertyBag();
|
||||
}
|
||||
|
||||
public ShardSet(String shardSetSpec) {
|
||||
Matcher matcher = this.shardSetPattern.matcher(shardSetSpec);
|
||||
if (!matcher.find())
|
||||
throw new IllegalArgumentException("The shard set '" + shardSetSpec + "' is not properly formatted");
|
||||
|
||||
this.method = ShardMethodEnum.valueOf(matcher.group(1));
|
||||
this.hasContent = ";fulltext".equals(matcher.group(2));
|
||||
this.config = new HashMap<>();
|
||||
for (int g = 3; g < matcher.groupCount(); g += 3)
|
||||
if (matcher.group(g) != null)
|
||||
this.config.put("shard." + matcher.group(g+1), matcher.group(g+2));
|
||||
}
|
||||
|
||||
public ShardMethodEnum getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public boolean hasContent() {
|
||||
return hasContent;
|
||||
}
|
||||
|
||||
public String toSpec() {
|
||||
StringBuilder spec = new StringBuilder(this.method.toString());
|
||||
if (this.hasContent)
|
||||
spec.append(";fulltext");
|
||||
for (Entry<String, String> c : this.config.entrySet()) {
|
||||
if (!c.getKey().startsWith("shard."))
|
||||
continue;
|
||||
spec.append(';').append(c.getKey().substring(6)).append(':').append(c.getValue());
|
||||
}
|
||||
return spec.toString();
|
||||
}
|
||||
|
||||
public Map<String, String> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public boolean isFor(ShardState shardState) {
|
||||
return this.method.equals(shardState.getShardInstance().getShard().getFloc().getShardMethod()) &&
|
||||
this.hasContent == shardState.getShardInstance().getShard().getFloc().hasContent() &&
|
||||
this.isConfigurationFor(shardState.getPropertyBag());
|
||||
}
|
||||
|
||||
public boolean isConfigurationFor(Map<String, String> propertyBag) {
|
||||
for (Entry<String, String> config : this.config.entrySet()) {
|
||||
if (config.getValue() == null || !config.getValue().equals(propertyBag.get(config.getKey())))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof ShardSet))
|
||||
return false;
|
||||
|
||||
ShardSet shardSet = (ShardSet) obj;
|
||||
return this.method.equals(shardSet.method) && this.config.equals(shardSet.config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (this.hash == null) {
|
||||
this.hash = new HashCodeBuilder().append(this.method).append(this.hasContent).append(this.config).build();
|
||||
}
|
||||
|
||||
return this.hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.toSpec();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package com.inteligr8.alfresco.asie.provider;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
public abstract class AbstractProvider<T> {
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
public abstract T selectBean();
|
||||
|
||||
protected <U extends T> U getPrimaryOrNamed(Class<U> type, String beanName) {
|
||||
ObjectProvider<U> provider = this.context.getBeanProvider(type);
|
||||
|
||||
// this will select the primary or if there is just one impl, that one impl
|
||||
U u = provider.getIfUnique();
|
||||
if (u == null) {
|
||||
// this will select the named bean; throwing exception if there is none
|
||||
u = this.context.getBean(beanName, type);
|
||||
}
|
||||
|
||||
return u;
|
||||
}
|
||||
|
||||
protected <U extends T> U getPrimary(Class<U> type) {
|
||||
ObjectProvider<U> provider = this.context.getBeanProvider(type);
|
||||
|
||||
// this will select the primary or if there is just one impl, that one impl
|
||||
return provider.getObject();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package com.inteligr8.alfresco.asie.provider;
|
||||
|
||||
import org.alfresco.service.cmr.attributes.AttributeService;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
import com.inteligr8.alfresco.asie.Constants;
|
||||
|
||||
@Configuration
|
||||
public class AttributeServiceProvider extends AbstractProvider<AttributeService> {
|
||||
|
||||
/**
|
||||
* This allows for the selection of the primary or first AttributeService
|
||||
* registered in the Spring BeanFactory. OOTB, there are multiple
|
||||
* AttributeService options and you would typically want
|
||||
* 'attributeService`. But an extension could have a @Primary that we
|
||||
* would want to use instead.
|
||||
*
|
||||
* @return An AttributeService.
|
||||
*/
|
||||
@Bean(Constants.BEAN_ATTRIBUTE_SERVICE)
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
|
||||
public AttributeService selectBean() {
|
||||
return this.getPrimaryOrNamed(AttributeService.class, "attributeService");
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.inteligr8.alfresco.asie.provider;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.inteligr8.alfresco.asie.Constants;
|
||||
|
||||
@Configuration
|
||||
public class ObjectMapperProvider {
|
||||
|
||||
@Bean(Constants.BEAN_OBJECT_MAPPER)
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
|
||||
public ObjectMapper createObjectMapper() {
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
om.registerModule(new JavaTimeModule());
|
||||
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
return om;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
|
||||
import com.inteligr8.alfresco.asie.model.ShardSet;
|
||||
|
||||
public abstract class AbstractAsieNodeShardWebScript extends AbstractAsieShardableWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Override
|
||||
public final void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException {
|
||||
this.logger.trace("execute()");
|
||||
|
||||
String nodeEndpoint = this.getRequiredPathParameter(req, "nodeEndpoint");
|
||||
int colon = nodeEndpoint.lastIndexOf(':');
|
||||
String nodeHostname = colon < 0 ? nodeEndpoint : nodeEndpoint.substring(0, colon);
|
||||
int nodePort = colon < 0 ? this.getDefaultSolrPort() : Integer.parseInt(nodeEndpoint.substring(colon+1));
|
||||
|
||||
ShardSet shardSet = this.getRequiredPathParameter(req, "shardSet", ShardSet.class);
|
||||
int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class);
|
||||
|
||||
this.execute(req, res, nodeHostname, nodePort, shardSet, shardId);
|
||||
}
|
||||
|
||||
protected abstract void execute(WebScriptRequest req, WebScriptResponse res,
|
||||
String nodeHostname, int nodePort, ShardSet shardSet, int shardId)
|
||||
throws IOException;
|
||||
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.inteligr8.alfresco.asie.service.ShardDiscoveryService;
|
||||
|
||||
public abstract class AbstractAsieNodeWebScript extends AbstractAsieShardableWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private ShardDiscoveryService sds;
|
||||
|
||||
@Override
|
||||
public final void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException {
|
||||
this.logger.trace("execute()");
|
||||
|
||||
String nodeEndpoint = this.getRequiredPathParameter(req, "nodeEndpoint");
|
||||
int colon = nodeEndpoint.lastIndexOf(':');
|
||||
String nodeHostname = colon < 0 ? nodeEndpoint : nodeEndpoint.substring(0, colon);
|
||||
nodeHostname = nodeHostname.replace('_', '.');
|
||||
int nodePort = colon < 0 ? this.getDefaultSolrPort() : Integer.parseInt(nodeEndpoint.substring(colon+1));
|
||||
|
||||
this.execute(req, res, nodeHostname, nodePort);
|
||||
}
|
||||
|
||||
protected void execute(WebScriptRequest req, WebScriptResponse res, String nodeHostname, int nodePort) throws IOException {
|
||||
this.logger.trace("execute({}, {})", nodeHostname, nodePort);
|
||||
|
||||
Set<ShardState> shardsOnNode = this.sds.findByNode(nodeHostname, nodePort);
|
||||
if (shardsOnNode == null || shardsOnNode.isEmpty())
|
||||
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE node could not be found");
|
||||
|
||||
this.execute(req, res, shardsOnNode);
|
||||
}
|
||||
|
||||
protected void execute(WebScriptRequest req, WebScriptResponse res, Set<ShardState> registeredNodeShards) throws IOException {
|
||||
this.logger.trace("execute({})", registeredNodeShards.size());
|
||||
// made to be optionally overridden
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.inteligr8.alfresco.asie.model.ShardSet;
|
||||
import com.inteligr8.alfresco.asie.service.ShardDiscoveryService;
|
||||
|
||||
public abstract class AbstractAsieShardWebScript extends AbstractAsieShardableWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private ShardDiscoveryService sds;
|
||||
|
||||
@Override
|
||||
public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException {
|
||||
this.logger.trace("execute()");
|
||||
|
||||
ShardSet shardSet = this.getRequiredPathParameter(req, "shardSet", ShardSet.class);
|
||||
this.logger.debug("Parsed shard set: {}", shardSet);
|
||||
int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class);
|
||||
|
||||
try {
|
||||
Set<ShardState> registeredShardNodes = this.sds.findByShard(shardSet, shardId);
|
||||
if (registeredShardNodes == null || registeredShardNodes.isEmpty())
|
||||
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE shard set or shard could not be found");
|
||||
|
||||
this.execute(req, res, registeredShardNodes);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), iae.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void execute(WebScriptRequest req, WebScriptResponse res, Set<ShardState> registeredShardNodes) throws IOException;
|
||||
|
||||
}
|
@@ -0,0 +1,128 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.Year;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
import org.alfresco.repo.index.shard.ShardRegistry;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.inteligr8.alfresco.asie.Constants;
|
||||
import com.inteligr8.alfresco.asie.api.CoreAdminApi;
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardEnumeratedHashTable;
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardHashTable;
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardNumericHashTable;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardSetInfo;
|
||||
import com.inteligr8.alfresco.asie.service.SolrShardHashService;
|
||||
|
||||
public abstract class AbstractAsieShardableWebScript extends AbstractAsieWebScript {
|
||||
|
||||
private final Pattern sampleTypePattern = Pattern.compile("([A-Za-z]+)([0-9]+)");
|
||||
|
||||
public enum SolrShardHashSampleType {
|
||||
PropertyYear,
|
||||
PropertyQuarter,
|
||||
PropertyMonth,
|
||||
PropertyWeek
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
private ShardRegistry shardRegistry;
|
||||
|
||||
@Autowired
|
||||
private SolrShardHashService sshs;
|
||||
|
||||
protected ShardRegistry getShardRegistry() {
|
||||
return shardRegistry;
|
||||
}
|
||||
|
||||
protected SolrShardHashTable<?> createSampleHashTable(String sampleType) {
|
||||
Matcher matcher = this.sampleTypePattern.matcher(sampleType);
|
||||
if (!matcher.find())
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'sampleType` is not properly formatted");
|
||||
|
||||
try {
|
||||
SolrShardHashSampleType type = SolrShardHashSampleType.valueOf(matcher.group(1));
|
||||
int shards = Integer.parseInt(matcher.group(2));
|
||||
return this.createSampleHashTable(type, shards);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// this should never happen, because of the regex
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'sampleType` number '" + matcher.group(2) + "' is not properly formatted");
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'sampleType` '" + matcher.group(1) + "' is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
protected SolrShardHashTable<?> createSampleHashTable(SolrShardHashSampleType sampleType, int shards) {
|
||||
int thisYear = Year.now().getValue();
|
||||
|
||||
switch (sampleType) {
|
||||
case PropertyYear:
|
||||
return new SolrShardNumericHashTable(shards, this.sshs).build(thisYear-40, thisYear+10);
|
||||
case PropertyQuarter:
|
||||
List<String> quarters = new LinkedList<>();
|
||||
for (int year = thisYear - 15; year <= thisYear + 5; year++)
|
||||
for (int quarter = 1; quarter <= 4; quarter++)
|
||||
quarters.add(year + "-Q" + quarter);
|
||||
return new SolrShardEnumeratedHashTable(shards, this.sshs).build(quarters);
|
||||
case PropertyMonth:
|
||||
NumberFormat monthFormat = NumberFormat.getInstance();
|
||||
monthFormat.setMinimumIntegerDigits(2);
|
||||
|
||||
List<String> months = new LinkedList<>();
|
||||
for (int year = thisYear - 15; year <= thisYear + 5; year++)
|
||||
for (Month month : Month.values())
|
||||
months.add(year + "-" + monthFormat.format(month.getValue()));
|
||||
return new SolrShardEnumeratedHashTable(shards, this.sshs).build(months);
|
||||
case PropertyWeek:
|
||||
List<String> weeks = new LinkedList<>();
|
||||
for (int year = thisYear - 15; year <= thisYear + 5; year++) {
|
||||
int maxWeek = LocalDate.of(year, Month.DECEMBER.getValue(), 31).get(WeekFields.SUNDAY_START.weekOfYear());
|
||||
for (int week = 1; week <= maxWeek; week++)
|
||||
weeks.add(year + "-W" + week);
|
||||
}
|
||||
return new SolrShardEnumeratedHashTable(shards, this.sshs).build(weeks);
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
protected void addShardHashSamples(ShardSetInfo shardSet, ShardInfo shard, SolrShardHashTable<?> sampleHashTable) {
|
||||
Set<Object> existingValues = shardSet.getShardHashSamples().get(shard.getId());
|
||||
if (existingValues == null) {
|
||||
existingValues = new HashSet<>();
|
||||
shardSet.getShardHashSamples().put(shard.getId(), existingValues);
|
||||
}
|
||||
|
||||
Set<?> values = sampleHashTable.hashReverse(shard.getId());
|
||||
if (values != null)
|
||||
existingValues.addAll(values);
|
||||
}
|
||||
|
||||
protected void addShardHashSamples(ShardInfo shard, SolrShardHashTable<?> sampleHashTable) {
|
||||
this.addShardHashSamples(shard.getShardSet(), shard, sampleHashTable);
|
||||
}
|
||||
|
||||
protected int getLatestTxId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected CoreAdminApi getApi(ShardInstance shard) {
|
||||
return this.createApi(shard.getHostName(), shard.getPort());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.extensions.webscripts.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;
|
||||
|
||||
public abstract class AbstractAsieWebScript extends AbstractWebScript implements InitializingBean {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Value("${solr.secureComms}")
|
||||
private String solrSecureComms;
|
||||
|
||||
@Value("${solr.port}")
|
||||
private int solrPort;
|
||||
|
||||
@Value("${solr.port.ssl}")
|
||||
private int solrSslPort;
|
||||
|
||||
@Value("${solr.sharedSecret.header}")
|
||||
private String solrSharedSecretHeader;
|
||||
|
||||
@Value("${solr.sharedSecret}")
|
||||
private String solrSharedSecret;
|
||||
|
||||
@Value("${inteligr8.asie.allowedAuthorities}")
|
||||
private String authorizedAuthoritiesStr;
|
||||
|
||||
@Value("${inteligr8.asie.basePath}")
|
||||
private String solrBaseUrl;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
private Set<String> authorizedAuthorities;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
this.authorizedAuthorities = new HashSet<>();
|
||||
String[] authorities = this.authorizedAuthoritiesStr.split(",");
|
||||
for (String authority : authorities) {
|
||||
authority = StringUtils.trimToNull(authority);
|
||||
if (authority != null)
|
||||
this.authorizedAuthorities.add(authority);
|
||||
}
|
||||
|
||||
if (this.authorizedAuthorities.isEmpty())
|
||||
this.logger.warn("All authenticated users will be authorized to access ASIE web scripts");
|
||||
|
||||
this.solrSharedSecret = StringUtils.trimToNull(this.solrSharedSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getAuthorities() {
|
||||
return this.authorizedAuthorities;
|
||||
}
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,146 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.alfresco.service.cmr.attributes.AttributeService;
|
||||
import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import com.inteligr8.alfresco.asie.Constants;
|
||||
import com.inteligr8.alfresco.asie.api.CoreAdminApi;
|
||||
import com.inteligr8.alfresco.asie.model.NodeParameterSet;
|
||||
import com.inteligr8.alfresco.asie.service.ShardBackupService;
|
||||
import com.inteligr8.alfresco.asie.spi.ShardStateService;
|
||||
import com.inteligr8.solr.model.CoreMetadata;
|
||||
import com.inteligr8.solr.model.core.StatusRequest;
|
||||
import com.inteligr8.solr.model.core.StatusResponse;
|
||||
import com.inteligr8.solr.model.core.UnloadRequest;
|
||||
|
||||
public abstract class AbstractUnregisterNodeWebScript<T extends NodeParameterSet> extends AbstractAsieNodeWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
private AttributeService attrService;
|
||||
|
||||
@Autowired
|
||||
private ShardBackupService sbs;
|
||||
|
||||
@Autowired
|
||||
private ShardStateService sss;
|
||||
|
||||
protected abstract T createParameters(WebScriptRequest req, String nodeHostname, int nodePort);
|
||||
|
||||
@Override
|
||||
protected void execute(WebScriptRequest req, WebScriptResponse res, String nodeHostname, int nodePort)
|
||||
throws IOException {
|
||||
T params = this.createParameters(req, nodeHostname, nodePort);
|
||||
|
||||
final List<Pair<Serializable[], ShardState>> matchingCores = new LinkedList<>();
|
||||
|
||||
AttributeQueryCallback callback = new AttributeQueryCallback() {
|
||||
@Override
|
||||
public boolean handleAttribute(Long id, Serializable value, Serializable[] keys) {
|
||||
ShardState shardState = (ShardState) value;
|
||||
if (!matches(params, shardState))
|
||||
return true;
|
||||
|
||||
matchingCores.add(Pair.of(keys, shardState));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
this.sss.iterate(callback);
|
||||
|
||||
Serializable[] keys = new String[] {
|
||||
Constants.ATTR_ASIE,
|
||||
Constants.ATTR_UNLOADED,
|
||||
nodeHostname + ":" + nodePort
|
||||
};
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> cores = (Map<String, String>) this.attrService.getAttribute(keys);
|
||||
if (cores == null)
|
||||
cores = new HashMap<>();
|
||||
try {
|
||||
for (Pair<Serializable[], ShardState> matchingCore : matchingCores) {
|
||||
ShardState shardNode = matchingCore.getValue();
|
||||
String core = shardNode.getPropertyBag().get("coreName");
|
||||
|
||||
StatusResponse status = this.getCoreStatus(nodeHostname, nodePort, core);
|
||||
if (status == null)
|
||||
throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "This should never happen");
|
||||
CoreMetadata coreMetadata = status.getStatus().getCores().get(core);
|
||||
if (coreMetadata == null || coreMetadata.getName() == null) {
|
||||
this.logger.warn("Registered core does not actually exist on the node host; could be a DNS issue: {}:{}/solr/{}", nodeHostname, nodePort, core);
|
||||
} else {
|
||||
this.unloadCore(nodeHostname, nodePort, core);
|
||||
cores.put(core, coreMetadata.getInstancePath());
|
||||
}
|
||||
|
||||
this.sss.remove(matchingCore.getKey());
|
||||
this.sbs.forget(shardNode);
|
||||
}
|
||||
} finally {
|
||||
// FIXME maybe a separate tx?
|
||||
this.attrService.setAttribute((Serializable) cores, keys);
|
||||
}
|
||||
|
||||
res.setStatus(HttpStatus.OK.value());
|
||||
}
|
||||
|
||||
protected boolean matches(T params, ShardState shardState) {
|
||||
if (!params.getHostname().equalsIgnoreCase(shardState.getShardInstance().getHostName())) {
|
||||
InetAddress nodeAddress = params.getAddress();
|
||||
if (nodeAddress == null)
|
||||
return false;
|
||||
InetAddress shardNodeAddress = resolve(shardState.getShardInstance().getHostName());
|
||||
if (!nodeAddress.equals(shardNodeAddress))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (params.getPort() != shardState.getShardInstance().getPort())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private InetAddress resolve(String hostname) {
|
||||
try {
|
||||
return InetAddress.getByName(hostname);
|
||||
} catch (UnknownHostException uhe) {
|
||||
// suppress
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected StatusResponse getCoreStatus(String nodeHostname, int nodePort, String core) {
|
||||
this.logger.debug("Retrieving status for core {} on ASIE node: {}", core, nodeHostname);
|
||||
CoreAdminApi api = this.createApi(nodeHostname, nodePort);
|
||||
return api.getStatus(new StatusRequest().withCore(core));
|
||||
}
|
||||
|
||||
protected void unloadCore(String nodeHostname, int nodePort, String core) {
|
||||
this.logger.info("Unloading core {} on ASIE node: {}", core, nodeHostname);
|
||||
CoreAdminApi api = this.createApi(nodeHostname, nodePort);
|
||||
api.unload(new UnloadRequest().withCore(core));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.extensions.webscripts.Description.RequiredAuthentication;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import net.sf.acegisecurity.GrantedAuthority;
|
||||
|
||||
public abstract class AbstractWebScript extends org.springframework.extensions.webscripts.AbstractWebScript {
|
||||
|
||||
protected abstract Set<String> getAuthorities();
|
||||
|
||||
@Override
|
||||
public final void execute(WebScriptRequest request, WebScriptResponse response) throws IOException {
|
||||
if (RequiredAuthentication.user.equals(this.getDescription().getRequiredAuthentication())) {
|
||||
if (!this.isAuthorized())
|
||||
throw new WebScriptException(HttpStatus.FORBIDDEN.value(), "You are not authorized for access this resource.");
|
||||
}
|
||||
|
||||
this.executeAuthorized(request, response);
|
||||
}
|
||||
|
||||
protected boolean isAuthorized() {
|
||||
if (this.getAuthorities().contains(AuthenticationUtil.getFullyAuthenticatedUser()))
|
||||
return true;
|
||||
for (GrantedAuthority auth : AuthenticationUtil.getFullAuthentication().getAuthorities()) {
|
||||
if (this.getAuthorities().contains(auth.getAuthority()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void executeAuthorized(WebScriptRequest request, WebScriptResponse response) throws IOException;
|
||||
|
||||
protected String getRequiredPathParameter(WebScriptRequest req, String pathParamName) {
|
||||
String pathParamValue = this.getOptionalPathParameter(req, pathParamName);
|
||||
if (pathParamValue == null)
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The '" + pathParamName + "' path parameter is required");
|
||||
return pathParamValue;
|
||||
}
|
||||
|
||||
protected <T> T getRequiredPathParameter(WebScriptRequest req, String pathParamName, Class<T> type) {
|
||||
T pathParamValue = this.getOptionalPathParameter(req, pathParamName, type);
|
||||
if (pathParamValue == null)
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The '" + pathParamName + "' path parameter is required");
|
||||
return pathParamValue;
|
||||
}
|
||||
|
||||
protected String getOptionalPathParameter(WebScriptRequest req, String pathParamName) {
|
||||
return StringUtils.trimToNull(req.getServiceMatch().getTemplateVars().get(pathParamName));
|
||||
}
|
||||
|
||||
protected <T> T getOptionalPathParameter(WebScriptRequest req, String pathParamName, Class<T> type) {
|
||||
String str = StringUtils.trimToNull(req.getServiceMatch().getTemplateVars().get(pathParamName));
|
||||
return this.getOptionalParameter(req, pathParamName, str, "path element", type);
|
||||
}
|
||||
|
||||
protected String getOptionalQueryParameter(WebScriptRequest req, String queryParamName) {
|
||||
return StringUtils.trimToNull(req.getParameter(queryParamName));
|
||||
}
|
||||
|
||||
protected <T> T getOptionalQueryParameter(WebScriptRequest req, String queryParamName, Class<T> type) {
|
||||
String str = StringUtils.trimToNull(req.getParameter(queryParamName));
|
||||
return this.getOptionalParameter(req, queryParamName, str, "query parameter", type);
|
||||
}
|
||||
|
||||
protected <T> T getOptionalParameter(WebScriptRequest req, String paramName, String paramValue, String paramTypeDisplay, Class<T> type) {
|
||||
if (paramValue == null)
|
||||
return null;
|
||||
if (type.equals(String.class))
|
||||
return type.cast(paramValue);
|
||||
|
||||
try {
|
||||
try {
|
||||
Constructor<T> constructor = type.getConstructor(String.class);
|
||||
return constructor.newInstance(paramValue);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
Method method = type.getDeclaredMethod("valueOf", String.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
T t = (T) method.invoke(null, paramValue);
|
||||
return t;
|
||||
}
|
||||
} catch (InvocationTargetException ite) {
|
||||
if (ite.getTargetException() instanceof NumberFormatException) {
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The `" + paramName + "` " + paramTypeDisplay + " '" + paramValue + "' must be a number");
|
||||
} else {
|
||||
throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected issue occurred", ite);
|
||||
}
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException e) {
|
||||
throw new WebScriptException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An unexpected issue occurred", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.extensions.webscripts.AbstractWebScript;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.service.ShardBackupService;
|
||||
import com.inteligr8.alfresco.asie.spi.ShardStateService;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.registry.delete")
|
||||
public class ClearRegistryWebScript extends AbstractWebScript {
|
||||
|
||||
@Autowired
|
||||
private ShardBackupService sbs;
|
||||
|
||||
@Autowired
|
||||
private ShardStateService sss;
|
||||
|
||||
@Override
|
||||
public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException {
|
||||
this.sss.clear();
|
||||
this.sbs.forget();
|
||||
|
||||
res.setStatus(HttpStatus.OK.value());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.model.Node;
|
||||
import com.inteligr8.alfresco.asie.service.ShardBackupService;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.backupNode.get")
|
||||
public class GetBackupNodeWebScript extends AbstractAsieShardWebScript {
|
||||
|
||||
@Autowired
|
||||
private ShardBackupService sbs;
|
||||
|
||||
@Override
|
||||
public void execute(WebScriptRequest req, WebScriptResponse res, Set<ShardState> shardNodes) throws IOException {
|
||||
if (shardNodes.isEmpty())
|
||||
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE shard state could not be found");
|
||||
|
||||
Node node = this.sbs.fetchNode(shardNodes);
|
||||
|
||||
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
res.setContentEncoding("utf-8");
|
||||
this.getObjectMapper().writeValue(res.getWriter(), node.getId());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.model.Node;
|
||||
import com.inteligr8.alfresco.asie.spi.ShardDiscoveryService;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.leadNode.get")
|
||||
public class GetLeadNodeWebScript extends AbstractAsieShardWebScript {
|
||||
|
||||
@Autowired
|
||||
private ShardDiscoveryService sds;
|
||||
|
||||
@Override
|
||||
public void execute(WebScriptRequest req, WebScriptResponse res, Set<ShardState> shardNodesCache) throws IOException {
|
||||
if (shardNodesCache.isEmpty())
|
||||
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE shard state could not be found");
|
||||
|
||||
ShardInstance latestNode = this.sds.computeLeadShard(shardNodesCache);
|
||||
if (latestNode == null)
|
||||
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The ASIE shard state could not be found");
|
||||
|
||||
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
res.setContentEncoding("utf-8");
|
||||
this.getObjectMapper().writeValue(res.getWriter(), new Node(latestNode).getId());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardHashTable;
|
||||
import com.inteligr8.alfresco.asie.rest.model.NodeInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.NodeShardInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardSetInfo;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.node.get")
|
||||
public class GetNodeWebScript extends AbstractAsieNodeWebScript {
|
||||
|
||||
@Override
|
||||
protected void execute(WebScriptRequest req, WebScriptResponse res, Set<ShardState> registeredNodeShards) throws IOException {
|
||||
ShardState anyRegisteredNodeShard = registeredNodeShards.iterator().next();
|
||||
ShardInstance registeredNode = anyRegisteredNodeShard.getShardInstance();
|
||||
int maxShards = registeredNode.getShard().getFloc().getNumberOfShards();
|
||||
|
||||
SolrShardHashSampleType sampleHashType = this.getOptionalQueryParameter(req, "sampleHashType", SolrShardHashSampleType.class);
|
||||
SolrShardHashTable<?> sampleHashTable = sampleHashType == null ? null : this.createSampleHashTable(sampleHashType, maxShards);
|
||||
|
||||
NodeInfo node = new NodeShardInfo(registeredNode);
|
||||
|
||||
for (ShardState registeredNodeShard : registeredNodeShards) {
|
||||
ShardInfo shard = new ShardInfo();
|
||||
shard.setId(registeredNodeShard.getShardInstance().getShard().getInstance());
|
||||
shard.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(registeredNodeShard.getLastIndexedTxCommitTime()), ZoneOffset.UTC));
|
||||
shard.setTxsCompleted(registeredNodeShard.getLastIndexedTxId());
|
||||
|
||||
shard.setShardSet(new ShardSetInfo(registeredNodeShard.getShardInstance().getShard().getFloc(), registeredNodeShard));
|
||||
if (sampleHashTable != null)
|
||||
this.addShardHashSamples(shard, sampleHashTable);
|
||||
|
||||
node.getShards().put(shard.getId(), shard);
|
||||
}
|
||||
|
||||
res.setContentType("application/json");
|
||||
res.setContentEncoding("utf-8");
|
||||
this.getObjectMapper().writeValue(res.getWriter(), node);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.alfresco.repo.index.shard.Floc;
|
||||
import org.alfresco.repo.index.shard.Shard;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardHashTable;
|
||||
import com.inteligr8.alfresco.asie.model.Node;
|
||||
import com.inteligr8.alfresco.asie.rest.model.NodeInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.NodeShardInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardSetInfo;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.nodes.get")
|
||||
public class GetNodesWebScript extends AbstractAsieShardableWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Override
|
||||
public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException {
|
||||
this.logger.trace("execute()");
|
||||
|
||||
Map<Floc, Map<Shard, Set<ShardState>>> flocs = this.getShardRegistry().getFlocs();
|
||||
if (flocs.isEmpty())
|
||||
throw new WebScriptException(HttpStatus.NO_CONTENT.value(), "There are no ASIE shards registred with ACS");
|
||||
|
||||
SolrShardHashSampleType sampleHashType = this.getOptionalQueryParameter(req, "sampleHashType", SolrShardHashSampleType.class);
|
||||
|
||||
Map<String, NodeInfo> nodes = new TreeMap<>();
|
||||
|
||||
for (Entry<Floc, Map<Shard, Set<ShardState>>> floc : flocs.entrySet()) {
|
||||
int maxShards = floc.getKey().getNumberOfShards();
|
||||
|
||||
SolrShardHashTable<?> sampleHashTable = sampleHashType == null ? null : this.createSampleHashTable(sampleHashType, maxShards);
|
||||
|
||||
for (Entry<Shard, Set<ShardState>> registeredShards : floc.getValue().entrySet()) {
|
||||
for (ShardState registeredShardNode : registeredShards.getValue()) {
|
||||
String nodeId = new Node(registeredShardNode.getShardInstance()).getId();
|
||||
NodeInfo node = nodes.get(nodeId);
|
||||
if (node == null) {
|
||||
node = new NodeShardInfo(registeredShardNode.getShardInstance());
|
||||
nodes.put(node.getId(), node);
|
||||
}
|
||||
|
||||
ShardInfo shard = new ShardInfo(registeredShardNode);
|
||||
shard.setShardSet(new ShardSetInfo(floc.getKey(), registeredShardNode));
|
||||
if (sampleHashTable != null)
|
||||
this.addShardHashSamples(shard, sampleHashTable);
|
||||
node.getShards().put(shard.getId(), shard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.setContentType("application/json");
|
||||
res.setContentEncoding("utf-8");
|
||||
this.getObjectMapper().writeValue(res.getWriter(), nodes);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,150 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.alfresco.repo.index.shard.Floc;
|
||||
import org.alfresco.repo.index.shard.Shard;
|
||||
import org.alfresco.repo.index.shard.ShardMethodEnum;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardHashTable;
|
||||
import com.inteligr8.alfresco.asie.rest.model.NodeInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.PropertyHashShardSetInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardNodeInfo;
|
||||
import com.inteligr8.alfresco.asie.service.ShardDiscoveryService;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.propertyHashShards.get")
|
||||
public class GetPropertyHashShardsWebScript extends AbstractAsieShardableWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private ShardDiscoveryService sds;
|
||||
|
||||
@Override
|
||||
public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException {
|
||||
this.logger.trace("execute()");
|
||||
|
||||
SolrShardHashSampleType sampleHashType = this.getRequiredPathParameter(req, "sampleHashType", SolrShardHashSampleType.class);
|
||||
|
||||
Integer max = this.getOptionalQueryParameter(req, "max", Integer.class);
|
||||
Integer min = this.getOptionalQueryParameter(req, "min", Integer.class);
|
||||
List<String> values = this.getOptionalQueryParameterAsList(req);
|
||||
this.validateParameters(min, max, values);
|
||||
|
||||
List<PropertyHashShardSetInfo> shardSets = new LinkedList<>();
|
||||
|
||||
Collection<Pair<Floc, Map<Shard, Set<ShardState>>>> flocs = this.sds.findByShardMethod(ShardMethodEnum.PROPERTY);
|
||||
if (flocs.isEmpty())
|
||||
throw new WebScriptException(HttpStatus.NO_CONTENT.value(), "There are no property-based shards");
|
||||
|
||||
for (Pair<Floc, Map<Shard, Set<ShardState>>> floc : flocs) {
|
||||
ShardState anyShardNode = this.getAnyShardNode(floc.getSecond());
|
||||
PropertyHashShardSetInfo shardSet = new PropertyHashShardSetInfo(floc.getFirst(), anyShardNode);
|
||||
shardSet.setShards(new TreeMap<>());
|
||||
|
||||
int maxShards = floc.getFirst().getNumberOfShards();
|
||||
SolrShardHashTable<?> sampleHashTable = this.createSampleHashTable(sampleHashType, maxShards);
|
||||
|
||||
Map<Integer, List<Object>> shardToHashMap = new HashMap<>();
|
||||
|
||||
if (max != null && min != null) {
|
||||
for (int i = min; i <= max; i++) {
|
||||
int shardId = sampleHashTable.hashForward(i);
|
||||
this.getAdd(shardToHashMap, shardId, i);
|
||||
}
|
||||
} else if (values != null) {
|
||||
for (String value : values) {
|
||||
int shardId = sampleHashTable.hashForward(value);
|
||||
this.getAdd(shardToHashMap, shardId, value);
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<Shard, Set<ShardState>> shardCache : floc.getSecond().entrySet()) {
|
||||
ShardInfo shard = new ShardInfo();
|
||||
shard.setId(shardCache.getKey().getInstance());
|
||||
shard.setNodes(new HashMap<>());
|
||||
|
||||
for (ShardState shardNodeCache : shardCache.getValue()) {
|
||||
if (shard.getTxsCompleted() == null || shard.getTxsCompleted().longValue() < shardNodeCache.getLastIndexedTxId()) {
|
||||
shard.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(shardNodeCache.getLastIndexedTxCommitTime()), ZoneOffset.UTC));
|
||||
shard.setTxsCompleted(shardNodeCache.getLastIndexedTxId());
|
||||
}
|
||||
|
||||
NodeInfo node = new ShardNodeInfo(shardNodeCache);
|
||||
shard.getNodes().put(node.getId(), node);
|
||||
}
|
||||
|
||||
List<Object> hashedValues = shardToHashMap.get(shard.getId());
|
||||
if (hashedValues != null) for (Object hashedValue : hashedValues)
|
||||
shardSet.getShards().put(hashedValue, shard);
|
||||
}
|
||||
|
||||
shardSets.add(shardSet);
|
||||
}
|
||||
|
||||
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
res.setContentEncoding("utf-8");
|
||||
this.getObjectMapper().writeValue(res.getWriter(), shardSets);
|
||||
}
|
||||
|
||||
private ShardState getAnyShardNode(Map<Shard, Set<ShardState>> shards) {
|
||||
for (Set<ShardState> shardNodes : shards.values())
|
||||
for (ShardState shardNode : shardNodes)
|
||||
return shardNode;
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<String> getOptionalQueryParameterAsList(WebScriptRequest req) {
|
||||
String valuesStr = this.getOptionalQueryParameter(req, "values");
|
||||
if (valuesStr == null)
|
||||
return null;
|
||||
|
||||
String[] values = valuesStr.trim().split("[ ]*[,|][ ]*");
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
private void validateParameters(Integer min, Integer max, List<String> values) {
|
||||
if (max != null && min != null) {
|
||||
if (min > max)
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The `min` parameter value may not be greater than the `max` parameter value");
|
||||
} else if (values != null) {
|
||||
} else {
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "Either `min`/`max` or `values` query parameter is required");
|
||||
}
|
||||
}
|
||||
|
||||
private <K, V> void getAdd(Map<K, List<V>> map, K key, V value) {
|
||||
List<V> values = map.get(key);
|
||||
if (values == null) {
|
||||
values = new LinkedList<>();
|
||||
map.put(key, values);
|
||||
}
|
||||
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,96 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardEnumeratedHashTable;
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardHashTable;
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardNumericHashTable;
|
||||
import com.inteligr8.alfresco.asie.service.SolrShardHashService;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.sampleHashes.get")
|
||||
public class GetSampleHashesWebScript extends AbstractAsieShardableWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private SolrShardHashService sshs;
|
||||
|
||||
@Override
|
||||
public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException {
|
||||
this.logger.trace("execute()");
|
||||
|
||||
int shards = this.getRequiredPathParameter(req, "shards", Integer.class);
|
||||
Integer max = this.getOptionalQueryParameter(req, "max", Integer.class);
|
||||
Integer min = this.getOptionalQueryParameter(req, "min", Integer.class);
|
||||
List<String> values = this.getOptionalQueryParameterAsList(req);
|
||||
this.validateParameters(min, max, values);
|
||||
|
||||
Map<String, Integer> forward = new HashMap<>();
|
||||
Map<Integer, Collection<? extends Object>> reverse = new HashMap<>();
|
||||
|
||||
SolrShardHashTable<?> table = null;
|
||||
if (max != null && min != null) {
|
||||
table = new SolrShardNumericHashTable(shards, this.sshs).build(min, max);
|
||||
for (int i = min; i <= max; i++)
|
||||
forward.put(String.valueOf(i), table.hashForward(i));
|
||||
} else if (values != null) {
|
||||
table = new SolrShardEnumeratedHashTable(shards, this.sshs).build(values);
|
||||
for (String value : values)
|
||||
forward.put(value, table.hashForward(value));
|
||||
}
|
||||
|
||||
for (int s = 0; s < shards; s++) {
|
||||
Set<?> vs = table.hashReverse(s);
|
||||
if (vs != null && !vs.isEmpty()) {
|
||||
reverse.put(s, vs);
|
||||
} else {
|
||||
reverse.put(s, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Map<?, ?>> response = new HashMap<>();
|
||||
response.put("forward", forward);
|
||||
response.put("reverse", reverse);
|
||||
|
||||
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
res.setContentEncoding("utf-8");
|
||||
this.getObjectMapper().writeValue(res.getWriter(), response);
|
||||
}
|
||||
|
||||
private List<String> getOptionalQueryParameterAsList(WebScriptRequest req) {
|
||||
String valuesStr = this.getOptionalQueryParameter(req, "values");
|
||||
if (valuesStr == null)
|
||||
return null;
|
||||
|
||||
String[] values = valuesStr.trim().split("[ ]*[,|][ ]*");
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
private void validateParameters(Integer min, Integer max, List<String> values) {
|
||||
if (max != null && min != null) {
|
||||
if (min > max)
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The `min` parameter value may not be greater than the `max` parameter value");
|
||||
} else if (values != null) {
|
||||
} else {
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "Either `min`/`max` or `values` query parameter is required");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.alfresco.repo.index.shard.Shard;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardHashTable;
|
||||
import com.inteligr8.alfresco.asie.rest.model.NodeInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardNodeInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardSetInfo;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.shard.get")
|
||||
public class GetShardWebScript extends AbstractAsieShardWebScript {
|
||||
|
||||
@Override
|
||||
public void execute(WebScriptRequest req, WebScriptResponse res, Set<ShardState> registeredShardNodes) throws IOException {
|
||||
ShardState aRegisteredShardNode = registeredShardNodes.iterator().next();
|
||||
Shard registeredShard = aRegisteredShardNode.getShardInstance().getShard();
|
||||
int maxShards = registeredShard.getFloc().getNumberOfShards();
|
||||
|
||||
SolrShardHashSampleType sampleHashType = this.getOptionalQueryParameter(req, "sampleHashType", SolrShardHashSampleType.class);
|
||||
SolrShardHashTable<?> sampleHashTable = sampleHashType == null ? null : this.createSampleHashTable(sampleHashType, maxShards);
|
||||
|
||||
ShardInfo shard = new ShardInfo();
|
||||
shard.setId(registeredShard.getInstance());
|
||||
shard.setShardSet(new ShardSetInfo(registeredShard.getFloc(), aRegisteredShardNode));
|
||||
shard.setNodes(new TreeMap<>());
|
||||
if (sampleHashTable != null)
|
||||
this.addShardHashSamples(shard, sampleHashTable);
|
||||
|
||||
for (ShardState registeredShardNode : registeredShardNodes) {
|
||||
if (shard.getTxsCompleted() == null || shard.getTxsCompleted().longValue() < registeredShardNode.getLastIndexedTxId()) {
|
||||
shard.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(registeredShardNode.getLastIndexedTxCommitTime()), ZoneOffset.UTC));
|
||||
shard.setTxsCompleted(registeredShardNode.getLastIndexedTxId());
|
||||
}
|
||||
|
||||
NodeInfo node = new ShardNodeInfo(registeredShardNode);
|
||||
shard.getNodes().put(node.getId(), node);
|
||||
}
|
||||
|
||||
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
res.setContentEncoding("utf-8");
|
||||
this.getObjectMapper().writeValue(res.getWriter(), shard);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.alfresco.repo.index.shard.Floc;
|
||||
import org.alfresco.repo.index.shard.Shard;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.compute.SolrShardHashTable;
|
||||
import com.inteligr8.alfresco.asie.rest.model.NodeInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardNodeInfo;
|
||||
import com.inteligr8.alfresco.asie.rest.model.ShardSetInfo;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.shards.get")
|
||||
public class GetShardsWebScript extends AbstractAsieShardableWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Override
|
||||
public void executeAuthorized(WebScriptRequest req, WebScriptResponse res) throws IOException {
|
||||
this.logger.trace("execute()");
|
||||
|
||||
Map<Floc, Map<Shard, Set<ShardState>>> flocs = this.getShardRegistry().getFlocs();
|
||||
if (flocs.isEmpty())
|
||||
throw new WebScriptException(HttpStatus.NO_CONTENT.value(), "There are no ASIE shards registred with ACS");
|
||||
|
||||
SolrShardHashSampleType sampleHashType = this.getOptionalQueryParameter(req, "sampleHashType", SolrShardHashSampleType.class);
|
||||
|
||||
Map<String, ShardSetInfo> shardSets = new TreeMap<>();
|
||||
|
||||
for (Entry<Floc, Map<Shard, Set<ShardState>>> floc : flocs.entrySet()) {
|
||||
int maxShards = floc.getKey().getNumberOfShards();
|
||||
ShardState anyShardNode = this.getAnyShardNode(floc.getValue());
|
||||
ShardSetInfo shardSet = new ShardSetInfo(floc.getKey(), anyShardNode);
|
||||
shardSet.setShards(new TreeMap<>());
|
||||
|
||||
SolrShardHashTable<?> sampleHashTable = sampleHashType == null ? null : this.createSampleHashTable(sampleHashType, maxShards);
|
||||
|
||||
for (Entry<Shard, Set<ShardState>> registeredShard : floc.getValue().entrySet()) {
|
||||
ShardInfo shard = new ShardInfo();
|
||||
shard.setId(registeredShard.getKey().getInstance());
|
||||
shard.setNodes(new TreeMap<>());
|
||||
|
||||
for (ShardState registeredShardNode : registeredShard.getValue()) {
|
||||
if (shard.getTxsCompleted() == null || shard.getTxsCompleted().longValue() < registeredShardNode.getLastIndexedTxId()) {
|
||||
shard.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(registeredShardNode.getLastIndexedTxCommitTime()), ZoneOffset.UTC));
|
||||
shard.setTxsCompleted(registeredShardNode.getLastIndexedTxId());
|
||||
}
|
||||
|
||||
NodeInfo node = new ShardNodeInfo(registeredShardNode);
|
||||
shard.getNodes().put(node.getId(), node);
|
||||
}
|
||||
|
||||
if (sampleHashTable != null)
|
||||
this.addShardHashSamples(shardSet, shard, sampleHashTable);
|
||||
shardSet.getShards().put(shard.getId(), shard);
|
||||
}
|
||||
|
||||
shardSets.put(shardSet.getMethodSpec(), shardSet);
|
||||
}
|
||||
|
||||
res.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
res.setContentEncoding("utf-8");
|
||||
this.getObjectMapper().writeValue(res.getWriter(), shardSets);
|
||||
}
|
||||
|
||||
private ShardState getAnyShardNode(Map<Shard, Set<ShardState>> shards) {
|
||||
for (Set<ShardState> shardNodes : shards.values())
|
||||
for (ShardState shardNode : shardNodes)
|
||||
return shardNode;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,135 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.alfresco.service.cmr.attributes.AttributeService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.Constants;
|
||||
import com.inteligr8.alfresco.asie.api.CoreAdminApi;
|
||||
import com.inteligr8.solr.model.ExceptionResponse;
|
||||
import com.inteligr8.solr.model.core.CreateRequest;
|
||||
import com.inteligr8.solr.model.core.ReloadRequest;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.InternalServerErrorException;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.nodeShard.post")
|
||||
public class ReloadNodeShardWebScript extends AbstractAsieNodeWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private final Pattern shardRangePattern = Pattern.compile("([0-9]+)-([0-9]+)");
|
||||
|
||||
@Autowired
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
private AttributeService attrService;
|
||||
|
||||
@Override
|
||||
protected void execute(WebScriptRequest req, WebScriptResponse res, final String nodeHostname, final int nodePort)
|
||||
throws IOException {
|
||||
String shardCore = this.getRequiredPathParameter(req, "shardCore");
|
||||
int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class);
|
||||
String coreName = shardCore + "-" + shardId;
|
||||
|
||||
Serializable[] keys = new String[] {
|
||||
Constants.ATTR_ASIE,
|
||||
Constants.ATTR_UNLOADED,
|
||||
nodeHostname + ":" + nodePort
|
||||
};
|
||||
|
||||
Map<String, String> cores = this.fetchUnloadedCores(keys);
|
||||
if (!cores.containsKey(coreName)) {
|
||||
cores.putAll(this.fetchOtherCores(req));
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
String coreInstancePath = cores.get(coreName);
|
||||
if (coreInstancePath == null)
|
||||
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The specified node/shard could not be found or formulated");
|
||||
|
||||
this.logger.info("Reloading core {} on ASIE node: {}", coreName, nodeHostname);
|
||||
CoreAdminApi api = this.createApi(nodeHostname, nodePort);
|
||||
try {
|
||||
api.create(new CreateRequest()
|
||||
.withCore(coreName)
|
||||
.withConfigDirectory(coreInstancePath));
|
||||
} catch (BadRequestException bre) {
|
||||
this.logger.warn("Core {} does not exist on ASIE node: {}; forgetting it", coreName, nodeHostname);
|
||||
cores.remove(coreName);
|
||||
changed = true;
|
||||
} catch (InternalServerErrorException isee) {
|
||||
ExceptionResponse response = isee.getResponse().readEntity(ExceptionResponse.class);
|
||||
if (response.getError() == null || response.getError().getMessage() == null || !response.getError().getMessage().endsWith(" already exists."))
|
||||
throw isee;
|
||||
|
||||
this.logger.warn("Core {} was already loaded on ASIE node: {}; reloading ...", coreName, nodeHostname);
|
||||
api.reload(new ReloadRequest()
|
||||
.withCore(coreName));
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this.attrService.setAttribute((Serializable) cores, keys);
|
||||
|
||||
res.setStatus(HttpStatus.OK.value());
|
||||
}
|
||||
|
||||
private Map<String, String> fetchUnloadedCores(Serializable[] keys) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> cores = (Map<String, String>) this.attrService.getAttribute(keys);
|
||||
if (cores == null)
|
||||
cores = new HashMap<String, String>();
|
||||
return cores;
|
||||
}
|
||||
|
||||
private Map<String, String> fetchOtherCores(WebScriptRequest req) {
|
||||
String coreName = this.getOptionalQueryParameter(req, "coreName");
|
||||
if (coreName == null)
|
||||
coreName = this.getOptionalQueryParameter(req, "core");
|
||||
String shardRange = this.getOptionalQueryParameter(req, "shardRange");
|
||||
Short shardCount = this.getOptionalQueryParameter(req, "shardCount", Short.class);
|
||||
if (coreName == null || shardRange == null || shardCount == null)
|
||||
return Collections.emptyMap();
|
||||
|
||||
String template = this.getOptionalQueryParameter(req, "template");
|
||||
if (template == null)
|
||||
template = "rerank";
|
||||
Short nodeId = this.getOptionalQueryParameter(req, "nodeId", Short.class);
|
||||
if (nodeId == null)
|
||||
nodeId = 1;
|
||||
Short nodeCount = this.getOptionalQueryParameter(req, "nodeCount", Short.class);
|
||||
if (nodeCount == null)
|
||||
nodeCount = 2;
|
||||
|
||||
String baseConfigDirectory = template + "--" + coreName + "--shards--" + shardCount + "-x-1--node--" + nodeId + "-of-" + nodeCount;
|
||||
|
||||
Matcher matcher = this.shardRangePattern.matcher(shardRange);
|
||||
if (!matcher.find())
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'shardRange' query parameter value is not formatted as expected");
|
||||
|
||||
Map<String, String> cores = new HashMap<>();
|
||||
|
||||
short startShardId = Short.parseShort(matcher.group(1));
|
||||
short endShardId = Short.parseShort(matcher.group(2));
|
||||
for (short shardId = startShardId; shardId <= endShardId; shardId++) {
|
||||
String shardConfigDirectory = baseConfigDirectory + "/" + coreName + "-" + shardId;
|
||||
cores.put(coreName, shardConfigDirectory);
|
||||
}
|
||||
|
||||
return cores;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.alfresco.service.cmr.attributes.AttributeService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.extensions.webscripts.WebScriptException;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.extensions.webscripts.WebScriptResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.Constants;
|
||||
import com.inteligr8.alfresco.asie.api.CoreAdminApi;
|
||||
import com.inteligr8.solr.model.ExceptionResponse;
|
||||
import com.inteligr8.solr.model.core.CreateRequest;
|
||||
import com.inteligr8.solr.model.core.ReloadRequest;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.InternalServerErrorException;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.node.post")
|
||||
public class ReloadNodeWebScript extends AbstractAsieNodeWebScript {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
private final Pattern shardRangePattern = Pattern.compile("([0-9]+)-([0-9]+)");
|
||||
|
||||
@Autowired
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
private AttributeService attrService;
|
||||
|
||||
@Override
|
||||
protected void execute(WebScriptRequest req, WebScriptResponse res, final String nodeHostname, final int nodePort)
|
||||
throws IOException {
|
||||
Serializable[] keys = new String[] {
|
||||
Constants.ATTR_ASIE,
|
||||
Constants.ATTR_UNLOADED,
|
||||
nodeHostname + ":" + nodePort
|
||||
};
|
||||
|
||||
Map<String, String> cores = this.fetchUnloadedCores(keys);
|
||||
cores.putAll(this.fetchOtherCores(req));
|
||||
if (cores.isEmpty())
|
||||
throw new WebScriptException(HttpStatus.NOT_FOUND.value(), "The specified node was not found or formulated");
|
||||
|
||||
boolean changed = false;
|
||||
|
||||
Iterator<Entry<String, String>> i = cores.entrySet().iterator();
|
||||
while (i.hasNext()) {
|
||||
Entry<String, String> core = i.next();
|
||||
String coreName = core.getKey();
|
||||
String coreInstancePath = core.getValue();
|
||||
|
||||
this.logger.info("Reloading core {} on ASIE node: {}", coreName, nodeHostname);
|
||||
CoreAdminApi api = this.createApi(nodeHostname, nodePort);
|
||||
try {
|
||||
api.create(new CreateRequest()
|
||||
.withCore(coreName)
|
||||
.withConfigDirectory(coreInstancePath));
|
||||
} catch (BadRequestException bre) {
|
||||
this.logger.warn("Core {} does not exist on ASIE node: {}; forgetting it", coreName, nodeHostname);
|
||||
i.remove();
|
||||
changed = true;
|
||||
} catch (InternalServerErrorException isee) {
|
||||
ExceptionResponse response = isee.getResponse().readEntity(ExceptionResponse.class);
|
||||
if (response.getError() == null || response.getError().getMessage() == null || !response.getError().getMessage().endsWith(" already exists."))
|
||||
throw isee;
|
||||
|
||||
this.logger.info("Core {} was already loaded on ASIE node: {}; reloading ...", coreName, nodeHostname);
|
||||
api.reload(new ReloadRequest()
|
||||
.withCore(coreName));
|
||||
}
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this.attrService.setAttribute((Serializable) cores, keys);
|
||||
|
||||
res.setStatus(HttpStatus.OK.value());
|
||||
}
|
||||
|
||||
private Map<String, String> fetchUnloadedCores(Serializable[] keys) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String> cores = (Map<String, String>) this.attrService.getAttribute(keys);
|
||||
if (cores == null)
|
||||
cores = new HashMap<String, String>();
|
||||
return cores;
|
||||
}
|
||||
|
||||
private Map<String, String> fetchOtherCores(WebScriptRequest req) {
|
||||
String coreName = this.getOptionalQueryParameter(req, "coreName");
|
||||
if (coreName == null)
|
||||
coreName = this.getOptionalQueryParameter(req, "core");
|
||||
String shardRange = this.getOptionalQueryParameter(req, "shardRange");
|
||||
Short shardCount = this.getOptionalQueryParameter(req, "shardCount", Short.class);
|
||||
if (coreName == null || shardRange == null || shardCount == null)
|
||||
return Collections.emptyMap();
|
||||
|
||||
String template = this.getOptionalQueryParameter(req, "template");
|
||||
if (template == null)
|
||||
template = "rerank";
|
||||
Short nodeId = this.getOptionalQueryParameter(req, "nodeId", Short.class);
|
||||
if (nodeId == null)
|
||||
nodeId = 1;
|
||||
Short nodeCount = this.getOptionalQueryParameter(req, "nodeCount", Short.class);
|
||||
if (nodeCount == null)
|
||||
nodeCount = 2;
|
||||
|
||||
String baseConfigDirectory = template + "--" + coreName + "--shards--" + shardCount + "-x-1--node--" + nodeId + "-of-" + nodeCount;
|
||||
|
||||
Matcher matcher = this.shardRangePattern.matcher(shardRange);
|
||||
if (!matcher.find())
|
||||
throw new WebScriptException(HttpStatus.BAD_REQUEST.value(), "The 'shardRange' query parameter value is not formatted as expected");
|
||||
|
||||
Map<String, String> cores = new HashMap<>();
|
||||
|
||||
short startShardId = Short.parseShort(matcher.group(1));
|
||||
short endShardId = Short.parseShort(matcher.group(2));
|
||||
for (short shardId = startShardId; shardId <= endShardId; shardId++) {
|
||||
String shardConfigDirectory = baseConfigDirectory + "/" + coreName + "-" + shardId;
|
||||
cores.put(coreName, shardConfigDirectory);
|
||||
}
|
||||
|
||||
return cores;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.model.NodeShardParameterSet;
|
||||
import com.inteligr8.alfresco.asie.model.ShardSet;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.nodeShard.delete")
|
||||
public class UnloadNodeShardWebScript extends AbstractUnregisterNodeWebScript<NodeShardParameterSet> {
|
||||
|
||||
@Override
|
||||
protected NodeShardParameterSet createParameters(WebScriptRequest req, String nodeHostname, int nodePort) {
|
||||
ShardSet shardSet = this.getRequiredPathParameter(req, "shardSet", ShardSet.class);
|
||||
int shardId = this.getRequiredPathParameter(req, "shardId", Integer.class);
|
||||
|
||||
return new NodeShardParameterSet(nodeHostname, nodePort, shardSet, shardId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matches(NodeShardParameterSet params, ShardState shardState) {
|
||||
if (!params.getShardSet().isFor(shardState))
|
||||
return false;
|
||||
if (params.getShardId() != shardState.getShardInstance().getShard().getInstance())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.inteligr8.alfresco.asie.rest;
|
||||
|
||||
import org.springframework.extensions.webscripts.WebScriptRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.model.NodeParameterSet;
|
||||
|
||||
@Component(value = "webscript.com.inteligr8.alfresco.asie.node.delete")
|
||||
public class UnloadNodeWebScript extends AbstractUnregisterNodeWebScript<NodeParameterSet> {
|
||||
|
||||
@Override
|
||||
protected NodeParameterSet createParameters(WebScriptRequest req, String nodeHostname, int nodePort) {
|
||||
return new NodeParameterSet(nodeHostname, nodePort);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
package com.inteligr8.alfresco.asie.rest.model;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.inteligr8.alfresco.asie.model.Node;
|
||||
|
||||
public abstract class NodeInfo {
|
||||
|
||||
@JsonProperty
|
||||
private String id;
|
||||
|
||||
@JsonProperty
|
||||
private Map<Integer, ShardInfo> shards;
|
||||
|
||||
public NodeInfo() {
|
||||
}
|
||||
|
||||
public NodeInfo(ShardInstance nodeCache) {
|
||||
this.setId(new Node(nodeCache).getId());
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Map<Integer, ShardInfo> getShards() {
|
||||
if (shards == null)
|
||||
shards = new TreeMap<>();
|
||||
return shards;
|
||||
}
|
||||
|
||||
public void setShards(Map<Integer, ShardInfo> shards) {
|
||||
this.shards = shards;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package com.inteligr8.alfresco.asie.rest.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class NodeShardInfo extends NodeInfo {
|
||||
|
||||
@JsonProperty
|
||||
private Map<Integer, ShardInfo> shards;
|
||||
|
||||
public NodeShardInfo() {
|
||||
}
|
||||
|
||||
public NodeShardInfo(ShardInstance nodeCache) {
|
||||
super(nodeCache);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
package com.inteligr8.alfresco.asie.rest.model;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.index.shard.Floc;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.inteligr8.alfresco.asie.model.ShardSet;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class PropertyHashShardSetInfo {
|
||||
|
||||
@JsonProperty
|
||||
private String methodSpec;
|
||||
|
||||
@JsonProperty
|
||||
private int shardCount;
|
||||
|
||||
@JsonProperty
|
||||
private Map<Object, ShardInfo> shards;
|
||||
|
||||
public PropertyHashShardSetInfo() {
|
||||
}
|
||||
|
||||
public PropertyHashShardSetInfo(Floc floc, ShardState anyShardNode) {
|
||||
ShardSet shardSet = new ShardSet(floc, anyShardNode);
|
||||
this.setMethodSpec(shardSet.toSpec());
|
||||
this.setShardCount(floc.getNumberOfShards());
|
||||
}
|
||||
|
||||
public String getMethodSpec() {
|
||||
return this.methodSpec;
|
||||
}
|
||||
|
||||
public void setMethodSpec(String methodSpec) {
|
||||
this.methodSpec = methodSpec;
|
||||
}
|
||||
|
||||
public int getShardCount() {
|
||||
return shardCount;
|
||||
}
|
||||
|
||||
public void setShardCount(int shardCount) {
|
||||
this.shardCount = shardCount;
|
||||
}
|
||||
|
||||
public Map<Object, ShardInfo> getShards() {
|
||||
return shards;
|
||||
}
|
||||
|
||||
public void setShards(Map<Object, ShardInfo> shards) {
|
||||
this.shards = shards;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,97 @@
|
||||
package com.inteligr8.alfresco.asie.rest.model;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ShardInfo {
|
||||
|
||||
@JsonProperty
|
||||
private int id;
|
||||
|
||||
@JsonProperty
|
||||
private Long txsCompleted;
|
||||
|
||||
@JsonProperty
|
||||
private Long txsRemaining;
|
||||
|
||||
@JsonProperty
|
||||
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
|
||||
private OffsetDateTime latestTx;
|
||||
|
||||
@JsonProperty
|
||||
private Map<String, NodeInfo> nodes;
|
||||
|
||||
@JsonProperty
|
||||
private ShardSetInfo shardSet;
|
||||
|
||||
public ShardInfo() {
|
||||
}
|
||||
|
||||
public ShardInfo(ShardState shard) {
|
||||
this.setId(shard.getShardInstance().getShard().getInstance());
|
||||
this.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(shard.getLastIndexedTxCommitTime()), ZoneOffset.UTC));
|
||||
this.setTxsCompleted(shard.getLastIndexedTxId());
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getTxsCompleted() {
|
||||
return txsCompleted;
|
||||
}
|
||||
|
||||
public void setTxsCompleted(Long txsCompleted) {
|
||||
this.txsCompleted = txsCompleted;
|
||||
}
|
||||
|
||||
public Long getTxsRemaining() {
|
||||
return txsRemaining;
|
||||
}
|
||||
|
||||
public void setTxsRemaining(Long txsRemaining) {
|
||||
this.txsRemaining = txsRemaining;
|
||||
}
|
||||
|
||||
public OffsetDateTime getLatestTx() {
|
||||
return latestTx;
|
||||
}
|
||||
|
||||
public void setLatestTx(OffsetDateTime latestTx) {
|
||||
this.latestTx = latestTx;
|
||||
}
|
||||
|
||||
public Map<String, NodeInfo> getNodes() {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public void setNodes(Map<String, NodeInfo> nodes) {
|
||||
this.nodes = nodes;
|
||||
}
|
||||
|
||||
public ShardSetInfo getShardSet() {
|
||||
return shardSet;
|
||||
}
|
||||
|
||||
public void setShardSet(ShardSetInfo shardSet) {
|
||||
this.shardSet = shardSet;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
package com.inteligr8.alfresco.asie.rest.model;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ShardNodeInfo extends NodeInfo {
|
||||
|
||||
@JsonProperty
|
||||
private Long txsCompleted;
|
||||
|
||||
@JsonProperty
|
||||
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssXXX")
|
||||
private OffsetDateTime latestTx;
|
||||
|
||||
public ShardNodeInfo() {
|
||||
}
|
||||
|
||||
public ShardNodeInfo(ShardState shard) {
|
||||
super(shard.getShardInstance());
|
||||
this.setLatestTx(OffsetDateTime.ofInstant(Instant.ofEpochMilli(shard.getLastIndexedTxCommitTime()), ZoneOffset.UTC));
|
||||
this.setTxsCompleted(shard.getLastIndexedTxId());
|
||||
}
|
||||
|
||||
public Long getTxsCompleted() {
|
||||
return txsCompleted;
|
||||
}
|
||||
|
||||
public void setTxsCompleted(Long txsCompleted) {
|
||||
this.txsCompleted = txsCompleted;
|
||||
}
|
||||
|
||||
public OffsetDateTime getLatestTx() {
|
||||
return latestTx;
|
||||
}
|
||||
|
||||
public void setLatestTx(OffsetDateTime latestTx) {
|
||||
this.latestTx = latestTx;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package com.inteligr8.alfresco.asie.rest.model;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.alfresco.repo.index.shard.Floc;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.inteligr8.alfresco.asie.model.ShardSet;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonInclude(Include.NON_EMPTY)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ShardSetInfo {
|
||||
|
||||
@JsonProperty
|
||||
private String methodSpec;
|
||||
|
||||
@JsonProperty
|
||||
private int shardCount;
|
||||
|
||||
@JsonProperty
|
||||
private Map<Integer, ShardInfo> shards;
|
||||
|
||||
@JsonProperty
|
||||
private Map<Integer, Set<Object>> shardHashSamples = new TreeMap<>();
|
||||
|
||||
public ShardSetInfo() {
|
||||
}
|
||||
|
||||
public ShardSetInfo(Floc floc, ShardState anyShardNode) {
|
||||
ShardSet shardSet = new ShardSet(floc, anyShardNode);
|
||||
this.methodSpec = shardSet.toSpec();
|
||||
this.setShardCount(floc.getNumberOfShards());
|
||||
}
|
||||
|
||||
public String getMethodSpec() {
|
||||
return this.methodSpec;
|
||||
}
|
||||
|
||||
public void setMethodSpec(String methodSpec) {
|
||||
this.methodSpec = methodSpec;
|
||||
}
|
||||
|
||||
public int getShardCount() {
|
||||
return shardCount;
|
||||
}
|
||||
|
||||
public void setShardCount(int shardCount) {
|
||||
this.shardCount = shardCount;
|
||||
}
|
||||
|
||||
public Map<Integer, ShardInfo> getShards() {
|
||||
return shards;
|
||||
}
|
||||
|
||||
public void setShards(Map<Integer, ShardInfo> shards) {
|
||||
this.shards = shards;
|
||||
}
|
||||
|
||||
public Map<Integer, Set<Object>> getShardHashSamples() {
|
||||
return shardHashSamples;
|
||||
}
|
||||
|
||||
public void setShardHashSamples(Map<Integer, Set<Object>> shardHashSamples) {
|
||||
this.shardHashSamples = shardHashSamples;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
package com.inteligr8.alfresco.asie.service;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.alfresco.repo.index.shard.Shard;
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.alfresco.service.cmr.attributes.AttributeService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.Constants;
|
||||
import com.inteligr8.alfresco.asie.model.Node;
|
||||
import com.inteligr8.alfresco.asie.spi.ShardDiscoveryService;
|
||||
|
||||
@Component
|
||||
public class ShardBackupService implements com.inteligr8.alfresco.asie.spi.ShardBackupService {
|
||||
|
||||
private static final String ATTR_BACKUP_NODE = "backupNode";
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
private ShardDiscoveryService sds;
|
||||
|
||||
@Autowired
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
private AttributeService attributeService;
|
||||
|
||||
@Value("${inteligr8.asie.backup.persistTimeMinutes}")
|
||||
private int persistTimeMinutes;
|
||||
|
||||
public Node fetchNode(Collection<ShardState> shardNodes) {
|
||||
if (shardNodes.isEmpty())
|
||||
return null;
|
||||
|
||||
ShardState shardNode0 = shardNodes.iterator().next();
|
||||
ShardInstance node0Shard = shardNode0.getShardInstance();
|
||||
Shard shard = node0Shard.getShard();
|
||||
String shardKey = shard.getFloc().getShardMethod().name() + "~" + shard.getFloc().getNumberOfShards() + "~" + shard.getInstance();
|
||||
|
||||
PersistedNode backupNode = (PersistedNode) this.attributeService.getAttribute(Constants.ATTR_ASIE, ATTR_BACKUP_NODE, shardKey);
|
||||
this.logger.debug("Found backup node: {}", backupNode);
|
||||
|
||||
if (backupNode == null || backupNode.isExpired()) {
|
||||
ShardInstance backupShardInstance = this.sds.computeLeadShard(shardNodes);
|
||||
backupNode = new PersistedNode(new Node(backupShardInstance));
|
||||
this.attributeService.setAttribute(backupNode, Constants.ATTR_ASIE, ATTR_BACKUP_NODE, shardKey);
|
||||
}
|
||||
|
||||
return backupNode.getNode();
|
||||
}
|
||||
|
||||
public void forget() {
|
||||
this.attributeService.removeAttribute(Constants.ATTR_ASIE, ATTR_BACKUP_NODE);
|
||||
}
|
||||
|
||||
public void forget(ShardState shardNode) {
|
||||
ShardInstance nodeShard = shardNode.getShardInstance();
|
||||
Shard shard = nodeShard.getShard();
|
||||
String shardKey = shard.getFloc().getShardMethod().name() + "~" + shard.getFloc().getNumberOfShards() + "~" + shard.getInstance();
|
||||
|
||||
this.attributeService.removeAttribute(Constants.ATTR_ASIE, ATTR_BACKUP_NODE, shardKey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class PersistedNode implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 4105196543023419818L;
|
||||
|
||||
private final Node node;
|
||||
private long expireTimeMillis;
|
||||
|
||||
PersistedNode(Node node) {
|
||||
this.node = node;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
this.expireTimeMillis = System.currentTimeMillis() + persistTimeMinutes * 60L * 1000L;
|
||||
}
|
||||
|
||||
boolean isExpired() {
|
||||
return this.expireTimeMillis < System.currentTimeMillis();
|
||||
}
|
||||
|
||||
Node getNode() {
|
||||
return this.node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "node: " + this.node + "; expires in: " + (System.currentTimeMillis() - this.expireTimeMillis) + " ms";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
package com.inteligr8.alfresco.asie.service;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.index.shard.Floc;
|
||||
import org.alfresco.repo.index.shard.Shard;
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
import org.alfresco.repo.index.shard.ShardMethodEnum;
|
||||
import org.alfresco.repo.index.shard.ShardRegistry;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.alfresco.util.Pair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.inteligr8.alfresco.asie.Constants;
|
||||
import com.inteligr8.alfresco.asie.model.ShardSet;
|
||||
|
||||
@Component
|
||||
public class ShardDiscoveryService implements com.inteligr8.alfresco.asie.spi.ShardDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(this.getClass());
|
||||
|
||||
@Autowired
|
||||
@Qualifier(Constants.QUALIFIER_ASIE)
|
||||
private ShardRegistry shardRegistry;
|
||||
|
||||
public ShardInstance computeLeadShard(Collection<ShardState> shardNodesCache) {
|
||||
if (shardNodesCache.isEmpty())
|
||||
return null;
|
||||
|
||||
long latestTime = 0L;
|
||||
ShardInstance latestNode = null;
|
||||
|
||||
for (ShardState shardNodeCache : shardNodesCache) {
|
||||
if (latestTime < shardNodeCache.getLastIndexedTxCommitTime()) {
|
||||
latestNode = shardNodeCache.getShardInstance();
|
||||
latestTime = shardNodeCache.getLastIndexedTxCommitTime();
|
||||
}
|
||||
}
|
||||
|
||||
return latestNode;
|
||||
}
|
||||
|
||||
public Set<ShardState> findByNode(String nodeHostname, int nodePort) {
|
||||
Map<Floc, Map<Shard, Set<ShardState>>> flocs = this.shardRegistry.getFlocs();
|
||||
if (flocs.isEmpty())
|
||||
return Collections.emptySet();
|
||||
|
||||
Set<ShardState> shards = new HashSet<>();
|
||||
|
||||
for (Entry<Floc, Map<Shard, Set<ShardState>>> floc : flocs.entrySet()) {
|
||||
for (Entry<Shard, Set<ShardState>> flocShard : floc.getValue().entrySet()) {
|
||||
for (ShardState shardState : flocShard.getValue()) {
|
||||
ShardInstance shardInstance = shardState.getShardInstance();
|
||||
if (!nodeHostname.equalsIgnoreCase(shardInstance.getHostName())) {
|
||||
InetAddress nodeAddress = this.resolve(nodeHostname);
|
||||
if (nodeAddress == null)
|
||||
continue;
|
||||
|
||||
InetAddress shardInstanceAddress = this.resolve(shardInstance.getHostName());
|
||||
if (!nodeAddress.equals(shardInstanceAddress))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (nodePort == shardInstance.getPort())
|
||||
shards.add(shardState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shards;
|
||||
}
|
||||
|
||||
public Map<Shard, Set<ShardState>> findByShardSet(ShardSet shardSet) {
|
||||
Map<Floc, Map<Shard, Set<ShardState>>> flocs = this.shardRegistry.getFlocs();
|
||||
if (flocs.isEmpty())
|
||||
return Collections.emptyMap();
|
||||
this.logger.trace("Found {} shard sets", flocs.size());
|
||||
|
||||
for (Entry<Floc, Map<Shard, Set<ShardState>>> floc : flocs.entrySet()) {
|
||||
if (!floc.getKey().getShardMethod().equals(shardSet.getMethod()))
|
||||
continue;
|
||||
|
||||
if (!shardSet.getConfig().isEmpty()) {
|
||||
if (floc.getValue().isEmpty())
|
||||
continue;
|
||||
Shard firstShard = floc.getValue().keySet().iterator().next();
|
||||
|
||||
Set<ShardState> firstShardStates = floc.getValue().get(firstShard);
|
||||
if (firstShardStates == null || firstShardStates.isEmpty())
|
||||
continue;
|
||||
ShardState firstShardState = firstShardStates.iterator().next();
|
||||
|
||||
Map<String, String> firstShardProps = firstShardState.getPropertyBag();
|
||||
if (!shardSet.isConfigurationFor(firstShardProps))
|
||||
continue;
|
||||
}
|
||||
|
||||
return floc.getValue();
|
||||
}
|
||||
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
public Collection<Pair<Floc, Map<Shard, Set<ShardState>>>> findByShardMethod(ShardMethodEnum shardMethod) {
|
||||
Map<Floc, Map<Shard, Set<ShardState>>> flocs = this.shardRegistry.getFlocs();
|
||||
if (flocs.isEmpty())
|
||||
return Collections.emptyList();
|
||||
this.logger.trace("Found {} shard sets", flocs.size());
|
||||
|
||||
List<Pair<Floc, Map<Shard, Set<ShardState>>>> filteredFlocs = new LinkedList<>();
|
||||
|
||||
for (Entry<Floc, Map<Shard, Set<ShardState>>> floc : flocs.entrySet()) {
|
||||
if (!floc.getKey().getShardMethod().equals(shardMethod))
|
||||
continue;
|
||||
filteredFlocs.add(new Pair<>(floc.getKey(), floc.getValue()));
|
||||
}
|
||||
|
||||
return filteredFlocs;
|
||||
}
|
||||
|
||||
public <T> Set<T> filterByShard(Map<Shard, Set<T>> shards, int shardId) {
|
||||
if (shards == null)
|
||||
return null;
|
||||
|
||||
for (Entry<Shard, Set<T>> shard : shards.entrySet()) {
|
||||
if (shard.getKey().getInstance() == shardId)
|
||||
return shard.getValue();
|
||||
}
|
||||
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public Set<ShardState> findByShard(ShardSet shardSet, int shardId) {
|
||||
Map<Shard, Set<ShardState>> shards = this.findByShardSet(shardSet);
|
||||
return this.filterByShard(shards, shardId);
|
||||
}
|
||||
|
||||
private InetAddress resolve(String hostname) {
|
||||
try {
|
||||
return InetAddress.getByName(hostname);
|
||||
} catch (UnknownHostException uhe) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.inteligr8.alfresco.asie.service;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.apache.commons.codec.digest.MurmurHash3;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class SolrShardHashService {
|
||||
|
||||
private final Charset charset = Charset.forName("utf-8");
|
||||
|
||||
public int hash(Object obj, int shardCount) {
|
||||
String str = obj.toString();
|
||||
byte[] bytes = str.getBytes(this.charset);
|
||||
|
||||
// From ASIE source:
|
||||
// Math.abs(Hash.murmurhash3_x86_32(shardBy, 0, shardBy.length(), 66)) % shardCount
|
||||
|
||||
// Using Apache Commons Codec:
|
||||
MurmurHash3.IncrementalHash32x86 hash = new MurmurHash3.IncrementalHash32x86();
|
||||
hash.start(66);
|
||||
hash.add(bytes, 0, bytes.length);
|
||||
return Math.abs(hash.end()) % shardCount;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
package com.inteligr8.alfresco.asie.spi;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
|
||||
import com.inteligr8.alfresco.asie.model.Node;
|
||||
|
||||
public interface ShardBackupService {
|
||||
|
||||
Node fetchNode(Collection<ShardState> shardNodes);
|
||||
|
||||
void forget();
|
||||
|
||||
void forget(ShardState shardNode);
|
||||
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
package com.inteligr8.alfresco.asie.spi;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.repo.index.shard.Floc;
|
||||
import org.alfresco.repo.index.shard.Shard;
|
||||
import org.alfresco.repo.index.shard.ShardInstance;
|
||||
import org.alfresco.repo.index.shard.ShardMethodEnum;
|
||||
import org.alfresco.repo.index.shard.ShardState;
|
||||
import org.alfresco.util.Pair;
|
||||
|
||||
import com.inteligr8.alfresco.asie.model.ShardSet;
|
||||
|
||||
public interface ShardDiscoveryService {
|
||||
|
||||
/**
|
||||
* Determine the lead shard in the specified node/shard snapshot metadata.
|
||||
*
|
||||
* @param shardNodes A collection of snapshot metadata.
|
||||
* @return A single node/shard holding the latest snapshot metadata.
|
||||
*/
|
||||
ShardInstance computeLeadShard(Collection<ShardState> shardNodes);
|
||||
|
||||
/**
|
||||
* Find the latest snapshot of each shard on the specified node.
|
||||
*
|
||||
* @param nodeHostname The hostname of a ASIE node.
|
||||
* @param nodePort The port of an ASIE node.
|
||||
* @return A set of the latest snapshot metadata of shards.
|
||||
*/
|
||||
Set<ShardState> findByNode(String nodeHostname, int nodePort);
|
||||
|
||||
/**
|
||||
* Find the shards, their nodes, and the latest snapshot of each within the
|
||||
* specified shard set.
|
||||
*
|
||||
* @param shardSet A shard set.
|
||||
* @return A map of shards to sets of the latest snapshot metadata of those shards and their nodes.
|
||||
*/
|
||||
Map<Shard, Set<ShardState>> findByShardSet(ShardSet shardSet);
|
||||
|
||||
/**
|
||||
* Find the shards, their nodes, and the latest snapshot of each using the
|
||||
* specified shard method.
|
||||
*
|
||||
* @param shardMethod A shard method.
|
||||
* @return A collection of maps of shards to sets of the latest snapshot metadata of those shards and their nodes.
|
||||
*/
|
||||
Collection<Pair<Floc, Map<Shard, Set<ShardState>>>> findByShardMethod(ShardMethodEnum shardMethod);
|
||||
|
||||
/**
|
||||
* Filter the latest snapshot of each shard.
|
||||
*
|
||||
* @param shards A map of shards to sets of the latest snapshot metadata of those shards and their nodes.
|
||||
* @param shardId A 0-based index of a shard.
|
||||
* @return A set of the latest snapshot metadata of shards.
|
||||
*/
|
||||
<T> Set<T> filterByShard(Map<Shard, Set<T>> shards, int shardId);
|
||||
|
||||
/**
|
||||
* Find the latest snapshot of each shard and their nodes within the
|
||||
* specified shard set.
|
||||
*
|
||||
* @param shardSet A shard set.
|
||||
* @param shardId A 0-based index of a shard.
|
||||
* @return A set of the latest snapshot metadata of shards.
|
||||
*/
|
||||
Set<ShardState> findByShard(ShardSet shardSet, int shardId);
|
||||
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.inteligr8.alfresco.asie.spi;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.alfresco.service.cmr.attributes.AttributeService.AttributeQueryCallback;
|
||||
|
||||
public interface ShardStateService {
|
||||
|
||||
/**
|
||||
* Clears the shard state.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
void remove(Serializable... keys);
|
||||
|
||||
void iterate(AttributeQueryCallback callback);
|
||||
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Retrieve Backup ASIE Node for ASIE Shard</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Retrieve a reference to the ASIE node that should be used for the backup of the specified ASIE shard registered with ACS.</p>
|
||||
<p>The following path parameters are expected:</p>
|
||||
<dl>
|
||||
<dt>shardSet</dt>
|
||||
<dd>A shard method combined with its distinguishing properties;
|
||||
methods: MOD_ACL_ID, ACL_ID, DB_ID, DB_ID_RANGE, DATE, PROPERTY, EXPLICIT_ID;
|
||||
e.g. PROPERTY;key:cm:created;regex:^d{4} or DB_ID</dd>
|
||||
<dt>shardId</dt>
|
||||
<dd>A number starting at 1</dd>
|
||||
</dl>
|
||||
<p>The response will have the following format:</p>
|
||||
<pre>"hostname:port/baseUrl"</pre>
|
||||
<p>This is meant to help determine which shard should be backed up.
|
||||
The node is computed based on the lead shard on the first request.
|
||||
That value is cached until a reques isn't made for at least a configurable amount of time.</p>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>204</dt>
|
||||
<dd>No ASIE node/shard information available</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path parameters are invalid</dd>
|
||||
<dt>404</dt>
|
||||
<dd>The specified shard set or shard ID could not be found</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/shard/{shardSet}/{shardId}/backup</url>
|
||||
<format default="json">any</format>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>none</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,45 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Retrieve Lead ASIE Node for ASIE Shard</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Retrieve a reference to the most current/up-to-date ASIE node for the specified ASIE shard registered with ACS.</p>
|
||||
<p>The following path parameters are expected:</p>
|
||||
<dl>
|
||||
<dt>shardSet</dt>
|
||||
<dd>A shard method combined with its distinguishing properties;
|
||||
methods: MOD_ACL_ID, ACL_ID, DB_ID, DB_ID_RANGE, DATE, PROPERTY, EXPLICIT_ID;
|
||||
e.g. PROPERTY;key:cm:created;regex:^d{4} or DB_ID</dd>
|
||||
<dt>shardId</dt>
|
||||
<dd>A number starting at 1</dd>
|
||||
</dl>
|
||||
<p>The response will have the following format:</p>
|
||||
<pre>"hostname:port/baseUrl"</pre>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>204</dt>
|
||||
<dd>No ASIE node/shard information available</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path parameters are invalid</dd>
|
||||
<dt>404</dt>
|
||||
<dd>The specified shard set or shard ID could not be found</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/shard/{shardSet}/{shardId}/lead</url>
|
||||
<format default="json">any</format>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>none</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,40 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Clears ASIE Node from Registry</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Clears meta-data about all ASIE shards on a single ASIE node registered with ACS.
|
||||
This call will attempt to unload all shards on the ASIE node to prevent automatic registration on its next index attempt.
|
||||
If that disablement fails, this call will continue to unregister all shards on the node from ACS; however, they will automatically register again on its next indexing attempt.</p>
|
||||
<p>The following path parameters are expected:</p>
|
||||
<dl>
|
||||
<dt>nodeEndpoint</dt>
|
||||
<dd>A hostname or hostname:port for the ASIE node</dd>
|
||||
</dl>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path parameters are invalid</dd>
|
||||
<dt>404</dt>
|
||||
<dd>The specified ASIE node could not be found</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/node/{nodeEndpoint}/shards</url>
|
||||
<url>/inteligr8/asie/node/{nodeEndpoint}</url>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,69 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Retrieves ASIE Node Information/Status</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Retrieves meta-data about all shards on a single ASIE node as registred with ACS.</p>
|
||||
<p>The following query parameter is supported:</p>
|
||||
<dl>
|
||||
<dt>nodeEndpoint</dt>
|
||||
<dd>A hostname or hostname:port for the ASIE node; dots are not allowed, you may use _ (underscore) instead</dd>
|
||||
<dt>sampleType</dt>
|
||||
<dd>A sample hash type; Sample hash types: PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek</dd>
|
||||
</dl>
|
||||
<p>The response will have the following format:</p>
|
||||
<pre>{
|
||||
"url": string,
|
||||
"hostname": string,
|
||||
"shards": {
|
||||
"string": { // shard ID as string
|
||||
"id": number,
|
||||
"txsCompleted": number,
|
||||
"txsRemaining": number,
|
||||
"latestTx": "datetime",
|
||||
"shardSet": {
|
||||
"id": "string", // generated shard set ID
|
||||
"method": "string",
|
||||
"methodDetail": "string",
|
||||
"hasContent": boolean,
|
||||
"shardCount": number,
|
||||
"shardHashSamples": {
|
||||
"string": [ // shard ID as string
|
||||
"string": // year as string
|
||||
],
|
||||
...
|
||||
}
|
||||
}
|
||||
},
|
||||
...
|
||||
]
|
||||
}</pre>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>204</dt>
|
||||
<dd>No ASIE shard information available</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path or query parameters are invalid</dd>
|
||||
<dt>404</dt>
|
||||
<dd>The specified ASIE node instance could not be found</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/node/{nodeEndpoint}?sampleHashType={sampleHashType?}</url>
|
||||
<format default="json">any</format>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,42 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Adds ASIE Node to Registry</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Loads all previously registered ASIE shards on a single ASIE node, which will eventually register with ACS.</p>
|
||||
<p>The following path parameters are expected:</p>
|
||||
<dl>
|
||||
<dt>nodeEndpoint</dt>
|
||||
<dd>A hostname or hostname:port for the ASIE node</dd>
|
||||
<dt>coreName</dt>
|
||||
<dd>A core name to restore in addition to known cores</dd>
|
||||
<dt>shardRange</dt>
|
||||
<dd>A shard range restore in addition to known shards (e.g. 0-7)</dd>
|
||||
</dl>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path parameters are invalid</dd>
|
||||
<dt>404</dt>
|
||||
<dd>The specified ASIE node was not previously registered</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/node/{nodeEndpoint}/shards?coreName={coreName?}&shardRange={shardRange?}&template={template?}&shardCount={shardCount?}&nodeId={nodeId?}&nodeCount={nodeCount?}</url>
|
||||
<url>/inteligr8/asie/node/{nodeEndpoint}?coreName={coreName?}&shardRange={shardRange?}&template={template?}&shardCount={shardCount?}&nodeId={nodeId?}&nodeCount={nodeCount?}</url>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,45 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Clears ASIE Node/Shard from Registry</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Clears meta-data about a single ASIE shard on a single ASIE node registred with ACS.
|
||||
This call will attempt to disable further indexing by the ASIE node of only that shard to prevent automatic registration on its next index attempt.
|
||||
If that disablement fails, this call will continue to unregister the node/shard from ACS; however, it will automatically register again on its next indexing attempt.</p>
|
||||
<p>The following path parameters are expected:</p>
|
||||
<dl>
|
||||
<dt>nodeEndpoint</dt>
|
||||
<dd>A hostname or hostname:port for the ASIE node</dd>
|
||||
<dt>shardSet</dt>
|
||||
<dd>A shard method combined with its distinguishing properties;
|
||||
methods: MOD_ACL_ID, ACL_ID, DB_ID, DB_ID_RANGE, DATE, PROPERTY, EXPLICIT_ID;
|
||||
e.g. PROPERTY;key:cm:created;regex:^d{4} or DB_ID</dd>
|
||||
<dt>shardId</dt>
|
||||
<dd>A number starting at 1</dd>
|
||||
</dl>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path parameters are invalid</dd>
|
||||
<dt>404</dt>
|
||||
<dd>The specified ASIE node, shard set, or shard ID could not be found</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/node/{nodeEndpoint}/shard/{shardSet}/{shardId}</url>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,41 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Adds ASIE Node to Registry</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Loads an ASIE shard on a single ASIE node, which will eventually register with ACS.</p>
|
||||
<p>The following path parameters are expected:</p>
|
||||
<dl>
|
||||
<dt>nodeEndpoint</dt>
|
||||
<dd>A hostname or hostname:port for the ASIE node</dd>
|
||||
<dt>shardCore</dt>
|
||||
<dd>A core name (prefix) for the ASIE shard (e.g. alfresco)</dd>
|
||||
<dt>shardId</dt>
|
||||
<dd>A numeric shard ID for the ASIE shard (e.g. 0)</dd>
|
||||
</dl>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path parameters are invalid</dd>
|
||||
<dt>404</dt>
|
||||
<dd>The specified ASIE node could not be found</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/node/{nodeEndpoint}/shard/{shardCore}/{shardId}</url>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,65 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Retrieve ASIE Node Information/Status</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Retrieve meta-data about the ASIE nodes and their shards registered with ACS.</p>
|
||||
<p>The following query parameter is supported:</p>
|
||||
<dl>
|
||||
<dt>sampleHashType</dt>
|
||||
<dd>A sample hash type; Sample hash types: PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek</dd>
|
||||
</dl>
|
||||
<p>The response will have the following format:</p>
|
||||
<pre>{
|
||||
"nodes": {
|
||||
"string": { // node URL
|
||||
"id": "string",
|
||||
"shards": {
|
||||
"string": { // shard ID as string
|
||||
"id": number,
|
||||
"txsCompleted": number,
|
||||
"txsRemaining": number,
|
||||
"latestTx": "datetime",
|
||||
"shardSet": {
|
||||
"methodSpec": "string", // generated shard set ID
|
||||
"shardCount": number,
|
||||
"shardHashSamples": {
|
||||
"string": [ // shard ID as string
|
||||
"string": // year as string
|
||||
],
|
||||
...
|
||||
}
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
}</pre>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>204</dt>
|
||||
<dd>No ASIE shard information available</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The query parameter is invalid</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/nodes?sampleHashType={sampleHashType?}</url>
|
||||
<format default="json">any</format>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,72 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Retrieve ASIE Shard Hash Status</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Retrieve meta-data about the ASIE shard hashes of the `PROPERTY` method registered with ACS.
|
||||
Sample hashes are performed given the query parameters, which allows for reverse hashes of those samples in the response.
|
||||
A min/max or an enumerated list of values must also be provided.</p>
|
||||
<p>The following path and query parameters is expected or supported:</p>
|
||||
<dl>
|
||||
<dt>sampleHashType</dt>
|
||||
<dd>PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek</dd>
|
||||
<dt>shards</dt>
|
||||
<dd>The total number of shards expected</dd>
|
||||
<dt>min</dt>
|
||||
<dd>For numerical hashes, the minimum of an integer range to compute sample hashes</dd>
|
||||
<dt>max</dt>
|
||||
<dd>For numerical hashes, the maximum of an integer range to compute sample hashes</dd>
|
||||
<dt>values</dt>
|
||||
<dd>For any hashes, a comma-delimited enumeration of values to compute sample hashes</dd>
|
||||
</dl>
|
||||
<p>The response will have the following format:</p>
|
||||
<pre>[
|
||||
{
|
||||
"methodSpec": "string", // generated shard set ID
|
||||
"shardCount": number
|
||||
"shards": {
|
||||
"string": { // hash
|
||||
"id": number,
|
||||
"txsCompleted": number,
|
||||
"txsRemaining": number,
|
||||
"latestTx": "datetime",
|
||||
"nodes": {
|
||||
"string": { // ASIE node URL
|
||||
"url": "string",
|
||||
"hostname": "string"
|
||||
},
|
||||
...
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
},
|
||||
...
|
||||
]</pre>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>204</dt>
|
||||
<dd>No ASIE PROPERTY shard information available</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path or query parameters are invalid</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/shards/byHash/{sampleHashType}/{shards}?min={min?}&max={max?}&values={values?}</url>
|
||||
<format default="json">any</format>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,32 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Clear ASIE Node/Shard Registry</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Clears all meta-data about the ASIE shard nodes and instances registred with ACS.
|
||||
This is more extreme than the OOTB purge capability, which sometimes errors rather than purging.
|
||||
This will avoid those errors, clearing all shard registration information regardless of how mess up they may be.</p>
|
||||
<p>If there are no shards, this will effectively do nothing.</p>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/shards</url>
|
||||
<url>/inteligr8/asie/nodes</url>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,58 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Compute ASIE Sample Hash Table</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Compute sample hashes.
|
||||
Sample hashes are performed given the query parameters, which allows for reverse hashes of those samples in the response.
|
||||
A min/max or an enumerated list of values must also be provided.</p>
|
||||
<p>The following path and query parameters is expected or supported:</p>
|
||||
<dl>
|
||||
<dt>shards</dt>
|
||||
<dd>The total number of shards expected</dd>
|
||||
<dt>min</dt>
|
||||
<dd>For numerical hashes, the minimum of an integer range to compute sample hashes</dd>
|
||||
<dt>max</dt>
|
||||
<dd>For numerical hashes, the maximum of an integer range to compute sample hashes</dd>
|
||||
<dt>values</dt>
|
||||
<dd>For any hashes, a comma-delimited enumeration of values to compute sample hashes</dd>
|
||||
</dl>
|
||||
<p>The response will have the following format:</p>
|
||||
<pre>{
|
||||
"forward": {
|
||||
"string": number, // sample value to shard ID
|
||||
...
|
||||
},
|
||||
"reverse": {
|
||||
"number": [ // shard ID, starting at 0
|
||||
"string", // sample value
|
||||
...
|
||||
],
|
||||
...
|
||||
}
|
||||
}</pre>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The path or query parameters are invalid</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/hash/{shards}?min={min?}&max={max?}&values={values?}</url>
|
||||
<format default="json">any</format>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,73 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Retrieve ASIE Shard Information/Status</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Retrieve meta-data about the specified ASIE shard registered with ACS.</p>
|
||||
<p>The following path and query parameters are expected or supported:</p>
|
||||
<dl>
|
||||
<dt>shardSet</dt>
|
||||
<dd>A shard method combined with its distinguishing properties;
|
||||
methods: MOD_ACL_ID, ACL_ID, DB_ID, DB_ID_RANGE, DATE, PROPERTY, EXPLICIT_ID;
|
||||
e.g. PROPERTY;key:cm:created;regex:^d{4} or DB_ID</dd>
|
||||
<dt>shardId</dt>
|
||||
<dd>A number starting at 1</dd>
|
||||
<dt>sampleHashType</dt>
|
||||
<dd>A sample hash type; Sample hash types: PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek</dd>
|
||||
</dl>
|
||||
<p>The response will have the following format:</p>
|
||||
<pre>{
|
||||
"id": number,
|
||||
"txsCompleted": number,
|
||||
"txsRemaining": number,
|
||||
"latestTx": "datetime",
|
||||
"nodes": {
|
||||
"string": { // ASIE node URL
|
||||
"url": "string",
|
||||
"hostname": "string"
|
||||
},
|
||||
...
|
||||
},
|
||||
"shardSet": {
|
||||
"id": "string", // generated shard set ID
|
||||
"method": "string",
|
||||
"methodDetail": "string",
|
||||
"hasContent": boolean,
|
||||
"shardCount": number,
|
||||
"shardHashSamples": {
|
||||
"string": [ // shard ID as string
|
||||
"string": // year as string
|
||||
],
|
||||
...
|
||||
}
|
||||
}
|
||||
}</pre>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>204</dt>
|
||||
<dd>No ASIE shard information available</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The parameters are invalid</dd>
|
||||
<dt>404</dt>
|
||||
<dd>The specified shard set or shard ID could not be found</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/shard/{shardSet}/{shardId}?includeSampleHashes={includeSampleHashes?}</url>
|
||||
<format default="json">any</format>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,68 @@
|
||||
<webscript xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://bitbucket.org/!api/2.0/snippets/inteligr8/AzMgbp/80fdd26a6b3769a63cdc6b54bf1f39e378545cf7/files/snippet.txt">
|
||||
|
||||
<!-- Naming & Organization -->
|
||||
<shortname>Retrieve ASIE Shard Information/Status</shortname>
|
||||
<family>Inteligr8 ASIE</family>
|
||||
<description><![CDATA[
|
||||
<p>Retrieve meta-data about all the ASIE shards registered with ACS.</p>
|
||||
<p>The following query parameter is supported:</p>
|
||||
<dl>
|
||||
<dt>sampleHashType</dt>
|
||||
<dd>A sample hash type; Sample hash types: PropertyYear, PropertyQuarter, PropertyMonth, PropertyWeek</dd>
|
||||
</dl>
|
||||
<p>The response will have the following format:</p>
|
||||
<pre>{
|
||||
"string": { // generated shard set ID
|
||||
"methodSpec": "string", // generated shard set ID
|
||||
"shardCount": number
|
||||
"shards": {
|
||||
"string": { // shard ID as string
|
||||
"id": number,
|
||||
"txsCompleted": number,
|
||||
"txsRemaining": number,
|
||||
"latestTx": "datetime",
|
||||
"nodes": {
|
||||
"string": { // ASIE node URL
|
||||
"id": "string",
|
||||
"txsCompleted": number,
|
||||
"latestTx": "datetime",
|
||||
},
|
||||
...
|
||||
}
|
||||
},
|
||||
...
|
||||
},
|
||||
"shardHashSamples": {
|
||||
"string": [ // shard ID as string
|
||||
"string": // year as string
|
||||
],
|
||||
...
|
||||
}
|
||||
}
|
||||
}</pre>
|
||||
<p>The following status codes should be expected:</p>
|
||||
<dl>
|
||||
<dt>200</dt>
|
||||
<dd>OK</dd>
|
||||
<dt>204</dt>
|
||||
<dd>No ASIE shard information available</dd>
|
||||
<dt>400</dt>
|
||||
<dd>The query parameter is invalid</dd>
|
||||
</dl>
|
||||
]]></description>
|
||||
|
||||
<!-- Endpoint Configuration -->
|
||||
<url>/inteligr8/asie/shards?sampleHashType={sampleHashType?}</url>
|
||||
<format default="json">any</format>
|
||||
|
||||
<!-- Security -->
|
||||
<authentication>admin</authentication>
|
||||
|
||||
<!-- Functionality -->
|
||||
<cache>
|
||||
<never>false</never>
|
||||
<public>false</public>
|
||||
</cache>
|
||||
|
||||
</webscript>
|
@@ -0,0 +1,22 @@
|
||||
|
||||
# defaulting to 3 days = 60 * 24 * 3 = 4320
|
||||
inteligr8.asie.backup.persistTimeMinutes=4320
|
||||
|
||||
inteligr8.asie.allowedAuthorities=ALFRESCO_ADMINISTRATORS
|
||||
|
||||
# same as solr.baseUrl, but that property is private to the Search subsystem
|
||||
inteligr8.asie.basePath=/solr
|
||||
|
||||
|
||||
|
||||
# Overrides of alfresco-repository.jar/alfresco/caches.properties
|
||||
cache.shardStateSharedCache.tx.maxItems=0
|
||||
cache.shardStateSharedCache.tx.statsEnabled=${caches.tx.statsEnabled}
|
||||
cache.shardStateSharedCache.maxItems=0
|
||||
cache.shardStateSharedCache.timeToLiveSeconds=1800
|
||||
cache.shardStateSharedCache.maxIdleSeconds=0
|
||||
cache.shardStateSharedCache.cluster.type=fully-distributed
|
||||
cache.shardStateSharedCache.backup-count=1
|
||||
cache.shardStateSharedCache.eviction-policy=LRU
|
||||
cache.shardStateSharedCache.merge-policy=com.hazelcast.spi.merge.PutIfAbsentMergePolicy
|
||||
cache.shardStateSharedCache.readBackupData=false
|
@@ -0,0 +1,3 @@
|
||||
|
||||
logger.inteligr8-asie.name=com.inteligr8.alfresco.asie
|
||||
logger.inteligr8-asie.level=INFO
|
@@ -0,0 +1,14 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
|
||||
<!-- Use this file for beans to be loaded in whatever order Alfresco/Spring decides -->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
|
||||
|
||||
<!-- Enable Spring annotation scanning for classes -->
|
||||
<context:component-scan base-package="com.inteligr8.alfresco.asie"
|
||||
name-generator="org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator" />
|
||||
|
||||
</beans>
|
Reference in New Issue
Block a user