refactored; split enterprise from generic

This commit is contained in:
2024-10-31 14:48:17 -04:00
parent ae4f664ba7
commit dc13bc09de
85 changed files with 897 additions and 400 deletions

12
shared/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
# Maven
target
pom.xml.versionsBackup
# Eclipse
.project
.classpath
.settings
.vscode
# IDEA
/.idea/

1
shared/README.md Normal file
View File

@@ -0,0 +1 @@
# ASIE Platform Module Library

73
shared/pom.xml Normal file
View 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
View 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
View 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!"

View File

@@ -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";
}

View File

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

View File

@@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
package com.inteligr8.alfresco.asie.model;
public interface RequestParameterSet {
}

View File

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

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View File

@@ -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");
}
}

View File

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

View File

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

View File

@@ -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
}
}

View File

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

View File

@@ -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());
}
}

View File

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

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}
}

View File

@@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
}
}

View File

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

View File

@@ -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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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";
}
}
}

View File

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

View File

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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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?}&amp;shardRange={shardRange?}&amp;template={template?}&amp;shardCount={shardCount?}&amp;nodeId={nodeId?}&amp;nodeCount={nodeCount?}</url>
<url>/inteligr8/asie/node/{nodeEndpoint}?coreName={coreName?}&amp;shardRange={shardRange?}&amp;template={template?}&amp;shardCount={shardCount?}&amp;nodeId={nodeId?}&amp;nodeCount={nodeCount?}</url>
<!-- Security -->
<authentication>admin</authentication>
<!-- Functionality -->
<cache>
<never>false</never>
<public>false</public>
</cache>
</webscript>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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?}&amp;max={max?}&amp;values={values?}</url>
<format default="json">any</format>
<!-- Security -->
<authentication>admin</authentication>
<!-- Functionality -->
<cache>
<never>false</never>
<public>false</public>
</cache>
</webscript>

View File

@@ -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>

View File

@@ -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?}&amp;max={max?}&amp;values={values?}</url>
<format default="json">any</format>
<!-- Security -->
<authentication>admin</authentication>
<!-- Functionality -->
<cache>
<never>false</never>
<public>false</public>
</cache>
</webscript>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -0,0 +1,3 @@
logger.inteligr8-asie.name=com.inteligr8.alfresco.asie
logger.inteligr8-asie.level=INFO

View File

@@ -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>